diff --git a/.chronus/changes/emitter-options-use-defaults-2026-3-16-22-18-0.md b/.chronus/changes/emitter-options-use-defaults-2026-3-16-22-18-0.md new file mode 100644 index 00000000000..95963f17f1e --- /dev/null +++ b/.chronus/changes/emitter-options-use-defaults-2026-3-16-22-18-0.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/compiler" +--- + +Apply JSON schema `default` values to emitter options so they appear in `context.options` during `$onEmit`. diff --git a/.chronus/changes/fix-openapi3-multipart-union-bytes-2026-3-16.md b/.chronus/changes/fix-openapi3-multipart-union-bytes-2026-3-16.md new file mode 100644 index 00000000000..264194d9ed4 --- /dev/null +++ b/.chronus/changes/fix-openapi3-multipart-union-bytes-2026-3-16.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/openapi3" +--- + +Fix OpenAPI emitter failing with "Duplicate type name" error when using a named union with a `bytes` variant in a multipart body (e.g. `HttpPart` where `MyUnion` includes `bytes`). diff --git a/packages/compiler/src/core/library.ts b/packages/compiler/src/core/library.ts index 24687c6f774..6c417ee5987 100644 --- a/packages/compiler/src/core/library.ts +++ b/packages/compiler/src/core/library.ts @@ -88,6 +88,7 @@ export function createTypeSpecLibrary< if (!emitterOptionValidator && lib.emitter?.options) { emitterOptionValidator = createJSONSchemaValidator(lib.emitter.options, { coerceTypes: true, + useDefaults: true, }); } return emitterOptionValidator; diff --git a/packages/compiler/src/core/schema-validator.ts b/packages/compiler/src/core/schema-validator.ts index f5b7d4439c1..0817d3c6788 100644 --- a/packages/compiler/src/core/schema-validator.ts +++ b/packages/compiler/src/core/schema-validator.ts @@ -16,6 +16,7 @@ import { export interface JSONSchemaValidatorOptions { coerceTypes?: boolean; strict?: boolean; + useDefaults?: boolean; } function absolutePathStatus(path: string): "valid" | "not-absolute" | "windows-style" { @@ -35,6 +36,7 @@ export function createJSONSchemaValidator( const ajv = new Ajv({ strict: options.strict, coerceTypes: options.coerceTypes, + useDefaults: options.useDefaults, allowUnionTypes: true, allErrors: true, } satisfies Options); diff --git a/packages/compiler/test/core/emitter-options.test.ts b/packages/compiler/test/core/emitter-options.test.ts index d0aa5077980..20f9365224f 100644 --- a/packages/compiler/test/core/emitter-options.test.ts +++ b/packages/compiler/test/core/emitter-options.test.ts @@ -20,6 +20,22 @@ const fakeEmitter = createTypeSpecLibrary({ }, }); +const fakeEmitterWithDefaults = createTypeSpecLibrary({ + name: "fake-emitter-defaults", + diagnostics: {}, + emitter: { + options: { + type: "object", + properties: { + "target-name": { type: "string", nullable: true, default: "defaultTarget" }, + "max-files": { type: "number", nullable: true, default: 10 }, + verbose: { type: "boolean", nullable: true, default: false }, + }, + additionalProperties: false, + }, + }, +}); + describe("compiler: emitter options", () => { async function runWithEmitterOptions( options: Record, @@ -127,4 +143,64 @@ describe("compiler: emitter options", () => { }); }); }); + + describe("schema defaults", () => { + async function runWithDefaultsEmitter( + options: Record, + ): Promise<[EmitContext | undefined, readonly Diagnostic[]]> { + let emitContext: EmitContext | undefined; + const diagnostics = await Tester.files({ + "node_modules/fake-emitter-defaults/package.json": JSON.stringify({ + main: "index.js", + }), + "node_modules/fake-emitter-defaults/index.js": mockFile.js({ + $lib: fakeEmitterWithDefaults, + $onEmit: (ctx: EmitContext) => { + emitContext = ctx; + }, + }), + }).diagnose("", { + compilerOptions: { + emit: ["fake-emitter-defaults"], + options: { + "fake-emitter-defaults": options, + }, + }, + }); + return [emitContext, diagnostics]; + } + + it("applies default values from schema when options are not provided", async () => { + const [context, diagnostics] = await runWithDefaultsEmitter({}); + expectDiagnosticEmpty(diagnostics); + ok(context, "Emit context should have been set."); + strictEqual(context.options["target-name"], "defaultTarget"); + strictEqual(context.options["max-files"], 10); + strictEqual(context.options["verbose"], false); + }); + + it("user-provided values override defaults", async () => { + const [context, diagnostics] = await runWithDefaultsEmitter({ + "target-name": "custom", + "max-files": 20, + verbose: true, + }); + expectDiagnosticEmpty(diagnostics); + ok(context, "Emit context should have been set."); + strictEqual(context.options["target-name"], "custom"); + strictEqual(context.options["max-files"], 20); + strictEqual(context.options["verbose"], true); + }); + + it("applies defaults only for missing options", async () => { + const [context, diagnostics] = await runWithDefaultsEmitter({ + "target-name": "custom", + }); + expectDiagnosticEmpty(diagnostics); + ok(context, "Emit context should have been set."); + strictEqual(context.options["target-name"], "custom"); + strictEqual(context.options["max-files"], 10); + strictEqual(context.options["verbose"], false); + }); + }); }); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientProvider.cs index c829e290267..7cb68c21967 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientProvider.cs @@ -106,7 +106,10 @@ public ClientProvider(InputClient inputClient) _publicCtorDescription = $"Initializes a new instance of {Name}."; ClientOptions = _inputClient.Parent is null ? ClientOptionsProvider.CreateClientOptionsProvider(_inputClient, this) : null; ClientOptionsParameter = ClientOptions != null ? ScmKnownParameters.ClientOptions(ClientOptions.Type) : null; - ClientSettings = ClientOptions != null ? new ClientSettingsProvider(_inputClient, this) : null; + bool isIndividuallyInitialized = (_inputClient.InitializedBy & InputClientInitializedBy.Individually) != 0; + ClientSettings = isIndividuallyInitialized + ? new ClientSettingsProvider(_inputClient, this) + : null; IsMultiServiceClient = _inputClient.IsMultiServiceClient; var apiKey = _inputAuth?.ApiKey; @@ -133,8 +136,7 @@ public ClientProvider(InputClient inputClient) this, initializationValue: Literal(apiKey.Prefix)) : null; - // skip auth fields for sub-clients - _apiKeyAuthFields = ClientOptions is null ? null : new(apiKeyAuthField, authorizationHeaderField, authorizationApiKeyPrefixField); + _apiKeyAuthFields = isIndividuallyInitialized ? new(apiKeyAuthField, authorizationHeaderField, authorizationApiKeyPrefixField) : null; } var tokenAuth = _inputAuth?.OAuth2; @@ -158,8 +160,7 @@ public ClientProvider(InputClient inputClient) var tokenCredentialScopesField = BuildTokenCredentialScopesField(tokenAuth, tokenCredentialType); - // skip auth fields for sub-clients - _oauth2Fields = ClientOptions is null ? null : new(tokenCredentialField, tokenCredentialScopesField); + _oauth2Fields = isIndividuallyInitialized ? new(tokenCredentialField, tokenCredentialScopesField) : null; } EndpointField = new( FieldModifiers.Private | FieldModifiers.ReadOnly, @@ -300,14 +301,8 @@ private IReadOnlyList GetSubClientInternalConstructorParamete PipelineProperty.AsParameter }; - if (_apiKeyAuthFields != null) - { - subClientParameters.Add(_apiKeyAuthFields.AuthField.AsParameter); - } - if (_oauth2Fields != null) - { - subClientParameters.Add(_oauth2Fields.AuthField.AsParameter); - } + // Auth credentials are NOT included here — the parent passes its authenticated + // pipeline, so the sub-client doesn't need separate credential parameters. subClientParameters.Add(_endpointParameter); subClientParameters.AddRange(ClientParameters); @@ -385,6 +380,12 @@ private IReadOnlyList GetClientParameters() public ClientOptionsProvider? ClientOptions { get; } public ClientSettingsProvider? ClientSettings { get; } + /// + /// Gets the effective — the client's own options for root clients, + /// or the root client's options for individually-initialized sub-clients. + /// + internal ClientOptionsProvider? EffectiveClientOptions => ClientOptions ?? GetRootClient()?.ClientOptions; + public PropertyProvider PipelineProperty { get; } public FieldProvider EndpointField { get; } @@ -647,7 +648,9 @@ void AppendPublicConstructors( foreach (var p in requiredParameters) { if (authParamName == null || p.Name != authParamName) + { initializerArgs.Add(p); + } } initializerArgs.Add(ClientOptionsParameter!); @@ -686,6 +689,14 @@ private IEnumerable BuildSettingsConstructors() yield break; } + // Only publicly constructible clients should get the Settings constructor. + // Internal clients (e.g., those made internal via custom code) cannot be + // constructed by consumers, so a public Settings constructor is not useful. + if (!DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public)) + { + yield break; + } + var settingsParam = new ParameterProvider(SettingsParamName, $"The settings for {Name}.", ClientSettings.Type); var experimentalAttr = new AttributeStatement(typeof(ExperimentalAttribute), [Literal(ClientSettingsProvider.ClientSettingsDiagnosticId)]); @@ -733,64 +744,108 @@ private IEnumerable BuildSettingsConstructors() private void AppendSubClientPublicConstructors(List constructors) { // For sub-clients that can be initialized individually, we need to create public constructors - // similar to the root client constructors but adapted for sub-client needs + // with the same auth pattern as the root client. var primaryConstructors = new List(); var secondaryConstructors = new List(); - // if there is key auth + var rootClient = GetRootClient(); + var clientOptionsParameter = rootClient?.ClientOptionsParameter; + var clientOptionsProvider = rootClient?.ClientOptions; + + if (clientOptionsParameter == null || clientOptionsProvider == null) + { + return; + } + + // Add the internal AuthenticationPolicy constructor first — public constructors chain to it. + var authPolicyParam = new ParameterProvider( + "authenticationPolicy", + $"The authentication policy to use for pipeline creation.", + new CSharpType(typeof(AuthenticationPolicy), isNullable: true)); + + var requiredNonAuthParams = GetRequiredParameters(null); + ParameterProvider[] internalConstructorParameters = [authPolicyParam, _endpointParameter, .. requiredNonAuthParams, clientOptionsParameter]; + + var internalConstructor = new ConstructorProvider( + new ConstructorSignature(Type, _publicCtorDescription, MethodSignatureModifiers.Internal, internalConstructorParameters), + BuildPrimaryConstructorBody(internalConstructorParameters, null, authPolicyParam, clientOptionsProvider, clientOptionsParameter, addExplicitValidation: true), + this); + primaryConstructors.Add(internalConstructor); + + // Add public constructors with auth — same pattern as root client if (_apiKeyAuthFields != null) { AppendSubClientPublicConstructorsForAuth(_apiKeyAuthFields, primaryConstructors, secondaryConstructors); } - // if there is oauth2 auth if (_oauth2Fields != null) { AppendSubClientPublicConstructorsForAuth(_oauth2Fields, primaryConstructors, secondaryConstructors); } - // if there is no auth + bool onlyContainsUnsupportedAuth = _inputAuth != null && _apiKeyAuthFields == null && _oauth2Fields == null; if (_apiKeyAuthFields == null && _oauth2Fields == null) { - AppendSubClientPublicConstructorsForAuth(null, primaryConstructors, secondaryConstructors); + AppendSubClientPublicConstructorsForAuth(null, primaryConstructors, secondaryConstructors, onlyContainsUnsupportedAuth); } constructors.AddRange(secondaryConstructors); constructors.AddRange(primaryConstructors); + // Add Settings constructor for individually-initialized sub-clients + foreach (var settingsConstructor in BuildSettingsConstructors()) + { + constructors.Add(settingsConstructor); + } + void AppendSubClientPublicConstructorsForAuth( AuthFields? authFields, List primaryConstructors, - List secondaryConstructors) - { - // For a sub-client with individual initialization, we need: - // - endpoint parameter - // - auth parameter (if auth exists) - // - client options parameter (we need to get this from the root client) - var rootClient = GetRootClient(); - var clientOptionsParameter = rootClient?.ClientOptionsParameter; - var clientOptionsProvider = rootClient?.ClientOptions; - if (clientOptionsParameter == null || clientOptionsProvider == null) + List secondaryConstructors, + bool onlyContainsUnsupportedAuth = false) + { + // Public constructor with credential parameter — delegates to the internal constructor via this(...). + var requiredParameters = GetRequiredParameters(authFields?.AuthField); + ParameterProvider[] primaryConstructorParameters = [_endpointParameter, .. requiredParameters, clientOptionsParameter]; + var constructorModifier = onlyContainsUnsupportedAuth ? MethodSignatureModifiers.Internal : MethodSignatureModifiers.Public; + + // Build the auth policy expression for the this() initializer + ValueExpression authPolicyArg = BuildAuthPolicyArgument(authFields, requiredParameters); + var initializerArgs = new List { authPolicyArg, _endpointParameter }; + string? authParamName = authFields != null + ? (authFields.AuthField.Name != TokenProviderFieldName ? CredentialParamName : authFields.AuthField.AsParameter.Name) + : null; + foreach (var p in requiredParameters) { - // Cannot create public constructor without client options - return; + if (authParamName == null || p.Name != authParamName) + { + initializerArgs.Add(p); + } } + initializerArgs.Add(clientOptionsParameter!); - var requiredParameters = GetRequiredParameters(authFields?.AuthField); - ParameterProvider[] primaryConstructorParameters = [_endpointParameter, .. requiredParameters, clientOptionsParameter]; var primaryConstructor = new ConstructorProvider( - new ConstructorSignature(Type, _publicCtorDescription, MethodSignatureModifiers.Public, primaryConstructorParameters), - BuildPrimaryConstructorBody(primaryConstructorParameters, authFields, null, clientOptionsProvider, clientOptionsParameter), + new ConstructorSignature(Type, _publicCtorDescription, constructorModifier, primaryConstructorParameters, + initializer: new ConstructorInitializer(false, initializerArgs)), + MethodBodyStatement.Empty, this); - primaryConstructors.Add(primaryConstructor); // If the endpoint parameter contains an initialization value, it is not required. ParameterProvider[] secondaryConstructorParameters = _endpointParameter.InitializationValue is null ? [_endpointParameter, .. requiredParameters] : [.. requiredParameters]; - var secondaryConstructor = BuildSecondaryConstructor(secondaryConstructorParameters, primaryConstructorParameters, MethodSignatureModifiers.Public); + var secondaryConstructor = BuildSecondaryConstructor(secondaryConstructorParameters, primaryConstructorParameters, constructorModifier); secondaryConstructors.Add(secondaryConstructor); + + // When endpoint has a default value and there are required parameters, + // add an additional constructor that accepts required parameters + options. + if (_endpointParameter.InitializationValue is not null && requiredParameters.Count > 0) + { + ParameterProvider[] simplifiedConstructorWithOptionsParameters = [.. requiredParameters, clientOptionsParameter]; + var simplifiedConstructorWithOptions = BuildSecondaryConstructor(simplifiedConstructorWithOptionsParameters, primaryConstructorParameters, constructorModifier); + secondaryConstructors.Add(simplifiedConstructorWithOptions); + } } } @@ -917,11 +972,18 @@ private MethodBodyStatement[] BuildPrimaryConstructorBody(IReadOnlyList().Create(clientOptionsParameter, perRetryWithAuth)).Terminate(), + PipelineProperty.Assign(This.ToApi().Create(clientOptionsParameter, perRetryWithoutAuth)).Terminate())); } else { @@ -940,9 +1002,9 @@ private MethodBodyStatement[] BuildPrimaryConstructorBody(IReadOnlyList().Create(clientOptionsParameter, perRetryPolicies)).Terminate()); + body.Add(PipelineProperty.Assign(This.ToApi().Create(clientOptionsParameter, perRetryPolicies)).Terminate()); + } foreach (var f in Fields) { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientSettingsProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientSettingsProvider.cs index 046d9d0e9c6..06b3e7934c7 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientSettingsProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientSettingsProvider.cs @@ -96,12 +96,13 @@ protected override PropertyProvider[] BuildProperties() this)); } - if (_clientProvider.ClientOptions != null) + var clientOptions = _clientProvider.EffectiveClientOptions; + if (clientOptions != null) { properties.Add(new PropertyProvider( null, MethodSignatureModifiers.Public, - _clientProvider.ClientOptions.Type.WithNullable(true), + clientOptions.Type.WithNullable(true), "Options", new AutoPropertyBody(true), this)); @@ -126,9 +127,10 @@ protected override MethodProvider[] BuildMethods() AppendBindingForProperty(body, sectionParam, propName, param.Name.ToVariableName(), param.Type); } - if (_clientProvider.ClientOptions != null) + var clientOptions = _clientProvider.EffectiveClientOptions; + if (clientOptions != null) { - AppendComplexObjectBinding(body, sectionParam, "Options", "options", _clientProvider.ClientOptions.Type); + AppendComplexObjectBinding(body, sectionParam, "Options", "options", clientOptions.Type); } var bindCoreMethod = new MethodProvider( diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmOutputLibrary.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmOutputLibrary.cs index fc1d9cd87df..13e43ad6a70 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmOutputLibrary.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmOutputLibrary.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using Microsoft.TypeSpec.Generator.ClientModel.Providers; using Microsoft.TypeSpec.Generator.Input; +using Microsoft.TypeSpec.Generator.Primitives; using Microsoft.TypeSpec.Generator.Providers; namespace Microsoft.TypeSpec.Generator.ClientModel @@ -41,11 +42,13 @@ private static void BuildClient(InputClient inputClient, HashSet t if (clientOptions != null) { types.Add(clientOptions); - var clientSettings = client.ClientSettings; - if (clientSettings != null) - { - types.Add(clientSettings); - } + } + + // Emit the Settings class for any publicly constructible client (root or individually-initialized sub-client). + var clientSettings = client.ClientSettings; + if (clientSettings != null && client.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public)) + { + types.Add(clientSettings); } // We use the spec view methods so that we include collection definitions even if the user is customizing or suppressing diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs index d058de7cf6e..c7d924e8747 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs @@ -281,7 +281,7 @@ public async Task CanRenameSubClient() ]); var inputServiceMethod = InputFactory.BasicServiceMethod("test", inputOperation); var inputClient = InputFactory.Client("TestClient", methods: [inputServiceMethod]); - InputClient subClient = InputFactory.Client("custom", parent: inputClient); + InputClient subClient = InputFactory.Client("custom", parent: inputClient, initializedBy: InputClientInitializedBy.Parent); var mockGenerator = await MockHelpers.LoadMockGeneratorAsync( clients: () => [inputClient], compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); @@ -309,7 +309,7 @@ public async Task CanRemoveCachingField() ]); var inputServiceMethod = InputFactory.BasicServiceMethod("test", inputOperation); var inputClient = InputFactory.Client("TestClient", methods: [inputServiceMethod]); - InputClient subClient = InputFactory.Client("dog", methods: [], parameters: [], parent: inputClient); + InputClient subClient = InputFactory.Client("dog", methods: [], parameters: [], parent: inputClient, initializedBy: InputClientInitializedBy.Parent); var mockGenerator = await MockHelpers.LoadMockGeneratorAsync( clients: () => [inputClient], compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderSubClientTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderSubClientTests.cs index 6de8507f420..2642f7e2798 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderSubClientTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderSubClientTests.cs @@ -15,11 +15,11 @@ namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.ClientProvide public class ClientProviderSubClientTests { private static readonly InputClient _testClient = InputFactory.Client("TestClient"); - private static readonly InputClient _animalClient = InputFactory.Client("animal", doc: "AnimalClient description", parent: _testClient); - private static readonly InputClient _dogClient = InputFactory.Client("dog", doc: "DogClient description", parent: _animalClient); - private static readonly InputClient _catClient = InputFactory.Client("cat", doc: "CatClient description", parent: _animalClient); - private static readonly InputClient _hawkClient = InputFactory.Client("hawkClient", doc: "HawkClient description", parent: _animalClient); - private static readonly InputClient _huskyClient = InputFactory.Client("husky", doc: "HuskyClient description", parent: _dogClient); + private static readonly InputClient _animalClient = InputFactory.Client("animal", doc: "AnimalClient description", parent: _testClient, initializedBy: InputClientInitializedBy.Parent); + private static readonly InputClient _dogClient = InputFactory.Client("dog", doc: "DogClient description", parent: _animalClient, initializedBy: InputClientInitializedBy.Parent); + private static readonly InputClient _catClient = InputFactory.Client("cat", doc: "CatClient description", parent: _animalClient, initializedBy: InputClientInitializedBy.Parent); + private static readonly InputClient _hawkClient = InputFactory.Client("hawkClient", doc: "HawkClient description", parent: _animalClient, initializedBy: InputClientInitializedBy.Parent); + private static readonly InputClient _huskyClient = InputFactory.Client("husky", doc: "HuskyClient description", parent: _dogClient, initializedBy: InputClientInitializedBy.Parent); [SetUp] public void SetUp() diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs index f90957cdb1e..566fbb0fb65 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs @@ -48,9 +48,9 @@ public bool ValidateIsLastNamespaceSegmentTheSame(string left, string right) private const string OnlyUnsupportedAuthCategory = "WithOnlyUnsupportedAuth"; private const string TestClientName = "TestClient"; private static readonly InputClient _testClient = InputFactory.Client(TestClientName); - private static readonly InputClient _animalClient = InputFactory.Client("animal", doc: "AnimalClient description", parent: _testClient); - private static readonly InputClient _dogClient = InputFactory.Client("dog", doc: "DogClient description", parent: _animalClient); - private static readonly InputClient _huskyClient = InputFactory.Client("husky", doc: "HuskyClient description", parent: _dogClient); + private static readonly InputClient _animalClient = InputFactory.Client("animal", doc: "AnimalClient description", parent: _testClient, initializedBy: InputClientInitializedBy.Parent); + private static readonly InputClient _dogClient = InputFactory.Client("dog", doc: "DogClient description", parent: _animalClient, initializedBy: InputClientInitializedBy.Parent); + private static readonly InputClient _huskyClient = InputFactory.Client("husky", doc: "HuskyClient description", parent: _dogClient, initializedBy: InputClientInitializedBy.Parent); private static readonly InputModelType _spreadModel = InputFactory.Model( "spreadModel", usage: InputModelTypeUsage.Spread, @@ -755,10 +755,17 @@ public void TestBuildConstructors_DeduplicatesConstructorParametersBySerializedN [Test] public void TestBuildConstructors_ForSubClient_InitializedByIndividually_HasPublicConstructors() { - var parentClient = InputFactory.Client("ParentClient"); + var endpointParameter = InputFactory.EndpointParameter( + KnownParameters.Endpoint.Name, + InputPrimitiveType.String, + defaultValue: InputFactory.Constant.String("https://default.endpoint.io"), + scope: InputParameterScope.Client, + isEndpoint: true); + var parentClient = InputFactory.Client("ParentClient", parameters: [endpointParameter]); var subClient = InputFactory.Client( "SubClient", parent: parentClient, + parameters: [endpointParameter], initializedBy: InputClientInitializedBy.Individually); MockHelpers.LoadMockGenerator( @@ -776,23 +783,28 @@ public void TestBuildConstructors_ForSubClient_InitializedByIndividually_HasPubl c => c.Signature?.Modifiers == MethodSignatureModifiers.Public).ToList(); Assert.IsTrue(publicConstructors.Count > 0, "SubClient with InitializedBy.Individually should have public constructors"); - // primary constructor should set the pipeline, options, and endpoint + // Primary public constructor should have auth credential param and chain to internal via this(...) var primaryConstructor = publicConstructors.FirstOrDefault( - c => c.Signature?.Initializer == null); - Assert.IsNotNull(primaryConstructor, "SubClient with InitializedBy.Individually should have primary public constructor"); - StringAssert.Contains( - "options ??= new global::Sample.ParentClientOptions();", - primaryConstructor!.BodyStatements!.ToDisplayString(), - "Primary constructor should null coalesce options parameter"); - StringAssert.Contains( - "Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.SubClient).Assembly) }, Array.Empty());", - primaryConstructor.BodyStatements!.ToDisplayString(), - "Primary constructor should set the Pipeline property"); - - // Should NOT have internal constructor since InitializedBy does not include Parent - var internalConstructor = constructors.FirstOrDefault( - c => c.Signature?.Modifiers == MethodSignatureModifiers.Internal); - Assert.IsNull(internalConstructor, "SubClient with InitializedBy.Individually (without Parent) should not have internal constructor"); + c => c.Signature?.Parameters.Any(p => p.Name == "options") == true && c.Signature?.Initializer != null); + Assert.IsNotNull(primaryConstructor, "SubClient with InitializedBy.Individually should have primary public constructor with initializer"); + + // Should have a credential parameter (from the mock api key auth) + Assert.IsTrue(primaryConstructor!.Signature.Parameters.Any(p => p.Name == "credential"), + "SubClient with InitializedBy.Individually should have credential parameter in public constructor"); + + // Should have internal AuthenticationPolicy constructor + var internalConstructors = constructors.Where( + c => c.Signature?.Modifiers == MethodSignatureModifiers.Internal).ToList(); + Assert.IsTrue(internalConstructors.Count > 0, "SubClient with InitializedBy.Individually should have internal constructor"); + var authPolicyConstructor = internalConstructors.FirstOrDefault( + c => c.Signature?.Parameters.Any(p => p.Type.Name == nameof(AuthenticationPolicy)) == true); + Assert.IsNotNull(authPolicyConstructor, "SubClient with InitializedBy.Individually should have internal AuthenticationPolicy constructor"); + + // Should have a Settings constructor + Assert.IsNotNull(clientProvider.ClientSettings, "SubClient with InitializedBy.Individually should have ClientSettings"); + var settingsConstructor = publicConstructors.FirstOrDefault( + c => c.Signature?.Parameters.Count == 1 && c.Signature.Parameters[0].Name == "settings"); + Assert.IsNotNull(settingsConstructor, "SubClient with InitializedBy.Individually should have Settings constructor"); var mockingConstructor = constructors.FirstOrDefault( c => c.Signature?.Modifiers == MethodSignatureModifiers.Protected); @@ -835,10 +847,17 @@ public void TestBuildConstructors_ForSubClient_InitializedByParentOnly_HasOnlyIn [Test] public void TestBuildConstructors_ForSubClient_InitializedByBoth_HasBothConstructors() { - var parentClient = InputFactory.Client("ParentClient"); + var endpointParameter = InputFactory.EndpointParameter( + KnownParameters.Endpoint.Name, + InputPrimitiveType.String, + defaultValue: InputFactory.Constant.String("https://default.endpoint.io"), + scope: InputParameterScope.Client, + isEndpoint: true); + var parentClient = InputFactory.Client("ParentClient", parameters: [endpointParameter]); var subClient = InputFactory.Client( "SubClient", parent: parentClient, + parameters: [endpointParameter], initializedBy: InputClientInitializedBy.Individually | InputClientInitializedBy.Parent); MockHelpers.LoadMockGenerator( @@ -856,23 +875,21 @@ public void TestBuildConstructors_ForSubClient_InitializedByBoth_HasBothConstruc c => c.Signature?.Modifiers == MethodSignatureModifiers.Public).ToList(); Assert.IsTrue(publicConstructors.Count > 0, "SubClient with InitializedBy.Individually | Parent should have public constructors"); - // primary constructor should set the pipeline, options, and endpoint + // Primary public constructor should have auth credential param and chain to internal via this(...) var primaryConstructor = publicConstructors.FirstOrDefault( - c => c.Signature?.Initializer == null); - Assert.IsNotNull(primaryConstructor, "SubClient with InitializedBy.Individually should have primary public constructor"); - StringAssert.Contains( - "options ??= new global::Sample.ParentClientOptions();", - primaryConstructor!.BodyStatements!.ToDisplayString(), - "Primary constructor should null coalesce options parameter"); - StringAssert.Contains( - "Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.SubClient).Assembly) }, Array.Empty());", - primaryConstructor.BodyStatements!.ToDisplayString(), - "Primary constructor should set the Pipeline property"); - - // Should also have internal constructor - var internalConstructor = constructors.FirstOrDefault( - c => c.Signature?.Modifiers == MethodSignatureModifiers.Internal); - Assert.IsNotNull(internalConstructor, "SubClient with InitializedBy.Individually | Parent should have internal constructor"); + c => c.Signature?.Parameters.Any(p => p.Name == "options") == true && c.Signature?.Initializer != null); + Assert.IsNotNull(primaryConstructor, "SubClient with InitializedBy.Individually should have primary public constructor with initializer"); + + // Should also have internal constructors (sub-client internal + AuthenticationPolicy internal) + var internalConstructors = constructors.Where( + c => c.Signature?.Modifiers == MethodSignatureModifiers.Internal).ToList(); + Assert.IsTrue(internalConstructors.Count > 0, "SubClient with InitializedBy.Individually | Parent should have internal constructor"); + + // Should have a Settings constructor + Assert.IsNotNull(clientProvider.ClientSettings, "SubClient with InitializedBy.Individually | Parent should have ClientSettings"); + var settingsConstructor = publicConstructors.FirstOrDefault( + c => c.Signature?.Parameters.Count == 1 && c.Signature.Parameters[0].Name == "settings"); + Assert.IsNotNull(settingsConstructor, "SubClient with InitializedBy.Individually | Parent should have Settings constructor"); var mockingConstructor = constructors.FirstOrDefault( c => c.Signature?.Modifiers == MethodSignatureModifiers.Protected); @@ -1219,7 +1236,7 @@ public void TestGetClientOptions(bool isSubClient) parentClient = InputFactory.Client("parent"); } - var client = InputFactory.Client(TestClientName, parent: parentClient); + var client = InputFactory.Client(TestClientName, parent: parentClient, initializedBy: isSubClient ? InputClientInitializedBy.Parent : InputClientInitializedBy.Individually); var clientProvider = new ClientProvider(client); Assert.IsNotNull(clientProvider); @@ -1572,6 +1589,7 @@ public void ApiVersionFieldIsStoredOnRootClient() var subClient = InputFactory.Client( "SubClient", parent: rootClient, + initializedBy: InputClientInitializedBy.Parent, parameters: [ InputFactory.PathParameter("apiVersion", InputPrimitiveType.String, isRequired: true, scope: InputParameterScope.Client, isApiVersion: true), @@ -3327,8 +3345,8 @@ public void MultiServiceClient_GeneratesExpectedClient() scope: InputParameterScope.Client); var client = InputFactory.Client(TestClientName, parameters: [subscriptionIdParameter, apiVersionParameter], isMultiServiceClient: true); - var serviceAClient = InputFactory.Client("ServiceA", clientNamespace: "Sample.ServiceA", parent: client, parameters: [apiVersionParameter, subscriptionIdParameter]); - var serviceBClient = InputFactory.Client("ServiceB", clientNamespace: "Sample.ServiceB", parent: client, parameters: [apiVersionParameter, subscriptionIdParameter]); + var serviceAClient = InputFactory.Client("ServiceA", clientNamespace: "Sample.ServiceA", parent: client, initializedBy: InputClientInitializedBy.Parent, parameters: [apiVersionParameter, subscriptionIdParameter]); + var serviceBClient = InputFactory.Client("ServiceB", clientNamespace: "Sample.ServiceB", parent: client, initializedBy: InputClientInitializedBy.Parent, parameters: [apiVersionParameter, subscriptionIdParameter]); MockHelpers.LoadMockGenerator( apiVersions: () => [.. serviceAVersions, .. serviceBVersions], @@ -3387,9 +3405,9 @@ public void MultiServiceClient_WithThreeServices_GeneratesExpectedClient() scope: InputParameterScope.Client); var client = InputFactory.Client(TestClientName, parameters: [subscriptionIdParameter, apiVersionParameter], isMultiServiceClient: true); - var keyVaultClient = InputFactory.Client("KeyVault", clientNamespace: "Sample.KeyVault", parent: client, parameters: [apiVersionParameter, subscriptionIdParameter]); - var storageClient = InputFactory.Client("Storage", clientNamespace: "Sample.Storage", parent: client, parameters: [apiVersionParameter, subscriptionIdParameter]); - var computeClient = InputFactory.Client("Compute", clientNamespace: "Sample.Compute", parent: client, parameters: [apiVersionParameter, subscriptionIdParameter]); + var keyVaultClient = InputFactory.Client("KeyVault", clientNamespace: "Sample.KeyVault", parent: client, initializedBy: InputClientInitializedBy.Parent, parameters: [apiVersionParameter, subscriptionIdParameter]); + var storageClient = InputFactory.Client("Storage", clientNamespace: "Sample.Storage", parent: client, initializedBy: InputClientInitializedBy.Parent, parameters: [apiVersionParameter, subscriptionIdParameter]); + var computeClient = InputFactory.Client("Compute", clientNamespace: "Sample.Compute", parent: client, initializedBy: InputClientInitializedBy.Parent, parameters: [apiVersionParameter, subscriptionIdParameter]); MockHelpers.LoadMockGenerator( apiVersions: () => [.. keyVaultVersions, .. storageVersions, .. computeVersions], diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/MultiServiceClient_GeneratesExpectedClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/MultiServiceClient_GeneratesExpectedClient.cs index 3e82b811e69..7aa404c1406 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/MultiServiceClient_GeneratesExpectedClient.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/MultiServiceClient_GeneratesExpectedClient.cs @@ -36,7 +36,14 @@ internal TestClient(global::System.ClientModel.Primitives.AuthenticationPolicy a _endpoint = endpoint; _subscriptionId = subscriptionId; - Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); + if ((authenticationPolicy != null)) + { + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); + } + else + { + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); + } _serviceAApiVersion = options.ServiceAApiVersion; _serviceBApiVersion = options.ServiceBApiVersion; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/MultiServiceClient_WithThreeServices_GeneratesExpectedClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/MultiServiceClient_WithThreeServices_GeneratesExpectedClient.cs index 54206fc6ffb..def2e6d7a73 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/MultiServiceClient_WithThreeServices_GeneratesExpectedClient.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/MultiServiceClient_WithThreeServices_GeneratesExpectedClient.cs @@ -39,7 +39,14 @@ internal TestClient(global::System.ClientModel.Primitives.AuthenticationPolicy a _endpoint = endpoint; _subscriptionId = subscriptionId; - Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); + if ((authenticationPolicy != null)) + { + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); + } + else + { + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); + } _serviceComputeApiVersion = options.ServiceComputeApiVersion; _serviceKeyVaultApiVersion = options.ServiceKeyVaultApiVersion; _serviceStorageApiVersion = options.ServiceStorageApiVersion; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/MultiServiceCombinedClient_GeneratesExpectedClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/MultiServiceCombinedClient_GeneratesExpectedClient.cs index 1bd204e71e8..56c1c6cf460 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/MultiServiceCombinedClient_GeneratesExpectedClient.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/MultiServiceCombinedClient_GeneratesExpectedClient.cs @@ -31,7 +31,14 @@ internal TestClient(global::System.ClientModel.Primitives.AuthenticationPolicy a options ??= new global::Sample.TestClientOptions(); _endpoint = endpoint; - Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); + if ((authenticationPolicy != null)) + { + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); + } + else + { + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); + } _serviceAApiVersion = options.ServiceAApiVersion; _serviceBApiVersion = options.ServiceBApiVersion; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/MultiServiceCombinedClient_WithThreeServices_GeneratesExpectedClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/MultiServiceCombinedClient_WithThreeServices_GeneratesExpectedClient.cs index a5520eb71c1..4096b627bc1 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/MultiServiceCombinedClient_WithThreeServices_GeneratesExpectedClient.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/MultiServiceCombinedClient_WithThreeServices_GeneratesExpectedClient.cs @@ -32,7 +32,14 @@ internal TestClient(global::System.ClientModel.Primitives.AuthenticationPolicy a options ??= new global::Sample.TestClientOptions(); _endpoint = endpoint; - Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); + if ((authenticationPolicy != null)) + { + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); + } + else + { + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); + } _serviceComputeApiVersion = options.ServiceComputeApiVersion; _serviceKeyVaultApiVersion = options.ServiceKeyVaultApiVersion; _serviceStorageApiVersion = options.ServiceStorageApiVersion; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,False,False,0).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,False,False,0).cs index bc5bbc28eac..395de44a9d8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,False,False,0).cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,False,False,0).cs @@ -3,4 +3,11 @@ options ??= new global::Sample.TestClientOptions(); _endpoint = endpoint; -Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); +if ((authenticationPolicy != null)) +{ + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); +} +else +{ + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,False,True,0).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,False,True,0).cs index 579690c23a0..395de44a9d8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,False,True,0).cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,False,True,0).cs @@ -3,4 +3,11 @@ options ??= new global::Sample.TestClientOptions(); _endpoint = endpoint; -Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); +if ((authenticationPolicy != null)) +{ + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); +} +else +{ + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,True,False,0).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,True,False,0).cs index 579690c23a0..395de44a9d8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,True,False,0).cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,True,False,0).cs @@ -3,4 +3,11 @@ options ??= new global::Sample.TestClientOptions(); _endpoint = endpoint; -Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); +if ((authenticationPolicy != null)) +{ + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); +} +else +{ + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,True,True,0).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,True,True,0).cs index 579690c23a0..395de44a9d8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,True,True,0).cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,True,True,0).cs @@ -3,4 +3,11 @@ options ??= new global::Sample.TestClientOptions(); _endpoint = endpoint; -Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); +if ((authenticationPolicy != null)) +{ + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); +} +else +{ + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,True,True,1).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,True,True,1).cs index 579690c23a0..395de44a9d8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,True,True,1).cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,True,True,1).cs @@ -3,4 +3,11 @@ options ??= new global::Sample.TestClientOptions(); _endpoint = endpoint; -Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); +if ((authenticationPolicy != null)) +{ + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); +} +else +{ + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithRequired,False,False,0).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithRequired,False,False,0).cs index bc5bbc28eac..395de44a9d8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithRequired,False,False,0).cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithRequired,False,False,0).cs @@ -3,4 +3,11 @@ options ??= new global::Sample.TestClientOptions(); _endpoint = endpoint; -Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); +if ((authenticationPolicy != null)) +{ + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); +} +else +{ + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithRequired,False,True,0).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithRequired,False,True,0).cs index 579690c23a0..395de44a9d8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithRequired,False,True,0).cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithRequired,False,True,0).cs @@ -3,4 +3,11 @@ options ??= new global::Sample.TestClientOptions(); _endpoint = endpoint; -Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); +if ((authenticationPolicy != null)) +{ + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); +} +else +{ + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithRequired,True,False,0).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithRequired,True,False,0).cs index 579690c23a0..395de44a9d8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithRequired,True,False,0).cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithRequired,True,False,0).cs @@ -3,4 +3,11 @@ options ??= new global::Sample.TestClientOptions(); _endpoint = endpoint; -Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); +if ((authenticationPolicy != null)) +{ + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); +} +else +{ + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithRequired,True,True,0).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithRequired,True,True,0).cs index 579690c23a0..395de44a9d8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithRequired,True,True,0).cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithRequired,True,True,0).cs @@ -3,4 +3,11 @@ options ??= new global::Sample.TestClientOptions(); _endpoint = endpoint; -Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); +if ((authenticationPolicy != null)) +{ + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); +} +else +{ + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithRequired,True,True,1).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithRequired,True,True,1).cs index 579690c23a0..395de44a9d8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithRequired,True,True,1).cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithRequired,True,True,1).cs @@ -3,4 +3,11 @@ options ??= new global::Sample.TestClientOptions(); _endpoint = endpoint; -Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); +if ((authenticationPolicy != null)) +{ + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); +} +else +{ + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/ValidateConstructorsWhenUnsupportedAuth.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/ValidateConstructorsWhenUnsupportedAuth.cs index 60e6d68058d..89829df5a76 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/ValidateConstructorsWhenUnsupportedAuth.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/ValidateConstructorsWhenUnsupportedAuth.cs @@ -20,7 +20,14 @@ internal TestClient(global::System.ClientModel.Primitives.AuthenticationPolicy a options ??= new global::Sample.TestClientOptions(); _endpoint = endpoint; - Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); + if ((authenticationPolicy != null)) + { + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); + } + else + { + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); + } } internal TestClient(global::System.Uri endpoint, global::Sample.TestClientOptions options) : this(null, endpoint, options) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/XmlDocsAreWritten.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/XmlDocsAreWritten.cs index 5e455f24f4c..7166e071f8c 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/XmlDocsAreWritten.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/XmlDocsAreWritten.cs @@ -1,4 +1,4 @@ -// +// #nullable disable @@ -44,7 +44,14 @@ internal TestClient(global::System.ClientModel.Primitives.AuthenticationPolicy a _endpoint = endpoint; _queryParam = queryParam; - Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); + if ((authenticationPolicy != null)) + { + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), authenticationPolicy }, Array.Empty()); + } + else + { + Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty()); + } } /// Initializes a new instance of TestClient. diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientSettingsProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientSettingsProviderTests.cs index 9c9fc994075..c4f31a8a1a0 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientSettingsProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientSettingsProviderTests.cs @@ -617,5 +617,178 @@ public void TestNamespace() Assert.IsNotNull(settingsProvider); Assert.AreEqual(clientProvider.Type.Namespace, settingsProvider!.Type.Namespace); } + + // Sub-client settings tests + + [Test] + public void TestSubClient_IndividuallyInitialized_HasSettings() + { + var endpointParam = InputFactory.EndpointParameter( + "endpoint", + InputPrimitiveType.String, + defaultValue: InputFactory.Constant.String("https://default.endpoint.io"), + scope: InputParameterScope.Client, + isEndpoint: true); + var parentClient = InputFactory.Client("ParentClient", parameters: [endpointParam]); + var subClient = InputFactory.Client( + "SubClient", + parent: parentClient, + parameters: [endpointParam], + initializedBy: InputClientInitializedBy.Individually); + + MockHelpers.LoadMockGenerator( + auth: () => new(new InputApiKeyAuth("mock", null), null), + clients: () => [parentClient]); + + var clientProvider = new ClientProvider(subClient); + var settingsProvider = clientProvider.ClientSettings; + + Assert.IsNotNull(settingsProvider, "Individually-initialized sub-client should have ClientSettings"); + Assert.AreEqual("SubClientSettings", settingsProvider!.Name); + } + + [Test] + public void TestSubClient_ParentOnly_NoSettings() + { + var parentClient = InputFactory.Client("ParentClient"); + var subClient = InputFactory.Client( + "SubClient", + parent: parentClient, + initializedBy: InputClientInitializedBy.Parent); + + MockHelpers.LoadMockGenerator( + auth: () => new(new InputApiKeyAuth("mock", null), null), + clients: () => [parentClient]); + + var clientProvider = new ClientProvider(subClient); + + Assert.IsNull(clientProvider.ClientSettings, "Parent-only sub-client should not have ClientSettings"); + } + + [Test] + public void TestSubClient_IndividuallyInitialized_HasEndpointProperty() + { + var endpointParam = InputFactory.EndpointParameter( + "endpoint", + InputPrimitiveType.String, + defaultValue: InputFactory.Constant.String("https://default.endpoint.io"), + scope: InputParameterScope.Client, + isEndpoint: true); + var parentClient = InputFactory.Client("ParentClient", parameters: [endpointParam]); + var subClient = InputFactory.Client( + "SubClient", + parent: parentClient, + parameters: [endpointParam], + initializedBy: InputClientInitializedBy.Individually); + + MockHelpers.LoadMockGenerator( + auth: () => new(new InputApiKeyAuth("mock", null), null), + clients: () => [parentClient]); + + var clientProvider = new ClientProvider(subClient); + var settingsProvider = clientProvider.ClientSettings; + + Assert.IsNotNull(settingsProvider); + var endpointProp = settingsProvider!.Properties.FirstOrDefault( + p => p.Name == "Endpoint" && p.Type.Equals(new CSharpType(typeof(Uri), isNullable: true))); + Assert.IsNotNull(endpointProp, "Sub-client settings should have an Endpoint property"); + } + + [Test] + public void TestSubClient_IndividuallyInitialized_HasOptionsFromRootClient() + { + var endpointParam = InputFactory.EndpointParameter( + "endpoint", + InputPrimitiveType.String, + defaultValue: InputFactory.Constant.String("https://default.endpoint.io"), + scope: InputParameterScope.Client, + isEndpoint: true); + var parentClient = InputFactory.Client("ParentClient", parameters: [endpointParam]); + var subClient = InputFactory.Client( + "SubClient", + parent: parentClient, + parameters: [endpointParam], + initializedBy: InputClientInitializedBy.Individually); + + MockHelpers.LoadMockGenerator( + auth: () => new(new InputApiKeyAuth("mock", null), null), + clients: () => [parentClient]); + + var clientProvider = new ClientProvider(subClient); + var settingsProvider = clientProvider.ClientSettings; + + Assert.IsNotNull(settingsProvider); + var optionsProp = settingsProvider!.Properties.FirstOrDefault(p => p.Name == "Options"); + Assert.IsNotNull(optionsProp, "Sub-client settings should have Options property from root client"); + + // The Options type should be the parent's ClientOptions type + var parentProvider = new ClientProvider(parentClient); + Assert.IsNotNull(parentProvider.ClientOptions); + Assert.AreEqual( + parentProvider.ClientOptions!.Type.WithNullable(true), + optionsProp!.Type, + "Sub-client settings Options type should match root client's ClientOptions type"); + } + + [Test] + public void TestSubClient_IndividuallyInitialized_BindCoreHasEndpointAndOptions() + { + var endpointParam = InputFactory.EndpointParameter( + "endpoint", + InputPrimitiveType.String, + defaultValue: InputFactory.Constant.String("https://default.endpoint.io"), + scope: InputParameterScope.Client, + isEndpoint: true); + var parentClient = InputFactory.Client("ParentClient", parameters: [endpointParam]); + var subClient = InputFactory.Client( + "SubClient", + parent: parentClient, + parameters: [endpointParam], + initializedBy: InputClientInitializedBy.Individually); + + MockHelpers.LoadMockGenerator( + auth: () => new(new InputApiKeyAuth("mock", null), null), + clients: () => [parentClient]); + + var clientProvider = new ClientProvider(subClient); + var settingsProvider = clientProvider.ClientSettings; + + Assert.IsNotNull(settingsProvider); + var bindCoreMethod = settingsProvider!.Methods.FirstOrDefault(m => m.Signature.Name == "BindCore"); + Assert.IsNotNull(bindCoreMethod, "Sub-client settings should have BindCore method"); + + var bodyString = bindCoreMethod!.BodyStatements!.ToDisplayString(); + Assert.IsTrue(bodyString.Contains("TryCreate"), "BindCore should bind the Endpoint via Uri.TryCreate"); + Assert.IsTrue(bodyString.Contains("GetSection") && bodyString.Contains("Options"), + "BindCore should bind the Options section"); + } + + [Test] + public void TestSubClient_IndividuallyInitialized_SettingsBaseType() + { + var endpointParam = InputFactory.EndpointParameter( + "endpoint", + InputPrimitiveType.String, + defaultValue: InputFactory.Constant.String("https://default.endpoint.io"), + scope: InputParameterScope.Client, + isEndpoint: true); + var parentClient = InputFactory.Client("ParentClient", parameters: [endpointParam]); + var subClient = InputFactory.Client( + "SubClient", + parent: parentClient, + parameters: [endpointParam], + initializedBy: InputClientInitializedBy.Individually); + + MockHelpers.LoadMockGenerator( + auth: () => new(new InputApiKeyAuth("mock", null), null), + clients: () => [parentClient]); + + var clientProvider = new ClientProvider(subClient); + var settingsProvider = clientProvider.ClientSettings; + + Assert.IsNotNull(settingsProvider); + Assert.AreEqual(ClientSettingsProvider.ClientSettingsType, settingsProvider!.Type.BaseType, + "Sub-client settings should inherit from ClientSettings"); + } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/common/InputFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/common/InputFactory.cs index 85632b4ee25..f23286ac277 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/common/InputFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/common/InputFactory.cs @@ -709,7 +709,7 @@ public static InputServiceMethodResponse ServiceMethodResponse(InputType? type, private static readonly Dictionary> _childClientsCache = new(); - public static InputClient Client(string name, string clientNamespace = "Sample", string? doc = null, IEnumerable? methods = null, IEnumerable? parameters = null, InputClient? parent = null, string? crossLanguageDefinitionId = null, IEnumerable? apiVersions = null, InputClientInitializedBy initializedBy = InputClientInitializedBy.Default, bool? isMultiServiceClient = false) + public static InputClient Client(string name, string clientNamespace = "Sample", string? doc = null, IEnumerable? methods = null, IEnumerable? parameters = null, InputClient? parent = null, string? crossLanguageDefinitionId = null, IEnumerable? apiVersions = null, InputClientInitializedBy initializedBy = InputClientInitializedBy.Individually, bool? isMultiServiceClient = false) { // when this client has parent, we add the constructed client into the `children` list of the parent var clientChildren = new List(); diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Metrics.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Metrics.cs index 8e55488af08..dab61b07de2 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Metrics.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Metrics.cs @@ -8,6 +8,8 @@ using System; using System.ClientModel; using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; @@ -17,6 +19,17 @@ namespace SampleTypeSpec public partial class Metrics { private readonly Uri _endpoint; + private const string AuthorizationHeader = "my-api-key"; + /// The OAuth2 flows supported by the service. + private static readonly Dictionary[] _flows = new Dictionary[] + { + new Dictionary + { + { GetTokenOptions.ScopesPropertyName, new string[] { "read" } }, + { GetTokenOptions.AuthorizationUrlPropertyName, "https://api.example.com/oauth2/authorize" }, + { GetTokenOptions.RefreshUrlPropertyName, "https://api.example.com/oauth2/refresh" } + } + }; private readonly string _metricsNamespace; /// Initializes a new instance of Metrics for mocking. @@ -38,19 +51,29 @@ internal Metrics(ClientPipeline pipeline, Uri endpoint, string metricsNamespace) /// Initializes a new instance of Metrics. /// Service endpoint. /// - /// or is null. + /// A credential used to authenticate to the service. + /// , or is null. /// is an empty string, and was expected to be non-empty. - public Metrics(Uri endpoint, string metricsNamespace) : this(endpoint, metricsNamespace, new SampleTypeSpecClientOptions()) + public Metrics(Uri endpoint, string metricsNamespace, ApiKeyCredential credential) : this(endpoint, metricsNamespace, credential, new SampleTypeSpecClientOptions()) { } /// Initializes a new instance of Metrics. /// Service endpoint. /// - /// The options for configuring the client. - /// or is null. + /// A credential provider used to authenticate to the service. + /// , or is null. /// is an empty string, and was expected to be non-empty. - public Metrics(Uri endpoint, string metricsNamespace, SampleTypeSpecClientOptions options) + public Metrics(Uri endpoint, string metricsNamespace, AuthenticationTokenProvider tokenProvider) : this(endpoint, metricsNamespace, tokenProvider, new SampleTypeSpecClientOptions()) + { + } + + /// Initializes a new instance of Metrics. + /// The authentication policy to use for pipeline creation. + /// Service endpoint. + /// + /// The options for configuring the client. + internal Metrics(AuthenticationPolicy authenticationPolicy, Uri endpoint, string metricsNamespace, SampleTypeSpecClientOptions options) { Argument.AssertNotNull(endpoint, nameof(endpoint)); Argument.AssertNotNullOrEmpty(metricsNamespace, nameof(metricsNamespace)); @@ -59,7 +82,43 @@ public Metrics(Uri endpoint, string metricsNamespace, SampleTypeSpecClientOption _endpoint = endpoint; _metricsNamespace = metricsNamespace; - Pipeline = ClientPipeline.Create(options, Array.Empty(), new PipelinePolicy[] { new UserAgentPolicy(typeof(Metrics).Assembly) }, Array.Empty()); + if (authenticationPolicy != null) + { + Pipeline = ClientPipeline.Create(options, Array.Empty(), new PipelinePolicy[] { new UserAgentPolicy(typeof(Metrics).Assembly), authenticationPolicy }, Array.Empty()); + } + else + { + Pipeline = ClientPipeline.Create(options, Array.Empty(), new PipelinePolicy[] { new UserAgentPolicy(typeof(Metrics).Assembly) }, Array.Empty()); + } + } + + /// Initializes a new instance of Metrics. + /// Service endpoint. + /// + /// A credential used to authenticate to the service. + /// The options for configuring the client. + /// , or is null. + /// is an empty string, and was expected to be non-empty. + public Metrics(Uri endpoint, string metricsNamespace, ApiKeyCredential credential, SampleTypeSpecClientOptions options) : this(ApiKeyAuthenticationPolicy.CreateHeaderApiKeyPolicy(credential, AuthorizationHeader), endpoint, metricsNamespace, options) + { + } + + /// Initializes a new instance of Metrics. + /// Service endpoint. + /// + /// A credential provider used to authenticate to the service. + /// The options for configuring the client. + /// , or is null. + /// is an empty string, and was expected to be non-empty. + public Metrics(Uri endpoint, string metricsNamespace, AuthenticationTokenProvider tokenProvider, SampleTypeSpecClientOptions options) : this(new BearerTokenPolicy(tokenProvider, _flows), endpoint, metricsNamespace, options) + { + } + + /// Initializes a new instance of Metrics from a . + /// The settings for Metrics. + [Experimental("SCME0002")] + public Metrics(MetricsSettings settings) : this(AuthenticationPolicy.Create(settings), settings?.SampleTypeSpecUrl, settings?.MetricsNamespace, settings?.Options) + { } /// The HTTP pipeline for sending and receiving REST requests and responses. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/MetricsSettings.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/MetricsSettings.cs new file mode 100644 index 00000000000..ef92878a0d4 --- /dev/null +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/MetricsSettings.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; +using System.ClientModel.Primitives; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Configuration; + +namespace SampleTypeSpec +{ + /// Represents the settings used to configure a that can be loaded from an . + [Experimental("SCME0002")] + public partial class MetricsSettings : ClientSettings + { + /// Gets or sets the SampleTypeSpecUrl. + public Uri SampleTypeSpecUrl { get; set; } + + /// Gets or sets the MetricsNamespace. + public string MetricsNamespace { get; set; } + + /// Gets or sets the Options. + public SampleTypeSpecClientOptions Options { get; set; } + + /// Binds configuration values from the given section. + /// The configuration section. + protected override void BindCore(IConfigurationSection section) + { + if (Uri.TryCreate(section["SampleTypeSpecUrl"], UriKind.Absolute, out Uri sampleTypeSpecUrl)) + { + SampleTypeSpecUrl = sampleTypeSpecUrl; + } + string metricsNamespace = section["MetricsNamespace"]; + if (!string.IsNullOrEmpty(metricsNamespace)) + { + MetricsNamespace = metricsNamespace; + } + IConfigurationSection optionsSection = section.GetSection("Options"); + if (optionsSection.Exists()) + { + Options = new SampleTypeSpecClientOptions(optionsSection); + } + } + } +} diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.cs index 78cb15103f5..a97f4d223f0 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.cs @@ -62,7 +62,14 @@ internal SampleTypeSpecClient(AuthenticationPolicy authenticationPolicy, Uri end options ??= new SampleTypeSpecClientOptions(); _endpoint = endpoint; - Pipeline = ClientPipeline.Create(options, Array.Empty(), new PipelinePolicy[] { new UserAgentPolicy(typeof(SampleTypeSpecClient).Assembly), authenticationPolicy }, Array.Empty()); + if (authenticationPolicy != null) + { + Pipeline = ClientPipeline.Create(options, Array.Empty(), new PipelinePolicy[] { new UserAgentPolicy(typeof(SampleTypeSpecClient).Assembly), authenticationPolicy }, Array.Empty()); + } + else + { + Pipeline = ClientPipeline.Create(options, Array.Empty(), new PipelinePolicy[] { new UserAgentPolicy(typeof(SampleTypeSpecClient).Assembly) }, Array.Empty()); + } _apiVersion = options.Version; } diff --git a/packages/openapi3/src/visibility-usage.ts b/packages/openapi3/src/visibility-usage.ts index b01ed33dedb..2e30d48499b 100644 --- a/packages/openapi3/src/visibility-usage.ts +++ b/packages/openapi3/src/visibility-usage.ts @@ -152,6 +152,16 @@ function addUsagesInOperation( navigateReferencedTypes(httpOperation.parameters.body.type, visibility, (type, vis) => trackUsage(metadataInfo, usages, type, vis), ); + // For multipart bodies, also navigate part types directly. HttpPart wrappers are + // empty models with no properties, so navigateReferencedTypes won't reach T through + // normal property traversal, causing T to be incorrectly treated as unreachable. + if (httpOperation.parameters.body.bodyKind === "multipart") { + for (const part of httpOperation.parameters.body.parts) { + navigateReferencedTypes(part.body.type, visibility, (type, vis) => + trackUsage(metadataInfo, usages, type, vis), + ); + } + } } for (const param of httpOperation.parameters.parameters) { navigateReferencedTypes(param.param, visibility, (type, vis) => diff --git a/packages/openapi3/test/multipart.test.ts b/packages/openapi3/test/multipart.test.ts index 1858f48e0f7..b5a53caa25d 100644 --- a/packages/openapi3/test/multipart.test.ts +++ b/packages/openapi3/test/multipart.test.ts @@ -91,6 +91,33 @@ worksFor(supportedVersions, ({ openApiFor }) => { description: "My doc", }); }); + + it("named union with bytes variant does not cause 'Duplicate type name' error", async () => { + const res = await openApiFor( + ` + union BinaryOrJson { + bytes, + { file_id: string }, + } + op upload(@header contentType: "multipart/form-data", @multipartBody body: { attachment: HttpPart }): void; + `, + ); + const op = res.paths["/"].post; + // The union should be referenced as a $ref (not inlined), and should be available in components + expect(op.requestBody.content["multipart/form-data"].schema.properties.attachment.$ref).toEqual( + "#/components/schemas/BinaryOrJson", + ); + const schema = res.components.schemas.BinaryOrJson; + expect(schema).toBeDefined(); + // The schema should use anyOf with 2 variants + expect(schema.anyOf).toHaveLength(2); + // The object variant ({file_id: string}) should appear in the schema regardless of version + expect(schema.anyOf).toContainEqual({ + type: "object", + properties: { file_id: { type: "string" } }, + required: ["file_id"], + }); + }); }); worksFor(["3.0.0"], ({ openApiFor }) => {