diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ConfigurationSchemaOptions.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ConfigurationSchemaOptions.cs new file mode 100644 index 00000000000..c1ad887b65c --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ConfigurationSchemaOptions.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.TypeSpec.Generator.ClientModel +{ + /// + /// Options that control ConfigurationSchema.json generation. + /// + public class ConfigurationSchemaOptions + { + /// + /// Gets or sets the top-level section name used in the generated ConfigurationSchema.json. + /// Defaults to "Clients". Azure SDK generators should set this to "AzureClients". + /// + public string SectionName { get; set; } = ConfigurationSchemaGenerator.DefaultSectionName; + + /// + /// Gets or sets the $ref value used for the base options definition in the generated ConfigurationSchema.json. + /// Defaults to "options". Azure SDK generators should set this to "azureOptions". + /// + public string OptionsRef { get; set; } = ConfigurationSchemaGenerator.DefaultOptionsRef; + + /// + /// Gets or sets whether to generate the .NuGet.targets file alongside the ConfigurationSchema.json. + /// Defaults to true. Set to false when the build infrastructure handles targets file packing centrally. + /// + public bool GenerateNuGetTargets { get; set; } = true; + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmCodeModelGenerator.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmCodeModelGenerator.cs index 880545ed93c..5725dedc32f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmCodeModelGenerator.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmCodeModelGenerator.cs @@ -30,6 +30,11 @@ public class ScmCodeModelGenerator : CodeModelGenerator internal SerializationFormatDefinition SerializationFormatDefinition { get; } = new SerializationFormatDefinition(); + /// + /// Gets the options that control ConfigurationSchema.json generation. + /// + public ConfigurationSchemaOptions ConfigurationSchema { get; } = new(); + [ImportingConstructor] public ScmCodeModelGenerator(GeneratorContext context) : base(context) @@ -49,7 +54,10 @@ protected override void Configure() public override async Task WriteAdditionalFiles(string outputPath) { - var schemaContent = ConfigurationSchemaGenerator.Generate(OutputLibrary); + var schemaContent = ConfigurationSchemaGenerator.Generate( + OutputLibrary, + ConfigurationSchema.SectionName, + ConfigurationSchema.OptionsRef); if (schemaContent != null) { var schemaPath = Path.Combine(outputPath, "schema", "ConfigurationSchema.json"); @@ -61,12 +69,15 @@ public override async Task WriteAdditionalFiles(string outputPath) Emitter.Info($"Writing {Path.GetFullPath(schemaPath)}"); await File.WriteAllTextAsync(schemaPath, schemaContent); - // Generate the .targets file for JsonSchemaSegment registration - var packageName = Configuration.PackageName; - var targetsPath = Path.Combine(outputPath, $"{packageName}.NuGet.targets"); - var targetsContent = GenerateTargetsFile(); - Emitter.Info($"Writing {Path.GetFullPath(targetsPath)}"); - await File.WriteAllTextAsync(targetsPath, targetsContent); + if (ConfigurationSchema.GenerateNuGetTargets) + { + // Generate the .targets file for JsonSchemaSegment registration + var packageName = Configuration.PackageName; + var targetsPath = Path.Combine(outputPath, $"{packageName}.NuGet.targets"); + var targetsContent = GenerateTargetsFile(); + Emitter.Info($"Writing {Path.GetFullPath(targetsPath)}"); + await File.WriteAllTextAsync(targetsPath, targetsContent); + } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ConfigurationSchemaGeneratorTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ConfigurationSchemaGeneratorTests.cs index e118f3fd6dd..dbc47ede7e8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ConfigurationSchemaGeneratorTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ConfigurationSchemaGeneratorTests.cs @@ -749,6 +749,96 @@ public void GetJsonSchemaForType_ReturnsCorrectSchema_ForNullableTypes() Assert.AreEqual("boolean", nullableBoolSchema["type"]?.GetValue()); } + [Test] + public void Generate_UsesCustomSectionName() + { + var client = InputFactory.Client("TestService"); + var clientProvider = new ClientProvider(client); + + var output = new TestOutputLibrary([clientProvider]); + var result = ConfigurationSchemaGenerator.Generate(output, sectionName: "AzureClients"); + + Assert.IsNotNull(result); + var doc = JsonNode.Parse(result!)!; + + Assert.IsNull(doc["properties"]?["Clients"], "Schema should not have a 'Clients' section"); + var azureClients = doc["properties"]?["AzureClients"]; + Assert.IsNotNull(azureClients, "Schema should have an 'AzureClients' section"); + + var testClient = azureClients!["properties"]?["TestService"]; + Assert.IsNotNull(testClient, "Schema should have a well-known 'TestService' entry under AzureClients"); + } + + [Test] + public void Generate_UsesCustomOptionsRef() + { + var client = InputFactory.Client("TestService"); + var clientProvider = new ClientProvider(client); + + var output = new TestOutputLibrary([clientProvider]); + var result = ConfigurationSchemaGenerator.Generate(output, optionsRef: "azureOptions"); + + Assert.IsNotNull(result); + var doc = JsonNode.Parse(result!)!; + + // The options definition should inherit from azureOptions instead of options + var definitions = doc["definitions"]; + Assert.IsNotNull(definitions); + + // Find the client options definition and verify it references azureOptions + foreach (var def in definitions!.AsObject()) + { + var allOf = def.Value?["allOf"]; + if (allOf != null) + { + var baseRef = allOf.AsArray()[0]?["$ref"]?.GetValue(); + Assert.AreEqual("#/definitions/azureOptions", baseRef, + $"Definition '{def.Key}' should reference azureOptions"); + } + } + } + + [Test] + public void Generate_UsesCustomSectionNameAndOptionsRef() + { + var client = InputFactory.Client("TestService"); + var clientProvider = new ClientProvider(client); + + var output = new TestOutputLibrary([clientProvider]); + var result = ConfigurationSchemaGenerator.Generate( + output, + sectionName: "AzureClients", + optionsRef: "azureOptions"); + + Assert.IsNotNull(result); + var doc = JsonNode.Parse(result!)!; + + // Verify section name + Assert.IsNull(doc["properties"]?["Clients"]); + Assert.IsNotNull(doc["properties"]?["AzureClients"]); + + // Verify options ref + var clientEntry = doc["properties"]?["AzureClients"]?["properties"]?["TestService"]; + Assert.IsNotNull(clientEntry); + + // The options definition should use azureOptions + var optionsRef = clientEntry!["properties"]?["Options"]?["$ref"]?.GetValue(); + Assert.IsNotNull(optionsRef); + var defName = optionsRef!.Replace("#/definitions/", ""); + var optionsDef = doc["definitions"]?[defName]; + Assert.IsNotNull(optionsDef); + Assert.AreEqual("#/definitions/azureOptions", optionsDef!["allOf"]?.AsArray()[0]?["$ref"]?.GetValue()); + } + + [Test] + public void ConfigurationSchemaOptions_HasCorrectDefaults() + { + var options = new ConfigurationSchemaOptions(); + Assert.AreEqual("Clients", options.SectionName); + Assert.AreEqual("options", options.OptionsRef); + Assert.IsTrue(options.GenerateNuGetTargets); + } + /// /// Test output library that wraps provided TypeProviders. ///