From 1b4c5850f799a47f9ba515247890fd35e90f2e60 Mon Sep 17 00:00:00 2001 From: jolov Date: Tue, 31 Mar 2026 17:25:19 -0700 Subject: [PATCH 1/3] Make ConfigurationSchema section name, options ref, and NuGet.targets generation configurable Add three settable properties to ScmCodeModelGenerator: - ConfigurationSchemaSectionName: top-level section key (default: 'Clients') - ConfigurationSchemaOptionsRef: base options \ value (default: 'options') - GenerateNuGetTargets: whether to emit the .NuGet.targets file (default: true) These properties allow downstream generators and plugins to customize the ConfigurationSchema.json output without overriding WriteAdditionalFiles. For example, Azure SDK generators can set 'AzureClients' and 'azureOptions' and disable NuGet.targets generation (handled centrally by their build infra). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/ScmCodeModelGenerator.cs | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) 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..0d8dbbe34a6 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,24 @@ public class ScmCodeModelGenerator : CodeModelGenerator internal SerializationFormatDefinition SerializationFormatDefinition { get; } = new SerializationFormatDefinition(); + /// + /// 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 ConfigurationSchemaSectionName { 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 ConfigurationSchemaOptionsRef { 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; + [ImportingConstructor] public ScmCodeModelGenerator(GeneratorContext context) : base(context) @@ -49,7 +67,10 @@ protected override void Configure() public override async Task WriteAdditionalFiles(string outputPath) { - var schemaContent = ConfigurationSchemaGenerator.Generate(OutputLibrary); + var schemaContent = ConfigurationSchemaGenerator.Generate( + OutputLibrary, + ConfigurationSchemaSectionName, + ConfigurationSchemaOptionsRef); if (schemaContent != null) { var schemaPath = Path.Combine(outputPath, "schema", "ConfigurationSchema.json"); @@ -61,12 +82,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 (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); + } } } From ef96a553c9fe19cf44351a490f536772abc3e8d3 Mon Sep 17 00:00:00 2001 From: jolov Date: Tue, 31 Mar 2026 17:29:48 -0700 Subject: [PATCH 2/3] Refactor into ConfigurationSchemaOptions type Move ConfigurationSchema-related properties into a dedicated ConfigurationSchemaOptions class for cleaner API surface. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/ConfigurationSchemaOptions.cs | 29 +++++++++++++++++++ .../src/ScmCodeModelGenerator.cs | 23 ++++----------- 2 files changed, 34 insertions(+), 18 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ConfigurationSchemaOptions.cs 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 0d8dbbe34a6..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 @@ -31,22 +31,9 @@ public class ScmCodeModelGenerator : CodeModelGenerator new SerializationFormatDefinition(); /// - /// 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". + /// Gets the options that control ConfigurationSchema.json generation. /// - public string ConfigurationSchemaSectionName { 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 ConfigurationSchemaOptionsRef { 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; + public ConfigurationSchemaOptions ConfigurationSchema { get; } = new(); [ImportingConstructor] public ScmCodeModelGenerator(GeneratorContext context) @@ -69,8 +56,8 @@ public override async Task WriteAdditionalFiles(string outputPath) { var schemaContent = ConfigurationSchemaGenerator.Generate( OutputLibrary, - ConfigurationSchemaSectionName, - ConfigurationSchemaOptionsRef); + ConfigurationSchema.SectionName, + ConfigurationSchema.OptionsRef); if (schemaContent != null) { var schemaPath = Path.Combine(outputPath, "schema", "ConfigurationSchema.json"); @@ -82,7 +69,7 @@ public override async Task WriteAdditionalFiles(string outputPath) Emitter.Info($"Writing {Path.GetFullPath(schemaPath)}"); await File.WriteAllTextAsync(schemaPath, schemaContent); - if (GenerateNuGetTargets) + if (ConfigurationSchema.GenerateNuGetTargets) { // Generate the .targets file for JsonSchemaSegment registration var packageName = Configuration.PackageName; From 359e6d78c643db7dcc2af2bb890938af5c260e11 Mon Sep 17 00:00:00 2001 From: jolov Date: Tue, 31 Mar 2026 17:33:00 -0700 Subject: [PATCH 3/3] Add tests for ConfigurationSchemaOptions Test custom section name, custom options ref, combined usage, and default values. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../test/ConfigurationSchemaGeneratorTests.cs | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) 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. ///