diff --git a/docs/samples/client/csharp/SampleService/SampleClient/src/Generated/Metrics.RestClient.cs b/docs/samples/client/csharp/SampleService/SampleClient/src/Generated/Metrics.RestClient.cs index 31dd56da2b8..371d027f6b7 100644 --- a/docs/samples/client/csharp/SampleService/SampleClient/src/Generated/Metrics.RestClient.cs +++ b/docs/samples/client/csharp/SampleService/SampleClient/src/Generated/Metrics.RestClient.cs @@ -17,7 +17,9 @@ internal PipelineMessage CreateGetWidgetMetricsRequest(string day, RequestOption { ClientUriBuilder uri = new ClientUriBuilder(); uri.Reset(_endpoint); - uri.AppendPath("/metrics/widgets/daysOfWeek/", false); + uri.AppendPath("/metrics/", false); + uri.AppendPath(_metricsNamespace, true); + uri.AppendPath("/widgets/daysOfWeek/", false); uri.AppendPath(day, true); PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier200); PipelineRequest request = message.Request; diff --git a/docs/samples/client/csharp/SampleService/SampleClient/src/Generated/Metrics.cs b/docs/samples/client/csharp/SampleService/SampleClient/src/Generated/Metrics.cs index 30284cdba24..65e1ea56883 100644 --- a/docs/samples/client/csharp/SampleService/SampleClient/src/Generated/Metrics.cs +++ b/docs/samples/client/csharp/SampleService/SampleClient/src/Generated/Metrics.cs @@ -14,6 +14,7 @@ namespace SampleTypeSpec public partial class Metrics { private readonly Uri _endpoint; + private readonly string _metricsNamespace; /// Initializes a new instance of Metrics for mocking. protected Metrics() @@ -23,30 +24,38 @@ protected Metrics() /// Initializes a new instance of Metrics. /// The HTTP pipeline for sending and receiving REST requests and responses. /// Service endpoint. - internal Metrics(ClientPipeline pipeline, Uri endpoint) + /// + internal Metrics(ClientPipeline pipeline, Uri endpoint, string metricsNamespace) { _endpoint = endpoint; Pipeline = pipeline; + _metricsNamespace = metricsNamespace; } /// Initializes a new instance of Metrics. /// Service endpoint. - /// is null. - public Metrics(Uri endpoint) : this(endpoint, new SampleTypeSpecClientOptions()) + /// + /// 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()) { } /// Initializes a new instance of Metrics. /// Service endpoint. + /// /// The options for configuring the client. - /// is null. - public Metrics(Uri endpoint, SampleTypeSpecClientOptions options) + /// or is null. + /// is an empty string, and was expected to be non-empty. + public Metrics(Uri endpoint, string metricsNamespace, SampleTypeSpecClientOptions options) { Argument.AssertNotNull(endpoint, nameof(endpoint)); + Argument.AssertNotNullOrEmpty(metricsNamespace, nameof(metricsNamespace)); options ??= new SampleTypeSpecClientOptions(); _endpoint = endpoint; + _metricsNamespace = metricsNamespace; Pipeline = ClientPipeline.Create(options, Array.Empty(), new PipelinePolicy[] { new UserAgentPolicy(typeof(Metrics).Assembly) }, Array.Empty()); } diff --git a/docs/samples/client/csharp/SampleService/SampleClient/src/Generated/SampleTypeSpecClient.cs b/docs/samples/client/csharp/SampleService/SampleClient/src/Generated/SampleTypeSpecClient.cs index 016ab1d0d2e..291d40887ea 100644 --- a/docs/samples/client/csharp/SampleService/SampleClient/src/Generated/SampleTypeSpecClient.cs +++ b/docs/samples/client/csharp/SampleService/SampleClient/src/Generated/SampleTypeSpecClient.cs @@ -32,6 +32,7 @@ public partial class SampleTypeSpecClient } }; private readonly string _apiVersion; + private readonly string _metricsNamespace; private AnimalOperations _cachedAnimalOperations; private PetOperations _cachedPetOperations; private DogOperations _cachedDogOperations; @@ -45,33 +46,41 @@ protected SampleTypeSpecClient() /// Initializes a new instance of SampleTypeSpecClient. /// Service endpoint. + /// /// A credential used to authenticate to the service. - /// or is null. - public SampleTypeSpecClient(Uri endpoint, ApiKeyCredential credential) : this(endpoint, credential, new SampleTypeSpecClientOptions()) + /// , or is null. + /// is an empty string, and was expected to be non-empty. + public SampleTypeSpecClient(Uri endpoint, string metricsNamespace, ApiKeyCredential credential) : this(endpoint, metricsNamespace, credential, new SampleTypeSpecClientOptions()) { } /// Initializes a new instance of SampleTypeSpecClient. /// Service endpoint. + /// /// A credential provider used to authenticate to the service. - /// or is null. - public SampleTypeSpecClient(Uri endpoint, AuthenticationTokenProvider tokenProvider) : this(endpoint, tokenProvider, new SampleTypeSpecClientOptions()) + /// , or is null. + /// is an empty string, and was expected to be non-empty. + public SampleTypeSpecClient(Uri endpoint, string metricsNamespace, AuthenticationTokenProvider tokenProvider) : this(endpoint, metricsNamespace, tokenProvider, new SampleTypeSpecClientOptions()) { } /// Initializes a new instance of SampleTypeSpecClient. /// Service endpoint. + /// /// A credential used to authenticate to the service. /// The options for configuring the client. - /// or is null. - public SampleTypeSpecClient(Uri endpoint, ApiKeyCredential credential, SampleTypeSpecClientOptions options) + /// , or is null. + /// is an empty string, and was expected to be non-empty. + public SampleTypeSpecClient(Uri endpoint, string metricsNamespace, ApiKeyCredential credential, SampleTypeSpecClientOptions options) { Argument.AssertNotNull(endpoint, nameof(endpoint)); + Argument.AssertNotNullOrEmpty(metricsNamespace, nameof(metricsNamespace)); Argument.AssertNotNull(credential, nameof(credential)); options ??= new SampleTypeSpecClientOptions(); _endpoint = endpoint; + _metricsNamespace = metricsNamespace; _keyCredential = credential; Pipeline = ClientPipeline.Create(options, Array.Empty(), new PipelinePolicy[] { new UserAgentPolicy(typeof(SampleTypeSpecClient).Assembly), ApiKeyAuthenticationPolicy.CreateHeaderApiKeyPolicy(_keyCredential, AuthorizationHeader) }, Array.Empty()); _apiVersion = options.Version; @@ -79,17 +88,21 @@ public SampleTypeSpecClient(Uri endpoint, ApiKeyCredential credential, SampleTyp /// Initializes a new instance of SampleTypeSpecClient. /// Service endpoint. + /// /// A credential provider used to authenticate to the service. /// The options for configuring the client. - /// or is null. - public SampleTypeSpecClient(Uri endpoint, AuthenticationTokenProvider tokenProvider, SampleTypeSpecClientOptions options) + /// , or is null. + /// is an empty string, and was expected to be non-empty. + public SampleTypeSpecClient(Uri endpoint, string metricsNamespace, AuthenticationTokenProvider tokenProvider, SampleTypeSpecClientOptions options) { Argument.AssertNotNull(endpoint, nameof(endpoint)); + Argument.AssertNotNullOrEmpty(metricsNamespace, nameof(metricsNamespace)); Argument.AssertNotNull(tokenProvider, nameof(tokenProvider)); options ??= new SampleTypeSpecClientOptions(); _endpoint = endpoint; + _metricsNamespace = metricsNamespace; _tokenProvider = tokenProvider; Pipeline = ClientPipeline.Create(options, Array.Empty(), new PipelinePolicy[] { new UserAgentPolicy(typeof(SampleTypeSpecClient).Assembly), new BearerTokenPolicy(_tokenProvider, _flows) }, Array.Empty()); _apiVersion = options.Version; @@ -3317,7 +3330,7 @@ public virtual PlantOperations GetPlantOperationsClient() /// Initializes a new instance of Metrics. public virtual Metrics GetMetricsClient() { - return Volatile.Read(ref _cachedMetrics) ?? Interlocked.CompareExchange(ref _cachedMetrics, new Metrics(Pipeline, _endpoint), null) ?? _cachedMetrics; + return Volatile.Read(ref _cachedMetrics) ?? Interlocked.CompareExchange(ref _cachedMetrics, new Metrics(Pipeline, _endpoint, _metricsNamespace), null) ?? _cachedMetrics; } } } diff --git a/docs/samples/client/csharp/SampleService/main.tsp b/docs/samples/client/csharp/SampleService/main.tsp index 2722adc9c6f..d63bf43a53a 100644 --- a/docs/samples/client/csharp/SampleService/main.tsp +++ b/docs/samples/client/csharp/SampleService/main.tsp @@ -833,14 +833,19 @@ interface PlantOperations { }; } +model MetricsClientParams { + metricsNamespace: string; +} + @clientInitialization({ initializedBy: InitializedBy.individually | InitializedBy.parent, + parameters: MetricsClientParams, }) interface Metrics { @doc("Get Widget metrics for given day of week") @get - @route("/metrics/widgets/daysOfWeek") - getWidgetMetrics(@path day: DaysOfWeekExtensibleEnum): { + @route("/metrics/{metricsNamespace}/widgets/daysOfWeek") + getWidgetMetrics(@path metricsNamespace: string, @path day: DaysOfWeekExtensibleEnum): { numSold: int32; averagePrice: float32; }; 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 606a888af4c..c58ae32a555 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 @@ -309,6 +309,22 @@ private IReadOnlyList GetSubClientInternalConstructorParamete return subClientParameters; } + /// + /// Determines whether this subclient has non-infrastructure parameters + /// (not API versions, not endpoint) that are not present on the parent's InputClient.Parameters. + /// Uses the raw to avoid circular lazy-initialization dependencies. + /// + internal bool HasAccessorOnlyParameters(InputClient parentInputClient) + { + var parentParamNames = parentInputClient.Parameters + .Select(p => p.Name) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + + return _inputClient.Parameters + .Where(p => !p.IsApiVersion && !(p is InputEndpointParameter ep && ep.IsEndpoint)) + .Any(p => !parentParamNames.Contains(p.Name)); + } + private Lazy> _clientParameters; internal IReadOnlyList ClientParameters => _clientParameters.Value; private IReadOnlyList GetClientParameters() @@ -397,7 +413,10 @@ protected override FieldProvider[] BuildFields() // add sub-client caching fields foreach (var subClient in _subClients.Value) { - if (subClient._clientCachingField != null) + // Only add caching field when the accessor does not require additional parameters. + // If the subclient has parameters that are not on the parent, each accessor call may + // produce a different client instance, so caching is not appropriate. + if (subClient._clientCachingField != null && !subClient.HasAccessorOnlyParameters(_inputClient)) { fields.Add(subClient._clientCachingField); } @@ -899,8 +918,19 @@ protected override ScmMethodProvider[] BuildMethods() var cachedClientFieldVar = new VariableExpression(subClient.Type, subClient._clientCachingField.Declaration, IsRef: true); List subClientConstructorArgs = new(3); - - // Populate constructor arguments + List accessorMethodParams = []; + + // Identify subclient-specific parameters by comparing with the parent's input parameters. + // Parameters present on both parent and subclient are shared (sourced from parent fields/properties). + var parentInputParamNames = _inputClient.Parameters + .Select(p => p.Name) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + var subClientSpecificParamNames = subClient._inputClient.Parameters + .Where(p => !parentInputParamNames.Contains(p.Name)) + .Select(p => p.Name) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + + // Populate constructor arguments, collecting subclient-specific params for the accessor method signature foreach (var param in subClient._subClientInternalConstructorParams.Value) { if (parentClientProperties.TryGetValue(param.Name, out var parentProperty)) @@ -920,30 +950,57 @@ protected override ScmMethodProvider[] BuildMethods() subClientConstructorArgs.Add(correspondingApiVersionField.Field); } } + else if (subClientSpecificParamNames.Contains(param.Name)) + { + // This parameter is subclient-specific — expose it as an accessor method parameter. + accessorMethodParams.Add(param); + subClientConstructorArgs.Add(param); + } } - // Create the interlocked compare exchange expression for the body - var interlockedCompareExchange = Static(typeof(Interlocked)).Invoke( - nameof(Interlocked.CompareExchange), - [cachedClientFieldVar, New.Instance(subClient.Type, subClientConstructorArgs), Null]); var factoryMethodName = subClient.Name.EndsWith(ClientSuffix, StringComparison.OrdinalIgnoreCase) ? $"Get{subClient.Name}" : $"Get{subClient.Name}{ClientSuffix}"; - var factoryMethod = new ScmMethodProvider( - new( - factoryMethodName, - $"Initializes a new instance of {subClient.Type.Name}", - MethodSignatureModifiers.Public | MethodSignatureModifiers.Virtual, - subClient.Type, - null, - []), - // return Volatile.Read(ref _cachedClient) ?? Interlocked.CompareExchange(ref _cachedClient, new Client(_pipeline, _keyCredential, _endpoint), null) ?? _cachedClient; - Return( - Static(typeof(Volatile)).Invoke(nameof(Volatile.Read), cachedClientFieldVar) - .NullCoalesce(interlockedCompareExchange.NullCoalesce(subClient._clientCachingField))), - this, - ScmMethodKind.Convenience); + ScmMethodProvider factoryMethod; + if (accessorMethodParams.Count > 0) + { + // When the accessor requires extra parameters, caching is not appropriate + // (different parameter values may produce different client instances). + // Return a new instance directly. + factoryMethod = new ScmMethodProvider( + new( + factoryMethodName, + $"Initializes a new instance of {subClient.Type.Name}", + MethodSignatureModifiers.Public | MethodSignatureModifiers.Virtual, + subClient.Type, + null, + [.. accessorMethodParams]), + Return(New.Instance(subClient.Type, subClientConstructorArgs)), + this, + ScmMethodKind.Convenience); + } + else + { + // No extra params - use the existing caching pattern + var interlockedCompareExchange = Static(typeof(Interlocked)).Invoke( + nameof(Interlocked.CompareExchange), + [cachedClientFieldVar, New.Instance(subClient.Type, subClientConstructorArgs), Null]); + factoryMethod = new ScmMethodProvider( + new( + factoryMethodName, + $"Initializes a new instance of {subClient.Type.Name}", + MethodSignatureModifiers.Public | MethodSignatureModifiers.Virtual, + subClient.Type, + null, + []), + // return Volatile.Read(ref _cachedClient) ?? Interlocked.CompareExchange(ref _cachedClient, new Client(_pipeline, _keyCredential, _endpoint), null) ?? _cachedClient; + Return( + Static(typeof(Volatile)).Invoke(nameof(Volatile.Read), cachedClientFieldVar) + .NullCoalesce(interlockedCompareExchange.NullCoalesce(subClient._clientCachingField))), + this, + ScmMethodKind.Convenience); + } methods.Add(factoryMethod); } 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 9d1a8bbacdf..48554a37f83 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 @@ -899,6 +899,116 @@ public void TestBuildMethods_ForParent_InitializedByBoth_HasSubClientAccessor() Assert.IsNotNull(cachingField, "Parent should have caching field for subclient with InitializedBy.Individually | Parent"); } + [Test] + public void TestBuildMethods_ForParent_InitializedByBoth_WithSubClientParams_HasParameterizedAccessor() + { + var parentClient = InputFactory.Client("ParentClient"); + var subClientParam = InputFactory.PathParameter("resourceId", InputPrimitiveType.String, scope: InputParameterScope.Client); + var subClient = InputFactory.Client( + "SubClient", + parent: parentClient, + parameters: [subClientParam], + initializedBy: InputClientInitializedBy.Individually | InputClientInitializedBy.Parent); + + MockHelpers.LoadMockGenerator( + clients: () => [parentClient]); + + var parentProvider = new ClientProvider(parentClient); + + Assert.IsNotNull(parentProvider); + + // The parent should have a factory method for the subclient + var factoryMethod = parentProvider.Methods.FirstOrDefault( + m => m.Signature?.Name == "GetSubClient" || m.Signature?.Name == "GetSubClientClient"); + Assert.IsNotNull(factoryMethod, "Parent should have factory method for subclient with parameters"); + + // The accessor method should include the subclient's extra parameters + Assert.IsNotNull(factoryMethod!.Signature, "Factory method should have a signature"); + Assert.AreEqual(1, factoryMethod.Signature!.Parameters.Count, + "Accessor method should include subclient parameters not present on parent"); + Assert.AreEqual("resourceId", factoryMethod.Signature.Parameters[0].Name, + "Accessor method parameter should be the subclient's extra parameter"); + } + + [Test] + public void TestBuildFields_ForParent_InitializedByBoth_WithSubClientParams_NoCachingField() + { + var parentClient = InputFactory.Client("ParentClient"); + var subClientParam = InputFactory.PathParameter("resourceId", InputPrimitiveType.String, scope: InputParameterScope.Client); + var subClient = InputFactory.Client( + "SubClient", + parent: parentClient, + parameters: [subClientParam], + initializedBy: InputClientInitializedBy.Individually | InputClientInitializedBy.Parent); + + MockHelpers.LoadMockGenerator( + clients: () => [parentClient]); + + var parentProvider = new ClientProvider(parentClient); + + Assert.IsNotNull(parentProvider); + + // The parent should NOT have a caching field for the subclient when the accessor requires parameters, + // since caching is not appropriate when different parameter values produce different client instances. + var cachingField = parentProvider.Fields.FirstOrDefault(f => f.Name == "_cachedSubClient"); + Assert.IsNull(cachingField, "Parent should not have caching field for subclient that has subclient-specific parameters in its accessor"); + } + + [Test] + public void TestBuildMethods_ForParent_InitializedByParentOnly_WithSubClientParams_HasParameterizedAccessor() + { + var parentClient = InputFactory.Client("ParentClient"); + var subClientParam = InputFactory.PathParameter("resourceId", InputPrimitiveType.String, scope: InputParameterScope.Client); + var subClient = InputFactory.Client( + "SubClient", + parent: parentClient, + parameters: [subClientParam], + initializedBy: InputClientInitializedBy.Parent); + + MockHelpers.LoadMockGenerator( + clients: () => [parentClient]); + + var parentProvider = new ClientProvider(parentClient); + + Assert.IsNotNull(parentProvider); + + // The parent should have a factory method for the subclient + var factoryMethod = parentProvider.Methods.FirstOrDefault( + m => m.Signature?.Name == "GetSubClient" || m.Signature?.Name == "GetSubClientClient"); + Assert.IsNotNull(factoryMethod, "Parent should have factory method for subclient with parameters"); + + // The accessor method should include the subclient's extra parameters + Assert.IsNotNull(factoryMethod!.Signature, "Factory method should have a signature"); + Assert.AreEqual(1, factoryMethod.Signature!.Parameters.Count, + "Accessor method should include subclient parameters not present on parent"); + Assert.AreEqual("resourceId", factoryMethod.Signature.Parameters[0].Name, + "Accessor method parameter should be the subclient's extra parameter"); + } + + [Test] + public void TestBuildFields_ForParent_InitializedByParentOnly_WithSubClientParams_NoCachingField() + { + var parentClient = InputFactory.Client("ParentClient"); + var subClientParam = InputFactory.PathParameter("resourceId", InputPrimitiveType.String, scope: InputParameterScope.Client); + var subClient = InputFactory.Client( + "SubClient", + parent: parentClient, + parameters: [subClientParam], + initializedBy: InputClientInitializedBy.Parent); + + MockHelpers.LoadMockGenerator( + clients: () => [parentClient]); + + var parentProvider = new ClientProvider(parentClient); + + Assert.IsNotNull(parentProvider); + + // The parent should NOT have a caching field for the subclient when the accessor requires parameters, + // since caching is not appropriate when different parameter values produce different client instances. + var cachingField = parentProvider.Fields.FirstOrDefault(f => f.Name == "_cachedSubClient"); + Assert.IsNull(cachingField, "Parent should not have caching field for subclient that has subclient-specific parameters in its accessor"); + } + private void ValidatePrimaryConstructor( ConstructorProvider primaryPublicConstructor, List inputParameters, diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Metrics.RestClient.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Metrics.RestClient.cs index a724f1907ef..697b9766d36 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Metrics.RestClient.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Metrics.RestClient.cs @@ -20,7 +20,9 @@ internal PipelineMessage CreateGetWidgetMetricsRequest(string day, RequestOption { ClientUriBuilder uri = new ClientUriBuilder(); uri.Reset(_endpoint); - uri.AppendPath("/metrics/widgets/daysOfWeek/", false); + uri.AppendPath("/metrics/", false); + uri.AppendPath(_metricsNamespace, true); + uri.AppendPath("/widgets/daysOfWeek/", false); uri.AppendPath(day, true); PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier200); PipelineRequest request = message.Request; 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 8f9c0ffffb6..8e55488af08 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 @@ -17,6 +17,7 @@ namespace SampleTypeSpec public partial class Metrics { private readonly Uri _endpoint; + private readonly string _metricsNamespace; /// Initializes a new instance of Metrics for mocking. protected Metrics() @@ -26,30 +27,38 @@ protected Metrics() /// Initializes a new instance of Metrics. /// The HTTP pipeline for sending and receiving REST requests and responses. /// Service endpoint. - internal Metrics(ClientPipeline pipeline, Uri endpoint) + /// + internal Metrics(ClientPipeline pipeline, Uri endpoint, string metricsNamespace) { _endpoint = endpoint; Pipeline = pipeline; + _metricsNamespace = metricsNamespace; } /// Initializes a new instance of Metrics. /// Service endpoint. - /// is null. - public Metrics(Uri endpoint) : this(endpoint, new SampleTypeSpecClientOptions()) + /// + /// 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()) { } /// Initializes a new instance of Metrics. /// Service endpoint. + /// /// The options for configuring the client. - /// is null. - public Metrics(Uri endpoint, SampleTypeSpecClientOptions options) + /// or is null. + /// is an empty string, and was expected to be non-empty. + public Metrics(Uri endpoint, string metricsNamespace, SampleTypeSpecClientOptions options) { Argument.AssertNotNull(endpoint, nameof(endpoint)); + Argument.AssertNotNullOrEmpty(metricsNamespace, nameof(metricsNamespace)); options ??= new SampleTypeSpecClientOptions(); _endpoint = endpoint; + _metricsNamespace = metricsNamespace; Pipeline = ClientPipeline.Create(options, Array.Empty(), new PipelinePolicy[] { new UserAgentPolicy(typeof(Metrics).Assembly) }, Array.Empty()); } 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 f2e56c36cb0..cc40aaedfa9 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 @@ -40,7 +40,6 @@ public partial class SampleTypeSpecClient private PetOperations _cachedPetOperations; private DogOperations _cachedDogOperations; private PlantOperations _cachedPlantOperations; - private Metrics _cachedMetrics; /// Initializes a new instance of SampleTypeSpecClient for mocking. protected SampleTypeSpecClient() @@ -1907,9 +1906,13 @@ public virtual PlantOperations GetPlantOperationsClient() } /// Initializes a new instance of Metrics. - public virtual Metrics GetMetricsClient() + /// + /// is null. + public virtual Metrics GetMetricsClient(string metricsNamespace) { - return Volatile.Read(ref _cachedMetrics) ?? Interlocked.CompareExchange(ref _cachedMetrics, new Metrics(Pipeline, _endpoint), null) ?? _cachedMetrics; + Argument.AssertNotNull(metricsNamespace, nameof(metricsNamespace)); + + return new Metrics(Pipeline, _endpoint, metricsNamespace); } } } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/tspCodeModel.json b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/tspCodeModel.json index 2fd876c5106..459ab161e6f 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/tspCodeModel.json +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/tspCodeModel.json @@ -13044,6 +13044,52 @@ { "$id": "866", "kind": "path", + "name": "metricsNamespace", + "serializedName": "metricsNamespace", + "type": { + "$id": "867", + "kind": "string", + "name": "string", + "crossLanguageDefinitionId": "TypeSpec.string", + "decorators": [] + }, + "isApiVersion": false, + "explode": false, + "style": "simple", + "allowReserved": false, + "skipUrlEncoding": false, + "optional": false, + "scope": "Client", + "decorators": [], + "readOnly": false, + "crossLanguageDefinitionId": "SampleTypeSpec.Metrics.getWidgetMetrics.metricsNamespace", + "methodParameterSegments": [ + { + "$id": "868", + "kind": "method", + "name": "metricsNamespace", + "serializedName": "metricsNamespace", + "type": { + "$id": "869", + "kind": "string", + "name": "string", + "crossLanguageDefinitionId": "TypeSpec.string", + "decorators": [] + }, + "location": "", + "isApiVersion": false, + "optional": false, + "scope": "Method", + "crossLanguageDefinitionId": "SampleTypeSpec.MetricsClientParams.metricsNamespace", + "readOnly": false, + "access": "public", + "decorators": [] + } + ] + }, + { + "$id": "870", + "kind": "path", "name": "day", "serializedName": "day", "type": { @@ -13061,7 +13107,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.Metrics.getWidgetMetrics.day", "methodParameterSegments": [ { - "$id": "867", + "$id": "871", "kind": "method", "name": "day", "serializedName": "day", @@ -13080,7 +13126,7 @@ ] }, { - "$id": "868", + "$id": "872", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -13096,7 +13142,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.Metrics.getWidgetMetrics.accept", "methodParameterSegments": [ { - "$id": "869", + "$id": "873", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -13132,7 +13178,7 @@ ], "httpMethod": "GET", "uri": "{sampleTypeSpecUrl}", - "path": "/metrics/widgets/daysOfWeek/{day}", + "path": "/metrics/{metricsNamespace}/widgets/daysOfWeek/{day}", "bufferResponse": true, "generateProtocolMethod": true, "generateConvenienceMethod": true, @@ -13142,10 +13188,10 @@ }, "parameters": [ { - "$ref": "867" + "$ref": "871" }, { - "$ref": "869" + "$ref": "873" } ], "response": { @@ -13161,12 +13207,12 @@ ], "parameters": [ { - "$id": "870", + "$id": "874", "kind": "endpoint", "name": "sampleTypeSpecUrl", "serializedName": "sampleTypeSpecUrl", "type": { - "$id": "871", + "$id": "875", "kind": "url", "name": "endpoint", "crossLanguageDefinitionId": "TypeSpec.url" @@ -13179,6 +13225,9 @@ "skipUrlEncoding": false, "readOnly": false, "crossLanguageDefinitionId": "SampleTypeSpec.Metrics.sampleTypeSpecUrl" + }, + { + "$ref": "868" } ], "initializedBy": 3,