From b099881bae192973097a71eacef40f09d87f9387 Mon Sep 17 00:00:00 2001 From: jolov Date: Tue, 22 Sep 2026 15:57:17 -0700 Subject: [PATCH 1/8] feat(http-client-csharp): emit experimental diagnostic metadata Consume shared lifecycle details, emit public diagnostic attributes and scoped dependency suppressions, and document explicit graduation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../http-client-csharp/.tspd/docs/usage.md | 29 +++++ .../emitter/src/lib/operation-converter.ts | 12 ++ .../emitter/src/type/input-operation.ts | 6 + .../emitter/test/Unit/decorator-list.test.ts | 70 ++++++++++ .../emitter/test/Unit/utils/test-util.ts | 2 + .../src/Providers/RestClientProvider.cs | 5 +- .../Providers/ScmMethodProviderCollection.cs | 21 ++- .../src/Utilities/ExperimentalApiHelpers.cs | 45 +++++++ .../ScmMethodProviderCollectionTests.cs | 120 ++++++++++++++++++ .../TestClient.cs | 18 +++ .../InputTypes/InputExperimentalDetails.cs | 24 ++++ .../src/InputTypes/InputOperation.cs | 8 +- .../Serialization/InputOperationConverter.cs | 5 +- .../test/TypeSpecInputConverterTests.cs | 40 ++++++ packages/http-client-csharp/package-lock.json | 87 +++++++++++++ packages/http-client-csharp/package.json | 2 + packages/http-client-csharp/readme.md | 29 +++++ 17 files changed, 518 insertions(+), 5 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Utilities/ExperimentalApiHelpers.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ExperimentalOperationSupportsPartialMethods/TestClient.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputExperimentalDetails.cs diff --git a/packages/http-client-csharp/.tspd/docs/usage.md b/packages/http-client-csharp/.tspd/docs/usage.md index 7db6449e111..83c0031c6ca 100644 --- a/packages/http-client-csharp/.tspd/docs/usage.md +++ b/packages/http-client-csharp/.tspd/docs/usage.md @@ -6,3 +6,32 @@ ### Customizing Generated Code For detailed instructions on how to customize the generated C# code, see the [Customization Guide](https://github.com/microsoft/typespec/blob/main/packages/http-client-csharp/.tspd/docs/customization.md). + +### Experimental operations + +Use `TypeSpec.HttpClient.@experimental` to assign a public diagnostic ID to an operation and +identify experiments used by its implementation: + +```typespec +import "@typespec/http-client"; + +@TypeSpec.HttpClient.experimental(#{ + emitterScope: "@typespec/http-client-csharp", + diagnosticId: "C", + dependsOn: #["A", "B"], +}) +op bar(): void; +``` + +The C# emitter adds `[Experimental("C")]` to the generated synchronous and asynchronous +protocol and convenience methods. It disables dependency diagnostics `A` and `B` inside +the generated method and request-helper bodies and restores them at the end of each body. +These suppressions do not affect callers. + +Both metadata fields are optional. Without `diagnosticId`, no public experimental attribute +is added; without `dependsOn`, no dependency suppressions are added. Emitter scopes apply to +both fields. C# currently consumes this metadata on operations, not on models or properties. + +Graduation is an explicit source change: dependencies becoming generally available, or +removing entries from `dependsOn`, does not remove `[Experimental("C")]`. Remove the +operation's `@experimental` decorator when the public API is ready to graduate. diff --git a/packages/http-client-csharp/emitter/src/lib/operation-converter.ts b/packages/http-client-csharp/emitter/src/lib/operation-converter.ts index 21d4fdbc3dd..6945baac2b9 100644 --- a/packages/http-client-csharp/emitter/src/lib/operation-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/operation-converter.ts @@ -39,8 +39,10 @@ import { isErrorModel, NoTarget, } from "@typespec/compiler"; +import { $ } from "@typespec/compiler/typekit"; import { unsafe_getEventDefinitions } from "@typespec/events/experimental"; import type { HttpStatusCodeRange } from "@typespec/http"; +import "@typespec/http-client/typekit"; import { getResourceOperation } from "@typespec/rest"; import { isTerminalEvent } from "@typespec/sse"; import type { CSharpEmitterContext } from "../sdk-context.js"; @@ -217,6 +219,13 @@ export function fromSdkServiceMethodOperation( generateConvenience = false; } + const lifecycle = diagnostics.pipe( + $(sdkContext.program).client.getFeatureLifecycleDetails.withDiagnostics( + method.operation.__raw.operation, + { emitterName: "@typespec/http-client-csharp" }, + ), + ); + operation = { name: method.name, isExactName: method.isExactName, @@ -252,6 +261,9 @@ export function fromSdkServiceMethodOperation( namespace: method.__raw?.namespace ? getClientNamespace(sdkContext, method.__raw.namespace) : undefined, + experimental: lifecycle + ? { diagnosticId: lifecycle.diagnosticId, dependsOn: [...lifecycle.dependsOn] } + : undefined, }; sdkContext.__typeCache.updateSdkOperationReferences(method.operation, operation); diff --git a/packages/http-client-csharp/emitter/src/type/input-operation.ts b/packages/http-client-csharp/emitter/src/type/input-operation.ts index 809719303e7..50632ee0576 100644 --- a/packages/http-client-csharp/emitter/src/type/input-operation.ts +++ b/packages/http-client-csharp/emitter/src/type/input-operation.ts @@ -29,4 +29,10 @@ export interface InputOperation { crossLanguageDefinitionId: string; decorators?: DecoratorInfo[]; namespace?: string; + experimental?: InputExperimentalDetails; +} + +export interface InputExperimentalDetails { + diagnosticId?: string; + dependsOn: string[]; } diff --git a/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts b/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts index 05039192bd4..a07a6881984 100644 --- a/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts @@ -5,6 +5,7 @@ import { expectDiagnostics, type TestHost, } from "@typespec/compiler/testing"; +import { HttpClientTestLibrary } from "@typespec/http-client/testing"; import { deepStrictEqual, strictEqual } from "assert"; import { ok } from "assert/strict"; import { beforeEach, describe, it, vi } from "vitest"; @@ -23,6 +24,75 @@ describe("Test emitting decorator list", () => { runner = await createEmitterTestHost(); }); + describe("experimental operations", () => { + beforeEach(async () => { + await runner.addTypeSpecLibrary(HttpClientTestLibrary); + }); + + it.each([ + { scope: undefined, applies: true }, + { scope: "@typespec/http-client-csharp", applies: true }, + { scope: "other-emitter, @typespec/http-client-csharp", applies: true }, + { scope: "other-emitter", applies: false }, + { scope: "!other-emitter", applies: true }, + { scope: "!@typespec/http-client-csharp", applies: false }, + { scope: "!other-emitter, !@typespec/http-client-csharp", applies: false }, + ])("respects emitter scope $scope", async ({ scope, applies }) => { + const program = await typeSpecCompile( + ` + @TypeSpec.HttpClient.experimental(#{ + ${scope === undefined ? "" : `emitterScope: "${scope}",`} + diagnosticId: "C", + dependsOn: #["A", "B"] + }) + op bar(): void; + `, + runner, + { IsHttpClientNeeded: true }, + ); + expectDiagnosticEmpty(program.diagnostics); + const sdkContext = await createCSharpSdkContext(createEmitterContext(program), { + additionalDecorators: [], + }); + expectDiagnosticEmpty(sdkContext.diagnostics); + const [root, diagnostics] = createModel(sdkContext); + expectDiagnosticEmpty(diagnostics); + deepStrictEqual( + root.clients[0].methods[0].operation.experimental, + applies ? { diagnosticId: "C", dependsOn: ["A", "B"] } : undefined, + ); + }); + + it.each([ + { decorator: "", expected: undefined }, + { + decorator: "@TypeSpec.HttpClient.experimental", + expected: { diagnosticId: undefined, dependsOn: [] }, + }, + { + decorator: '@TypeSpec.HttpClient.experimental(#{ diagnosticId: "C" })', + expected: { diagnosticId: "C", dependsOn: [] }, + }, + { + decorator: '@TypeSpec.HttpClient.experimental(#{ dependsOn: #["A"] })', + expected: { diagnosticId: undefined, dependsOn: ["A"] }, + }, + { + decorator: '@TypeSpec.HttpClient.experimental(#{ diagnosticId: "C", dependsOn: #[] })', + expected: { diagnosticId: "C", dependsOn: [] }, + }, + ])("preserves optional metadata for $decorator", async ({ decorator, expected }) => { + const program = await typeSpecCompile(`${decorator} op bar(): void;`, runner, { + IsHttpClientNeeded: true, + }); + expectDiagnosticEmpty(program.diagnostics); + const sdkContext = await createCSharpSdkContext(createEmitterContext(program)); + const [root, diagnostics] = createModel(sdkContext); + expectDiagnosticEmpty(diagnostics); + deepStrictEqual(root.clients[0].methods[0].operation.experimental, expected); + }); + }); + it("emit decorator list on a client", async () => { const program = await typeSpecCompile( ` diff --git a/packages/http-client-csharp/emitter/test/Unit/utils/test-util.ts b/packages/http-client-csharp/emitter/test/Unit/utils/test-util.ts index 59e17d91097..1c406eb10da 100644 --- a/packages/http-client-csharp/emitter/test/Unit/utils/test-util.ts +++ b/packages/http-client-csharp/emitter/test/Unit/utils/test-util.ts @@ -52,6 +52,7 @@ export interface TypeSpecCompileOptions { NoEmit?: boolean; IsVersionNeeded?: boolean; IsSseNeeded?: boolean; + IsHttpClientNeeded?: boolean; } export async function typeSpecCompile( @@ -85,6 +86,7 @@ export async function typeSpecCompile( const fileContent = ` import "@typespec/rest"; import "@typespec/http"; + ${options?.IsHttpClientNeeded ? 'import "@typespec/http-client";' : ""} import "@typespec/http/streams"; ${needSse ? 'import "@typespec/events";\nimport "@typespec/sse";' : ""} import "@typespec/versioning"; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs index 38fde18782f..b9a08ed4189 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs @@ -8,6 +8,7 @@ using System.Linq; using Microsoft.TypeSpec.Generator.ClientModel.Primitives; using Microsoft.TypeSpec.Generator.ClientModel.Snippets; +using Microsoft.TypeSpec.Generator.ClientModel.Utilities; using Microsoft.TypeSpec.Generator.EmitterRpc; using Microsoft.TypeSpec.Generator.Expressions; using Microsoft.TypeSpec.Generator.Input; @@ -245,13 +246,15 @@ private ScmMethodProvider BuildCreateRequestMethod(InputServiceMethod serviceMet // Build message and all request modifications var messageStatements = BuildMessage(serviceMethod, signature, isNextLinkRequest); - return new ScmMethodProvider( + var method = new ScmMethodProvider( signature, messageStatements, this, ScmMethodKind.CreateRequest, xmlDocProvider: XmlDocProvider.Empty, serviceMethod: serviceMethod); + ExperimentalApiHelpers.AddDependencySuppressions(method, serviceMethod.Operation); + return method; } private MethodBodyStatements BuildMessage( diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs index aba81a66377..89df02d287c 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs @@ -215,8 +215,13 @@ private ScmMethodProvider BuildConvenienceMethod(MethodProvider protocolMethod, GetConvenienceMethodModifiers(protocolMethod.Signature.Modifiers, signatureParameters), GetResponseType(ServiceMethod.Operation.Responses, true, isAsync, out _), null, - signatureParameters, - Attributes: BuildConvenienceMethodAttributes()); + signatureParameters); + } + + var convenienceAttributes = BuildConvenienceMethodAttributes(); + if (convenienceAttributes is not null) + { + methodSignature.Update(attributes: [.. methodSignature.Attributes, .. convenienceAttributes]); } // Recompute the response body type so we can branch the body accordingly. @@ -302,6 +307,7 @@ .. GetStackVariablesForReturnValueConversion(result, responseBodyType, isAsync, } var convenienceMethod = new ScmMethodProvider(methodSignature, methodBody, EnclosingType, ScmMethodKind.Convenience, collectionDefinition: collection, serviceMethod: ServiceMethod); + ExperimentalApiHelpers.AddDependencySuppressions(convenienceMethod, ServiceMethod.Operation); if (convenienceMethod.XmlDocs != null) { @@ -1121,6 +1127,11 @@ private static bool IsConvertibleFromBinaryData(CSharpType type) private IReadOnlyList? BuildConvenienceMethodAttributes() { + if (ExperimentalApiHelpers.BuildAttribute(ServiceMethod.Operation) is { } experimentalAttribute) + { + return [experimentalAttribute]; + } + var bodyInputParam = ServiceMethod.Parameters.FirstOrDefault(p => p.Location == InputRequestLocation.Body); if (bodyInputParam?.Type is InputModelType bodyModel && bodyModel.Usage.HasFlag(InputModelTypeUsage.MultipartFormData)) @@ -1441,6 +1452,11 @@ private ScmMethodProvider BuildProtocolMethod(MethodProvider createRequestMethod bodyParameters = parameters; } + if (ExperimentalApiHelpers.BuildAttribute(ServiceMethod.Operation) is { } experimentalAttribute) + { + methodSignature.Update(attributes: [.. methodSignature.Attributes, experimentalAttribute]); + } + TypeProvider? collection = null; MethodBodyStatement[] methodBody; if (_pagingServiceMethod != null) @@ -1478,6 +1494,7 @@ .. ServiceMethod.Operation.BufferResponse var protocolMethod = new ScmMethodProvider(methodSignature, methodBody, EnclosingType, ScmMethodKind.Protocol, collectionDefinition: collection, serviceMethod: ServiceMethod); + ExperimentalApiHelpers.AddDependencySuppressions(protocolMethod, ServiceMethod.Operation); if (protocolMethod.XmlDocs != null) { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Utilities/ExperimentalApiHelpers.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Utilities/ExperimentalApiHelpers.cs new file mode 100644 index 00000000000..47179878447 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Utilities/ExperimentalApiHelpers.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.TypeSpec.Generator.Input; +using Microsoft.TypeSpec.Generator.Providers; +using Microsoft.TypeSpec.Generator.Statements; +using static Microsoft.TypeSpec.Generator.Snippets.Snippet; + +namespace Microsoft.TypeSpec.Generator.ClientModel.Utilities +{ + internal static class ExperimentalApiHelpers + { + public static AttributeStatement? BuildAttribute(InputOperation operation) + => operation.Experimental?.DiagnosticId is { } diagnosticId + ? new AttributeStatement(typeof(ExperimentalAttribute), [Literal(diagnosticId)]) + : null; + + public static void AddDependencySuppressions(MethodProvider method, InputOperation operation) + { + if (operation.Experimental?.DependsOn is not { Count: > 0 } dependencies) + { + return; + } + + // An empty #pragma warning disable would suppress every diagnostic. + if (dependencies.Any(string.IsNullOrWhiteSpace)) + { + throw new ArgumentException("Experimental dependency diagnostic identifiers cannot be empty or whitespace.", nameof(operation)); + } + + var suppressions = dependencies.Distinct(StringComparer.Ordinal) + .Select(id => new SuppressionStatement(null, Literal(id), "This method depends on experimental functionality.")) + .ToArray(); + method.Update(bodyStatements: new MethodBodyStatements( + [ + .. suppressions.Select(s => s.DisableStatement), + method.BodyStatements!, + .. suppressions.Select(s => s.RestoreStatement) + ])); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs index a2cab910cda..9ab7524f727 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs @@ -5,6 +5,7 @@ using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Net.ServerSentEvents; @@ -12,13 +13,17 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.TypeSpec.Generator.ClientModel.Providers; using Microsoft.TypeSpec.Generator.EmitterRpc; +using Microsoft.TypeSpec.Generator.Expressions; using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Input.Extensions; using Microsoft.TypeSpec.Generator.Primitives; using Microsoft.TypeSpec.Generator.Providers; using Microsoft.TypeSpec.Generator.Snippets; +using Microsoft.TypeSpec.Generator.Statements; using Microsoft.TypeSpec.Generator.Tests.Common; using NUnit.Framework; @@ -34,6 +39,121 @@ internal class ScmMethodProviderCollectionTests InputFactory.Property("p2", InputPrimitiveType.String, isRequired: true), ]); + [TestCase("C", new[] { "A", "B" })] + [TestCase("C", new string[0])] + [TestCase(null, new[] { "A", "B" })] + [TestCase(null, new string[0])] + [TestCase(null, null)] + [TestCase("C", new[] { "A", "A", "B" })] + public void ExperimentalOperationGeneratesScopedDiagnostics(string? diagnosticId, string[]? dependencies) + { + var operation = InputFactory.Operation("Bar"); + operation.Update(experimental: dependencies is null ? null : new InputExperimentalDetails(diagnosticId, dependencies)); + var serviceMethod = InputFactory.BasicServiceMethod("Bar", operation); + var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]); + MockHelpers.LoadMockGenerator(clients: () => [inputClient]); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + var methods = new ScmMethodProviderCollection(serviceMethod, client); + + Assert.AreEqual(4, methods.Count); + var expectedDependencies = dependencies?.Distinct().ToArray() ?? []; + foreach (var method in methods.Append(client.RestClient.GetCreateRequestMethod(operation))) + { + var attribute = method.Signature.Attributes.SingleOrDefault(a => a.Type.Equals(typeof(ExperimentalAttribute))); + if (method is ScmMethodProvider { Kind: ScmMethodKind.CreateRequest } || diagnosticId is null) + { + Assert.IsNull(attribute); + } + else + { + Assert.IsNotNull(attribute); + Assert.AreEqual(diagnosticId, ((LiteralExpression)((ScopedApi)attribute!.Arguments.Single()).Original).Literal); + } + + using var writer = new CodeWriter(); + writer.WriteMethod(method); + var code = writer.ToString(false); + var directives = CSharpSyntaxTree.ParseText(code).GetRoot() + .DescendantTrivia(descendIntoTrivia: true) + .Select(t => t.GetStructure()) + .OfType() + .ToArray(); + var disables = directives.Where(d => d.DisableOrRestoreKeyword.IsKind(SyntaxKind.DisableKeyword)).ToArray(); + var restores = directives.Where(d => d.DisableOrRestoreKeyword.IsKind(SyntaxKind.RestoreKeyword)).ToArray(); + CollectionAssert.AreEqual(expectedDependencies, disables.Select(d => d.ErrorCodes.Single().ToString())); + CollectionAssert.AreEqual(expectedDependencies, restores.Select(d => d.ErrorCodes.Single().ToString())); + Assert.IsTrue(disables.All(d => d.SpanStart > code.IndexOf('{'))); + Assert.IsTrue(restores.All(d => d.Span.End < code.LastIndexOf('}'))); + } + } + + [TestCase("")] + [TestCase(" \t")] + public void ExperimentalOperationRejectsEmptyDependencyDiagnostic(string diagnosticId) + { + var operation = InputFactory.Operation("Bar"); + operation.Update(experimental: new InputExperimentalDetails("C", [diagnosticId])); + var serviceMethod = InputFactory.BasicServiceMethod("Bar", operation); + var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]); + MockHelpers.LoadMockGenerator(clients: () => [inputClient]); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + + Assert.Throws(() => new ScmMethodProviderCollection(serviceMethod, client)); + } + + [TestCase(null)] + [TestCase("C")] + public void ExperimentalMultipartOperationHasOnePublicDiagnostic(string? diagnosticId) + { + var body = InputFactory.Model("UploadBody", usage: InputModelTypeUsage.Input | InputModelTypeUsage.MultipartFormData); + var operation = InputFactory.Operation("Upload", + parameters: [InputFactory.BodyParameter("body", body, isRequired: true)], + requestMediaTypes: ["multipart/form-data"]); + if (diagnosticId is not null) + { + operation.Update(experimental: new InputExperimentalDetails(diagnosticId, [ClientModel.Providers.ScmModelProvider.FileBinaryContentDiagnosticId])); + } + var serviceMethod = InputFactory.BasicServiceMethod("Upload", operation, + parameters: [InputFactory.MethodParameter("body", body, location: InputRequestLocation.Body, isRequired: true)]); + var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]); + MockHelpers.LoadMockGenerator(inputModels: () => [body], clients: () => [inputClient]); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + var methods = new ScmMethodProviderCollection(serviceMethod, client); + + Assert.AreEqual(2, methods.Count(m => m.Kind == ScmMethodKind.Convenience)); + foreach (var method in methods.Where(m => m.Kind == ScmMethodKind.Convenience)) + { + var attribute = method.Signature.Attributes.Single(a => a.Type.Equals(typeof(ExperimentalAttribute))); + Assert.AreEqual(diagnosticId ?? ClientModel.Providers.ScmModelProvider.FileBinaryContentDiagnosticId, + ((LiteralExpression)((ScopedApi)attribute.Arguments.Single()).Original).Literal); + } + } + + [Test] + public async Task ExperimentalOperationSupportsPartialMethods() + { + var operation = InputFactory.Operation("Bar"); + operation.Update(experimental: new InputExperimentalDetails("C", ["A", "B"])); + var serviceMethod = InputFactory.BasicServiceMethod("Bar", operation); + var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]); + await MockHelpers.LoadMockGeneratorAsync( + clients: () => [inputClient], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + var methods = new ScmMethodProviderCollection(serviceMethod, client); + + Assert.AreEqual(4, methods.Count); + foreach (var method in methods) + { + Assert.IsTrue(method.IsPartialMethod); + var attribute = method.Signature.Attributes.Single(a => a.Type.Equals(typeof(ExperimentalAttribute))); + Assert.AreEqual("C", ((LiteralExpression)((ScopedApi)attribute.Arguments.Single()).Original).Literal); + var body = (MethodBodyStatements)method.BodyStatements!; + Assert.AreEqual(2, body.Statements.OfType().Count()); + Assert.AreEqual(2, body.Statements.OfType().Count()); + } + } + [Test] public void JsonLinesRequestGeneratesAsyncStreamingConvenienceMethod() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ExperimentalOperationSupportsPartialMethods/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ExperimentalOperationSupportsPartialMethods/TestClient.cs new file mode 100644 index 00000000000..6a22a011362 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ExperimentalOperationSupportsPartialMethods/TestClient.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Threading; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + public partial ClientResult Bar(RequestOptions options); + public partial Task BarAsync(RequestOptions options); + public partial ClientResult Bar(CancellationToken cancellationToken); + public partial Task BarAsync(CancellationToken cancellationToken); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputExperimentalDetails.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputExperimentalDetails.cs new file mode 100644 index 00000000000..ca85c97ad11 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputExperimentalDetails.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Microsoft.TypeSpec.Generator.Input +{ + public sealed class InputExperimentalDetails + { + [JsonConstructor] + public InputExperimentalDetails(string? diagnosticId = null, IReadOnlyList? dependsOn = null) + { + DiagnosticId = diagnosticId; + DependsOn = dependsOn ?? []; + } + + [JsonPropertyName("diagnosticId")] + public string? DiagnosticId { get; } + + [JsonPropertyName("dependsOn")] + public IReadOnlyList DependsOn { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputOperation.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputOperation.cs index 20e9f92bfe5..2bf8d28a7d9 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputOperation.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputOperation.cs @@ -102,6 +102,7 @@ public InputOperation() : this( public string CrossLanguageDefinitionId { get; internal set; } public IReadOnlyList Decorators { get; internal set; } = new List(); public IReadOnlyList Examples { get; internal set; } = new List(); + public InputExperimentalDetails? Experimental { get; internal set; } private bool? _isMultipartFormData; public bool IsMultipartFormData => _isMultipartFormData ??= RequestMediaTypes is not null && RequestMediaTypes.Count == 1 && RequestMediaTypes[0] == "multipart/form-data"; @@ -124,7 +125,8 @@ public void Update( bool? generateProtocolMethod = null, bool? generateConvenienceMethod = null, string? crossLanguageDefinitionId = null, - string? ns = null) + string? ns = null, + InputExperimentalDetails? experimental = null) { if (name != null) { @@ -198,6 +200,10 @@ public void Update( { Namespace = ns; } + if (experimental != null) + { + Experimental = experimental; + } } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputOperationConverter.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputOperationConverter.cs index 4225b8747c7..646a1f5cc61 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputOperationConverter.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputOperationConverter.cs @@ -57,6 +57,7 @@ public override void Write(Utf8JsonWriter writer, InputOperation value, JsonSeri IReadOnlyList? decorators = null; IReadOnlyList? examples = null; bool isExactName = false; + InputExperimentalDetails? experimental = null; while (reader.TokenType != JsonTokenType.EndObject) { @@ -80,7 +81,8 @@ public override void Write(Utf8JsonWriter writer, InputOperation value, JsonSeri || reader.TryReadString("crossLanguageDefinitionId", ref crossLanguageDefinitionId) || reader.TryReadComplexType("decorators", options, ref decorators) || reader.TryReadComplexType("examples", options, ref examples) - || reader.TryReadString("namespace", ref ns); + || reader.TryReadString("namespace", ref ns) + || reader.TryReadComplexType("experimental", options, ref experimental); if (!isKnownProperty) { @@ -110,6 +112,7 @@ public override void Write(Utf8JsonWriter writer, InputOperation value, JsonSeri operation.Decorators = decorators ?? []; operation.Examples = examples ?? []; operation.Namespace = ns; + operation.Experimental = experimental; return operation; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/test/TypeSpecInputConverterTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/test/TypeSpecInputConverterTests.cs index 319b0f898ab..530f8b54371 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/test/TypeSpecInputConverterTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/test/TypeSpecInputConverterTests.cs @@ -10,6 +10,46 @@ namespace Microsoft.TypeSpec.Generator.Input.Tests { public class TypeSpecInputConverterTests { + [TestCase("""{"diagnosticId":"C","dependsOn":["A","B"]}""", "C", new[] { "A", "B" })] + [TestCase("""{"diagnosticId":"C"}""", "C", new string[0])] + [TestCase("""{"dependsOn":["A"]}""", null, new[] { "A" })] + [TestCase("""{}""", null, new string[0])] + [TestCase("""null""", null, null)] + [TestCase(null, null, null)] + public void LoadsExperimentalOperationDetails(string? experimental, string? diagnosticId, string[]? dependencies) + { + var content = $$""" + { + "$id": "operation", + "name": "bar", + "httpMethod": "GET", + "uri": "", + "path": "", + {{(experimental is null ? "" : $@"""experimental"": {experimental},")}} + "crossLanguageDefinitionId": "Test.bar" + } + """; + var referenceHandler = new TypeSpecReferenceHandler(); + var options = new JsonSerializerOptions + { + ReferenceHandler = referenceHandler, + Converters = { new InputOperationConverter(referenceHandler) } + }; + + var operation = JsonSerializer.Deserialize(content, options)!; + + if (dependencies is null) + { + Assert.IsNull(operation.Experimental); + } + else + { + Assert.IsNotNull(operation.Experimental); + Assert.AreEqual(diagnosticId, operation.Experimental!.DiagnosticId); + CollectionAssert.AreEqual(dependencies, operation.Experimental.DependsOn); + } + } + [TestCase(true)] [TestCase(false)] public void LoadsReferencesDefinedInDecoratorArguments(bool definitionsFirst) diff --git a/packages/http-client-csharp/package-lock.json b/packages/http-client-csharp/package-lock.json index e970782e722..4d4622b1ac1 100644 --- a/packages/http-client-csharp/package-lock.json +++ b/packages/http-client-csharp/package-lock.json @@ -20,6 +20,7 @@ "@typespec/compiler": "1.16.0", "@typespec/events": "0.86.0", "@typespec/http": "1.16.0", + "@typespec/http-client": "0.17.0", "@typespec/http-specs": "0.1.0-alpha.43", "@typespec/json-schema": "1.16.0", "@typespec/library-linter": "0.86.0", @@ -45,6 +46,7 @@ "@typespec/compiler": "^1.16.0", "@typespec/events": ">=0.86.0 <0.87.0 || ~0.87.0-0", "@typespec/http": "^1.16.0", + "@typespec/http-client": ">=0.17.0 <0.18.0 || ~0.18.0-0", "@typespec/openapi": "^1.16.0", "@typespec/rest": ">=0.86.0 <0.87.0 || ~0.87.0-0", "@typespec/sse": ">=0.86.0 <0.87.0 || ~0.87.0-0", @@ -66,6 +68,21 @@ "ws": "^8.21.0" } }, + "node_modules/@alloy-js/csharp": { + "version": "0.24.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@alloy-js/csharp/-/csharp-0.24.0.tgz", + "integrity": "sha1-3teiKMOBpttHQNseUhI2dmW4OGU=", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@alloy-js/core": "~0.24.0", + "@alloy-js/msbuild": "~0.24.0", + "change-case": "^5.4.4", + "marked": "^18.0.5", + "pathe": "^2.0.3" + } + }, "node_modules/@alloy-js/markdown": { "version": "0.24.0", "dev": true, @@ -75,6 +92,33 @@ "yaml": "^2.7.1" } }, + "node_modules/@alloy-js/msbuild": { + "version": "0.24.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@alloy-js/msbuild/-/msbuild-0.24.0.tgz", + "integrity": "sha1-CtpDJb2TbS+QcA+WHwkCzHTKCZQ=", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@alloy-js/core": "~0.24.0", + "change-case": "^5.4.4", + "marked": "^18.0.5", + "pathe": "^2.0.3" + } + }, + "node_modules/@alloy-js/python": { + "version": "0.5.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@alloy-js/python/-/python-0.5.0.tgz", + "integrity": "sha1-5jp01/A7C2FQRtAy6x9KXwv8QHQ=", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@alloy-js/core": "~0.24.0", + "change-case": "^5.4.4", + "pathe": "^2.0.3" + } + }, "node_modules/@alloy-js/typescript": { "version": "0.24.0", "dev": true, @@ -1533,6 +1577,21 @@ "node": ">=10" } }, + "node_modules/@typespec/emitter-framework": { + "version": "0.21.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typespec/emitter-framework/-/emitter-framework-0.21.0.tgz", + "integrity": "sha512-ACVZ6cUpO+dmzA0xbBxYuYAWwkPp59kABxIES5BRvhKg873hyI7o3vM3Ni+m5FK4JWlBdw1oR4QINSNUt5rDCw==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@alloy-js/core": "^0.24.1", + "@alloy-js/csharp": "^0.24.0", + "@alloy-js/python": "^0.5.0", + "@alloy-js/typescript": "^0.24.0", + "@typespec/compiler": "^1.16.0" + } + }, "node_modules/@typespec/events": { "version": "0.86.0", "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@typespec/events/-/events-0.86.0.tgz", @@ -1565,6 +1624,20 @@ } } }, + "node_modules/@typespec/http-client": { + "version": "0.17.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typespec/http-client/-/http-client-0.17.0.tgz", + "integrity": "sha1-Z92YYnO4zIC0v8htDkxGrxjRuRk=", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@alloy-js/core": "^0.24.1", + "@alloy-js/typescript": "^0.24.0", + "@typespec/compiler": "^1.16.0", + "@typespec/emitter-framework": "^0.21.0", + "@typespec/http": "^1.16.0" + } + }, "node_modules/@typespec/http-specs": { "version": "0.1.0-alpha.43", "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@typespec/http-specs/-/http-specs-0.1.0-alpha.43.tgz", @@ -4009,6 +4082,20 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/marked": { + "version": "18.0.13", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/marked/-/marked-18.0.13.tgz", + "integrity": "sha512-xTxVzZsBFwunP6HDmtBkabUQEYArnP7/rMDGmPj9SlrKlQ4i8MdYVow+nJL0eOqwpUqhzBoTBRADGN6uYwPyOw==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/math-intrinsics/-/math-intrinsics-1.1.0.tgz", diff --git a/packages/http-client-csharp/package.json b/packages/http-client-csharp/package.json index 2439b55f914..f6e654c25e1 100644 --- a/packages/http-client-csharp/package.json +++ b/packages/http-client-csharp/package.json @@ -63,6 +63,7 @@ "@typespec/compiler": "^1.16.0", "@typespec/events": ">=0.86.0 <0.87.0 || ~0.87.0-0", "@typespec/http": "^1.16.0", + "@typespec/http-client": ">=0.17.0 <0.18.0 || ~0.18.0-0", "@typespec/openapi": "^1.16.0", "@typespec/rest": ">=0.86.0 <0.87.0 || ~0.87.0-0", "@typespec/sse": ">=0.86.0 <0.87.0 || ~0.87.0-0", @@ -78,6 +79,7 @@ "@typespec/compiler": "1.16.0", "@typespec/events": "0.86.0", "@typespec/http": "1.16.0", + "@typespec/http-client": "0.17.0", "@typespec/http-specs": "0.1.0-alpha.43", "@typespec/json-schema": "1.16.0", "@typespec/library-linter": "0.86.0", diff --git a/packages/http-client-csharp/readme.md b/packages/http-client-csharp/readme.md index 39a4197c08e..f84a664d4b3 100644 --- a/packages/http-client-csharp/readme.md +++ b/packages/http-client-csharp/readme.md @@ -19,6 +19,35 @@ npm install @typespec/http-client-csharp For detailed instructions on how to customize the generated C# code, see the [Customization Guide](https://github.com/microsoft/typespec/blob/main/packages/http-client-csharp/.tspd/docs/customization.md). +### Experimental operations + +Use `TypeSpec.HttpClient.@experimental` to assign a public diagnostic ID to an operation and +identify experiments used by its implementation: + +```typespec +import "@typespec/http-client"; + +@TypeSpec.HttpClient.experimental(#{ + emitterScope: "@typespec/http-client-csharp", + diagnosticId: "C", + dependsOn: #["A", "B"], +}) +op bar(): void; +``` + +The C# emitter adds `[Experimental("C")]` to the generated synchronous and asynchronous +protocol and convenience methods. It disables dependency diagnostics `A` and `B` inside +the generated method and request-helper bodies and restores them at the end of each body. +These suppressions do not affect callers. + +Both metadata fields are optional. Without `diagnosticId`, no public experimental attribute +is added; without `dependsOn`, no dependency suppressions are added. Emitter scopes apply to +both fields. C# currently consumes this metadata on operations, not on models or properties. + +Graduation is an explicit source change: dependencies becoming generally available, or +removing entries from `dependsOn`, does not remove `[Experimental("C")]`. Remove the +operation's `@experimental` decorator when the public API is ready to graduate. + ## Emitter usage 1. Via the command line From 36b88e465c0d42c1b602aa793576e5ef20239bbc Mon Sep 17 00:00:00 2001 From: jolov Date: Tue, 22 Sep 2026 16:10:05 -0700 Subject: [PATCH 2/8] test(http-client-csharp): add experimental operation samples Annotate direct and composed sample experiments, regenerate C# output, and verify public diagnostic attributes across generated overloads. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Local.Tests/CustomizationTests.cs | 23 +++++++++++++++++++ .../Local/Sample-TypeSpec/Sample-TypeSpec.tsp | 10 ++++++++ .../SampleTypeSpecClient.RestClient.cs | 2 ++ .../src/Generated/SampleTypeSpecClient.cs | 16 +++++++++++++ .../Local/Sample-TypeSpec/tspCodeModel.json | 14 +++++++++-- 5 files changed, 63 insertions(+), 2 deletions(-) diff --git a/packages/http-client-csharp/generator/TestProjects/Local.Tests/CustomizationTests.cs b/packages/http-client-csharp/generator/TestProjects/Local.Tests/CustomizationTests.cs index b995d6d61fb..b9e72cad196 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local.Tests/CustomizationTests.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local.Tests/CustomizationTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using NUnit.Framework; @@ -10,6 +11,28 @@ namespace TestProjects.Local.Tests { public class CustomizationTests { + [TestCase("HelloDemo2", "SAMPLE0001", 2)] + [TestCase("HelloDemo2Async", "SAMPLE0001", 2)] + [TestCase("DynamicModelOperation", "SAMPLE0002", 2)] + [TestCase("DynamicModelOperationAsync", "SAMPLE0002", 2)] + [TestCase("SayHi", null, 2)] + [TestCase("SayHiAsync", null, 2)] + [TestCase("CreateHelloDemo2Request", null, 1)] + [TestCase("CreateDynamicModelOperationRequest", null, 1)] + public void ExperimentalOperationDiagnosticsAreScopedToPublicApis(string methodName, string? diagnosticId, int overloadCount) + { + var methods = typeof(SampleTypeSpecClient) + .GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Where(method => method.Name == methodName) + .ToArray(); + + Assert.AreEqual(overloadCount, methods.Length); + foreach (var method in methods) + { + Assert.AreEqual(diagnosticId, method.GetCustomAttribute()?.DiagnosticId); + } + } + [Test] public void ModelNameIsCustomized() { diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/Sample-TypeSpec.tsp b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/Sample-TypeSpec.tsp index 7daa3c98cff..d1aada67e56 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/Sample-TypeSpec.tsp +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/Sample-TypeSpec.tsp @@ -4,6 +4,7 @@ import "@typespec/http/streams"; import "@typespec/sse"; import "@typespec/events"; import "@typespec/xml"; +import "@typespec/http-client"; import "@typespec/http-client-csharp"; import "@azure-tools/typespec-client-generator-core"; import "@azure-tools/typespec-azure-core"; @@ -553,6 +554,10 @@ op noContentTypeOverride(info: Wrapper): RoundTripModel; @doc("Return hi in demo2") @get @convenientAPI(true, "csharp") +@TypeSpec.HttpClient.experimental(#{ + emitterScope: "@typespec/http-client-csharp", + diagnosticId: "SAMPLE0001", +}) op helloDemo2(): Thing; @route("/literal") @@ -710,6 +715,11 @@ op EmbeddedParameters(@bodyRoot body: ModelWithEmbeddedNonBodyParameters): void; @route("dynamicModel") @doc("An operation with a dynamic model") @post +@TypeSpec.HttpClient.experimental(#{ + emitterScope: "@typespec/http-client-csharp", + diagnosticId: "SAMPLE0002", + dependsOn: #["SCME0001"], +}) op DynamicModelOperation(@body body: DynamicModel): void; @route("xmlAdvanced") diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.RestClient.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.RestClient.cs index 23707366819..083c3e52f2c 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.RestClient.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.RestClient.cs @@ -413,6 +413,7 @@ internal PipelineMessage CreateEmbeddedParametersRequest(string requiredHeader, internal PipelineMessage CreateDynamicModelOperationRequest(BinaryContent content, RequestOptions options) { +#pragma warning disable SCME0001 // This method depends on experimental functionality. ClientUriBuilder uri = new ClientUriBuilder(); uri.Reset(_endpoint); uri.AppendPath("/dynamicModel", false); @@ -422,6 +423,7 @@ internal PipelineMessage CreateDynamicModelOperationRequest(BinaryContent conten request.Content = content; message.Apply(options); return message; +#pragma warning restore SCME0001 // This method depends on experimental functionality. } internal PipelineMessage CreateGetXmlAdvancedModelRequest(RequestOptions options) 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 b309a6313ad..b3798714ec3 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 @@ -363,6 +363,7 @@ public virtual async Task> NoContentTypeAsync(Wrapp /// The request options, which can override default behaviors of the client pipeline on a per-call basis. /// Service returned a non-success status code. /// The response returned from the service. + [Experimental("SAMPLE0001")] public virtual ClientResult HelloDemo2(RequestOptions options) { using PipelineMessage message = CreateHelloDemo2Request(options); @@ -380,6 +381,7 @@ public virtual ClientResult HelloDemo2(RequestOptions options) /// The request options, which can override default behaviors of the client pipeline on a per-call basis. /// Service returned a non-success status code. /// The response returned from the service. + [Experimental("SAMPLE0001")] public virtual async Task HelloDemo2Async(RequestOptions options) { using PipelineMessage message = CreateHelloDemo2Request(options); @@ -389,6 +391,7 @@ public virtual async Task HelloDemo2Async(RequestOptions options) /// Return hi in demo2. /// The cancellation token that can be used to cancel the operation. /// Service returned a non-success status code. + [Experimental("SAMPLE0001")] public virtual ClientResult HelloDemo2(CancellationToken cancellationToken = default) { ClientResult result = HelloDemo2(cancellationToken.ToRequestOptions()); @@ -398,6 +401,7 @@ public virtual ClientResult HelloDemo2(CancellationToken cancellationToke /// Return hi in demo2. /// The cancellation token that can be used to cancel the operation. /// Service returned a non-success status code. + [Experimental("SAMPLE0001")] public virtual async Task> HelloDemo2Async(CancellationToken cancellationToken = default) { ClientResult result = await HelloDemo2Async(cancellationToken.ToRequestOptions()).ConfigureAwait(false); @@ -1693,12 +1697,15 @@ public virtual async Task EmbeddedParametersAsync(ModelWithEmbedde /// is null. /// Service returned a non-success status code. /// The response returned from the service. + [Experimental("SAMPLE0002")] public virtual ClientResult DynamicModelOperation(BinaryContent content, RequestOptions options = null) { +#pragma warning disable SCME0001 // This method depends on experimental functionality. Argument.AssertNotNull(content, nameof(content)); using PipelineMessage message = CreateDynamicModelOperationRequest(content, options); return ClientResult.FromResponse(Pipeline.ProcessMessage(message, options)); +#pragma warning restore SCME0001 // This method depends on experimental functionality. } /// @@ -1714,12 +1721,15 @@ public virtual ClientResult DynamicModelOperation(BinaryContent content, Request /// is null. /// Service returned a non-success status code. /// The response returned from the service. + [Experimental("SAMPLE0002")] public virtual async Task DynamicModelOperationAsync(BinaryContent content, RequestOptions options = null) { +#pragma warning disable SCME0001 // This method depends on experimental functionality. Argument.AssertNotNull(content, nameof(content)); using PipelineMessage message = CreateDynamicModelOperationRequest(content, options); return ClientResult.FromResponse(await Pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); +#pragma warning restore SCME0001 // This method depends on experimental functionality. } /// An operation with a dynamic model. @@ -1727,11 +1737,14 @@ public virtual async Task DynamicModelOperationAsync(BinaryContent /// The cancellation token that can be used to cancel the operation. /// is null. /// Service returned a non-success status code. + [Experimental("SAMPLE0002")] public virtual ClientResult DynamicModelOperation(DynamicModel body, CancellationToken cancellationToken = default) { +#pragma warning disable SCME0001 // This method depends on experimental functionality. Argument.AssertNotNull(body, nameof(body)); return DynamicModelOperation(body, cancellationToken.ToRequestOptions()); +#pragma warning restore SCME0001 // This method depends on experimental functionality. } /// An operation with a dynamic model. @@ -1739,11 +1752,14 @@ public virtual ClientResult DynamicModelOperation(DynamicModel body, Cancellatio /// The cancellation token that can be used to cancel the operation. /// is null. /// Service returned a non-success status code. + [Experimental("SAMPLE0002")] public virtual async Task DynamicModelOperationAsync(DynamicModel body, CancellationToken cancellationToken = default) { +#pragma warning disable SCME0001 // This method depends on experimental functionality. Argument.AssertNotNull(body, nameof(body)); return await DynamicModelOperationAsync(body, cancellationToken.ToRequestOptions()).ConfigureAwait(false); +#pragma warning restore SCME0001 // This method depends on experimental functionality. } /// 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 c9dfa4e0ea7..bf9a5e01557 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 @@ -10072,7 +10072,11 @@ "generateConvenienceMethod": true, "crossLanguageDefinitionId": "SampleTypeSpec.helloDemo2", "decorators": [], - "namespace": "SampleTypeSpec" + "namespace": "SampleTypeSpec", + "experimental": { + "diagnosticId": "SAMPLE0001", + "dependsOn": [] + } }, "parameters": [ { @@ -14033,7 +14037,13 @@ "generateConvenienceMethod": true, "crossLanguageDefinitionId": "SampleTypeSpec.DynamicModelOperation", "decorators": [], - "namespace": "SampleTypeSpec" + "namespace": "SampleTypeSpec", + "experimental": { + "diagnosticId": "SAMPLE0002", + "dependsOn": [ + "SCME0001" + ] + } }, "parameters": [ { From 1ca2a909acca0c2668059a256cd2fcb51295c6cc Mon Sep 17 00:00:00 2001 From: jolov Date: Tue, 22 Sep 2026 16:37:37 -0700 Subject: [PATCH 3/8] fix(http-client-csharp): suppress experimental dependencies in signatures Use member-scoped suppressions for generated signatures and bodies, preserve existing suppressions, and add compilation coverage for experimental type references. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../http-client-csharp/.tspd/docs/usage.md | 11 ++- .../src/Utilities/ExperimentalApiHelpers.cs | 13 ++- .../ScmMethodProviderCollectionTests.cs | 84 +++++++++++++++++-- ...talDependenciesCoverSignaturesAndBodies.cs | 37 ++++++++ .../SampleTypeSpecClient.RestClient.cs | 4 +- .../src/Generated/SampleTypeSpecClient.cs | 16 ++-- packages/http-client-csharp/readme.md | 11 ++- 7 files changed, 147 insertions(+), 29 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ExperimentalDependenciesCoverSignaturesAndBodies.cs diff --git a/packages/http-client-csharp/.tspd/docs/usage.md b/packages/http-client-csharp/.tspd/docs/usage.md index 83c0031c6ca..5213dbabe43 100644 --- a/packages/http-client-csharp/.tspd/docs/usage.md +++ b/packages/http-client-csharp/.tspd/docs/usage.md @@ -24,9 +24,14 @@ op bar(): void; ``` The C# emitter adds `[Experimental("C")]` to the generated synchronous and asynchronous -protocol and convenience methods. It disables dependency diagnostics `A` and `B` inside -the generated method and request-helper bodies and restores them at the end of each body. -These suppressions do not affect callers. +protocol and convenience methods. It disables dependency diagnostics `A` and `B` before +each generated method or request-helper declaration and restores them after that method. +This covers experimental types in parameter and return types, including generic arguments, +as well as references in the implementation. These suppressions do not affect neighboring +members or callers. + +Dependencies identify diagnostics rather than individual types: one diagnostic can apply +to multiple types or members, including those defined in external libraries. Both metadata fields are optional. Without `diagnosticId`, no public experimental attribute is added; without `dependsOn`, no dependency suppressions are added. Emitter scopes apply to diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Utilities/ExperimentalApiHelpers.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Utilities/ExperimentalApiHelpers.cs index 47179878447..9f45a6283a7 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Utilities/ExperimentalApiHelpers.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Utilities/ExperimentalApiHelpers.cs @@ -31,15 +31,12 @@ public static void AddDependencySuppressions(MethodProvider method, InputOperati throw new ArgumentException("Experimental dependency diagnostic identifiers cannot be empty or whitespace.", nameof(operation)); } - var suppressions = dependencies.Distinct(StringComparer.Ordinal) - .Select(id => new SuppressionStatement(null, Literal(id), "This method depends on experimental functionality.")) - .ToArray(); - method.Update(bodyStatements: new MethodBodyStatements( + method.Update(suppressions: [ - .. suppressions.Select(s => s.DisableStatement), - method.BodyStatements!, - .. suppressions.Select(s => s.RestoreStatement) - ])); + .. method.Suppressions, + .. dependencies.Distinct(StringComparer.Ordinal) + .Select(id => new SuppressionStatement(null, Literal(id), "This method depends on experimental functionality.")) + ]); } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs index 9ab7524f727..35c8351e05e 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.TypeSpec.Generator.ClientModel.Providers; +using Microsoft.TypeSpec.Generator.ClientModel.Utilities; using Microsoft.TypeSpec.Generator.EmitterRpc; using Microsoft.TypeSpec.Generator.Expressions; using Microsoft.TypeSpec.Generator.Input; @@ -82,8 +83,11 @@ public void ExperimentalOperationGeneratesScopedDiagnostics(string? diagnosticId var restores = directives.Where(d => d.DisableOrRestoreKeyword.IsKind(SyntaxKind.RestoreKeyword)).ToArray(); CollectionAssert.AreEqual(expectedDependencies, disables.Select(d => d.ErrorCodes.Single().ToString())); CollectionAssert.AreEqual(expectedDependencies, restores.Select(d => d.ErrorCodes.Single().ToString())); - Assert.IsTrue(disables.All(d => d.SpanStart > code.IndexOf('{'))); - Assert.IsTrue(restores.All(d => d.Span.End < code.LastIndexOf('}'))); + var declarationStart = code.IndexOf( + method.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public) ? "public " : "internal ", + StringComparison.Ordinal); + Assert.IsTrue(disables.All(d => d.SpanStart < declarationStart)); + Assert.IsTrue(restores.All(d => d.SpanStart > code.LastIndexOf('}'))); } } @@ -148,12 +152,82 @@ await MockHelpers.LoadMockGeneratorAsync( Assert.IsTrue(method.IsPartialMethod); var attribute = method.Signature.Attributes.Single(a => a.Type.Equals(typeof(ExperimentalAttribute))); Assert.AreEqual("C", ((LiteralExpression)((ScopedApi)attribute.Arguments.Single()).Original).Literal); - var body = (MethodBodyStatements)method.BodyStatements!; - Assert.AreEqual(2, body.Statements.OfType().Count()); - Assert.AreEqual(2, body.Statements.OfType().Count()); + Assert.AreEqual(2, method.Suppressions.Count); } } + [TestCase("plain", false, true, null)] + [TestCase("generic", false, true, null)] + [TestCase("array", false, true, null)] + [TestCase("nested", false, true, null)] + [TestCase("plain", true, true, null)] + [TestCase("plain", false, false, null)] + [TestCase("plain", false, true, "C")] + public void ExperimentalDependenciesCoverSignaturesAndBodies(string shape, bool expressionBody, bool suppressBodyDependency, string? publicDiagnosticId) + { + var operation = InputFactory.Operation("UseDependencies"); + operation.Update(experimental: new InputExperimentalDetails(publicDiagnosticId, suppressBodyDependency ? ["A", "B"] : ["A"])); + var serviceMethod = InputFactory.BasicServiceMethod("UseDependencies", operation); + var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]); + MockHelpers.LoadMockGenerator(clients: () => [inputClient]); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + + var tree = CSharpSyntaxTree.ParseText(Helpers.GetExpectedFromFile()); + var references = AppDomain.CurrentDomain.GetAssemblies() + .Where(assembly => !assembly.IsDynamic && !string.IsNullOrEmpty(assembly.Location)) + .Select(assembly => MetadataReference.CreateFromFile(assembly.Location)); + var compilation = CSharpCompilation.Create( + "ExperimentalDependencies", + [tree], + references, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + var dependency = compilation.GetTypeByMetadataName("Sample.SignatureDependency")!; + ITypeSymbol signatureSymbol = shape switch + { + "plain" => dependency, + "generic" => compilation.GetTypeByMetadataName("System.Collections.Generic.List`1")!.Construct(dependency), + "array" => compilation.CreateArrayTypeSymbol(dependency), + "nested" => compilation.GetTypeByMetadataName("System.Collections.Generic.List`1")!.Construct(compilation.CreateArrayTypeSymbol(dependency)), + _ => throw new ArgumentOutOfRangeException(nameof(shape)) + }; + var signatureType = signatureSymbol.GetCSharpType(); + var bodyType = compilation.GetTypeByMetadataName("Sample.BodyDependency")!.GetCSharpType(); + var parameter = new ParameterProvider("value", $"The value.", signatureType); + var attribute = ExperimentalApiHelpers.BuildAttribute(operation); + var signature = new MethodSignature( + "UseDependencies", null, MethodSignatureModifiers.Public, signatureType, null, [parameter], + Attributes: attribute is null ? [] : [attribute]); + var existingSuppression = new SuppressionStatement(null, Snippet.Literal("CS0168"), "Existing suppression."); + var method = expressionBody + ? new MethodProvider(signature, (ValueExpression)parameter, client, suppressions: [existingSuppression]) + : new MethodProvider(signature, new MethodBodyStatements( + [new ExpressionStatement(Snippet.New.Instance(bodyType)), Snippet.Return(parameter)]), client, suppressions: [existingSuppression]); + ExperimentalApiHelpers.AddDependencySuppressions(method, operation); + Assert.Contains(existingSuppression, method.Suppressions.ToArray()); + + using var writer = new CodeWriter(); + writer.WriteMethod(method); + var root = tree.GetRoot(); + var clientDeclaration = root.DescendantNodes().OfType().Single(c => c.Identifier.ValueText == "TestClient"); + var generatedMethod = SyntaxFactory.ParseMemberDeclaration(writer.ToString(false))!; + var updatedRoot = root.ReplaceNode(clientDeclaration, clientDeclaration.AddMembers(generatedMethod)); + var updatedTree = tree.WithRootAndOptions(updatedRoot, tree.Options); + compilation = compilation.ReplaceSyntaxTree(tree, updatedTree); + + var emittedMethod = updatedTree.GetRoot().DescendantNodes().OfType() + .Single(m => m.Identifier.ValueText == "UseDependencies"); + var diagnostics = compilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).ToArray(); + var methodDiagnostics = diagnostics.Where(d => emittedMethod.Span.Contains(d.Location.SourceSpan)).ToArray(); + CollectionAssert.AreEqual( + suppressBodyDependency || expressionBody ? Array.Empty() : ["B"], + methodDiagnostics.Select(d => d.Id), + string.Join(Environment.NewLine, methodDiagnostics.Select(d => d.ToString()))); + var outsideDiagnostics = diagnostics.Where(d => !emittedMethod.Span.Contains(d.Location.SourceSpan)).ToArray(); + CollectionAssert.AreEquivalent( + publicDiagnosticId is null ? new[] { "A", "B" } : ["A", "B", "C"], + outsideDiagnostics.Select(d => d.Id).Distinct()); + } + [Test] public void JsonLinesRequestGeneratesAsyncStreamingConvenienceMethod() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ExperimentalDependenciesCoverSignaturesAndBodies.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ExperimentalDependenciesCoverSignaturesAndBodies.cs new file mode 100644 index 00000000000..11c482ea116 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ExperimentalDependenciesCoverSignaturesAndBodies.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; + +namespace Sample +{ + [Experimental("A")] + public class SignatureDependency + { + } + + [Experimental("B")] + public class BodyDependency + { + } + + public class TestClient + { + } + + public class Consumer + { +#pragma warning disable A, B + public void Call(TestClient client) + { + client.UseDependencies(null); + } +#pragma warning restore A, B + + public SignatureDependency Unsuppressed(SignatureDependency value) + { + new BodyDependency(); + return value; + } + } +} diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.RestClient.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.RestClient.cs index 083c3e52f2c..a737e1e8b31 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.RestClient.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.RestClient.cs @@ -411,9 +411,9 @@ internal PipelineMessage CreateEmbeddedParametersRequest(string requiredHeader, return message; } +#pragma warning disable SCME0001 // This method depends on experimental functionality. internal PipelineMessage CreateDynamicModelOperationRequest(BinaryContent content, RequestOptions options) { -#pragma warning disable SCME0001 // This method depends on experimental functionality. ClientUriBuilder uri = new ClientUriBuilder(); uri.Reset(_endpoint); uri.AppendPath("/dynamicModel", false); @@ -423,8 +423,8 @@ internal PipelineMessage CreateDynamicModelOperationRequest(BinaryContent conten request.Content = content; message.Apply(options); return message; -#pragma warning restore SCME0001 // This method depends on experimental functionality. } +#pragma warning restore SCME0001 // This method depends on experimental functionality. internal PipelineMessage CreateGetXmlAdvancedModelRequest(RequestOptions options) { 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 b3798714ec3..9d60265288c 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 @@ -1697,16 +1697,16 @@ public virtual async Task EmbeddedParametersAsync(ModelWithEmbedde /// is null. /// Service returned a non-success status code. /// The response returned from the service. +#pragma warning disable SCME0001 // This method depends on experimental functionality. [Experimental("SAMPLE0002")] public virtual ClientResult DynamicModelOperation(BinaryContent content, RequestOptions options = null) { -#pragma warning disable SCME0001 // This method depends on experimental functionality. Argument.AssertNotNull(content, nameof(content)); using PipelineMessage message = CreateDynamicModelOperationRequest(content, options); return ClientResult.FromResponse(Pipeline.ProcessMessage(message, options)); -#pragma warning restore SCME0001 // This method depends on experimental functionality. } +#pragma warning restore SCME0001 // This method depends on experimental functionality. /// /// [Protocol Method] An operation with a dynamic model @@ -1721,46 +1721,46 @@ public virtual ClientResult DynamicModelOperation(BinaryContent content, Request /// is null. /// Service returned a non-success status code. /// The response returned from the service. +#pragma warning disable SCME0001 // This method depends on experimental functionality. [Experimental("SAMPLE0002")] public virtual async Task DynamicModelOperationAsync(BinaryContent content, RequestOptions options = null) { -#pragma warning disable SCME0001 // This method depends on experimental functionality. Argument.AssertNotNull(content, nameof(content)); using PipelineMessage message = CreateDynamicModelOperationRequest(content, options); return ClientResult.FromResponse(await Pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); -#pragma warning restore SCME0001 // This method depends on experimental functionality. } +#pragma warning restore SCME0001 // This method depends on experimental functionality. /// An operation with a dynamic model. /// /// The cancellation token that can be used to cancel the operation. /// is null. /// Service returned a non-success status code. +#pragma warning disable SCME0001 // This method depends on experimental functionality. [Experimental("SAMPLE0002")] public virtual ClientResult DynamicModelOperation(DynamicModel body, CancellationToken cancellationToken = default) { -#pragma warning disable SCME0001 // This method depends on experimental functionality. Argument.AssertNotNull(body, nameof(body)); return DynamicModelOperation(body, cancellationToken.ToRequestOptions()); -#pragma warning restore SCME0001 // This method depends on experimental functionality. } +#pragma warning restore SCME0001 // This method depends on experimental functionality. /// An operation with a dynamic model. /// /// The cancellation token that can be used to cancel the operation. /// is null. /// Service returned a non-success status code. +#pragma warning disable SCME0001 // This method depends on experimental functionality. [Experimental("SAMPLE0002")] public virtual async Task DynamicModelOperationAsync(DynamicModel body, CancellationToken cancellationToken = default) { -#pragma warning disable SCME0001 // This method depends on experimental functionality. Argument.AssertNotNull(body, nameof(body)); return await DynamicModelOperationAsync(body, cancellationToken.ToRequestOptions()).ConfigureAwait(false); -#pragma warning restore SCME0001 // This method depends on experimental functionality. } +#pragma warning restore SCME0001 // This method depends on experimental functionality. /// /// [Protocol Method] Get an advanced XML model with various property types diff --git a/packages/http-client-csharp/readme.md b/packages/http-client-csharp/readme.md index f84a664d4b3..80db5263884 100644 --- a/packages/http-client-csharp/readme.md +++ b/packages/http-client-csharp/readme.md @@ -36,9 +36,14 @@ op bar(): void; ``` The C# emitter adds `[Experimental("C")]` to the generated synchronous and asynchronous -protocol and convenience methods. It disables dependency diagnostics `A` and `B` inside -the generated method and request-helper bodies and restores them at the end of each body. -These suppressions do not affect callers. +protocol and convenience methods. It disables dependency diagnostics `A` and `B` before +each generated method or request-helper declaration and restores them after that method. +This covers experimental types in parameter and return types, including generic arguments, +as well as references in the implementation. These suppressions do not affect neighboring +members or callers. + +Dependencies identify diagnostics rather than individual types: one diagnostic can apply +to multiple types or members, including those defined in external libraries. Both metadata fields are optional. Without `diagnosticId`, no public experimental attribute is added; without `dependsOn`, no dependency suppressions are added. Emitter scopes apply to From e0682c4da2af27da88695037569316bbee337edc Mon Sep 17 00:00:00 2001 From: jolov Date: Tue, 22 Sep 2026 17:49:41 -0700 Subject: [PATCH 4/8] feat(http-client-csharp): support experimental types and members Generalize lifecycle metadata and attribute emission to models, properties, enums and values, and namespace/interface clients. Preserve compilation of generated references and helpers, diagnose unrepresentable targets, and add annotated samples and regression coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../http-client-csharp/.tspd/docs/usage.md | 57 +- .../emitter/src/lib/client-converter.ts | 2 + .../emitter/src/lib/experimental.ts | 37 + .../http-client-csharp/emitter/src/lib/lib.ts | 7 + .../emitter/src/lib/operation-converter.ts | 17 +- .../emitter/src/lib/type-converter.ts | 18 + .../emitter/src/type/input-operation.ts | 7 +- .../emitter/src/type/input-type.ts | 6 + .../emitter/test/Unit/decorator-list.test.ts | 88 +- .../src/Providers/ClientProvider.cs | 6 + .../Providers/CollectionResultDefinition.cs | 6 + .../src/Providers/RestClientProvider.cs | 4 +- .../src/Providers/ScmModelFactoryProvider.cs | 4 +- .../src/Providers/ScmModelProvider.cs | 3 +- .../src/Utilities/ExperimentalApiHelpers.cs | 42 - .../ClientProviders/ClientProviderTests.cs | 23 + .../CollectionResultDefinitionTests.cs | 22 + .../ScmModelFactoryProviderTests.cs | 13 + .../ScmMethodProviderCollectionTests.cs | 20 +- .../ScmModelProvider/ScmModelProviderTests.cs | 19 + .../src/InputTypes/InputClient.cs | 1 + .../src/InputTypes/InputProperty.cs | 1 + .../src/InputTypes/InputType.cs | 1 + .../Serialization/InputClientConverter.cs | 3 + .../Serialization/InputEnumTypeConverter.cs | 3 + .../InputEnumTypeValueConverter.cs | 3 + .../InputModelPropertyConverter.cs | 3 + .../Serialization/InputModelTypeConverter.cs | 3 + .../test/TypeSpecInputConverterTests.cs | 53 + .../src/Primitives/EnumTypeMember.cs | 2 + .../src/Providers/ApiVersionEnumProvider.cs | 20 +- .../src/Providers/ExtensibleEnumProvider.cs | 5 +- .../src/Providers/FixedEnumProvider.cs | 10 +- .../src/Providers/ModelFactoryProvider.cs | 6 +- .../src/Providers/PropertyProvider.cs | 2 +- .../src/Providers/TypeProvider.cs | 14 +- .../src/Utilities/ExperimentalApiHelpers.cs | 221 + .../test/Providers/TypeProviderTests.cs | 84 + .../test/common/InputFactory.cs | 18 + .../Local.Tests/CustomizationTests.cs | 27 + .../Local/Sample-TypeSpec/Sample-TypeSpec.tsp | 73 + ...entalSamplesGetAllAsyncCollectionResult.cs | 56 + ...alSamplesGetAllAsyncCollectionResultOfT.cs | 68 + ...perimentalSamplesGetAllCollectionResult.cs | 55 + ...imentalSamplesGetAllCollectionResultOfT.cs | 63 + .../ExperimentalSamples.RestClient.cs | 47 + .../src/Generated/ExperimentalSamples.cs | 144 + .../Models/LifecycleModel.Serialization.cs | 172 + .../src/Generated/Models/LifecycleModel.cs | 51 + .../PagePreviewDetails.Serialization.cs | 163 + .../Generated/Models/PagePreviewDetails.cs | 41 + .../Models/PreviewChoice.Serialization.cs | 40 + .../src/Generated/Models/PreviewChoice.cs | 26 + .../Models/PreviewDetails.Serialization.cs | 146 + .../src/Generated/Models/PreviewDetails.cs | 44 + .../Models/PreviewExtensibleChoice.cs | 75 + .../Generated/Models/SampleTypeSpecContext.cs | 5 + .../SampleTypeSpecClient.RestClient.cs | 20 +- .../src/Generated/SampleTypeSpecClient.cs | 55 +- .../Generated/SampleTypeSpecClientOptions.cs | 3 + .../Generated/SampleTypeSpecModelFactory.cs | 26 + .../Local/Sample-TypeSpec/tspCodeModel.json | 3886 ++++++++++------- packages/http-client-csharp/readme.md | 57 +- 63 files changed, 4516 insertions(+), 1681 deletions(-) create mode 100644 packages/http-client-csharp/emitter/src/lib/experimental.ts delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Utilities/ExperimentalApiHelpers.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/ExperimentalApiHelpers.cs create mode 100644 packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/ExperimentalSamplesGetAllAsyncCollectionResult.cs create mode 100644 packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/ExperimentalSamplesGetAllAsyncCollectionResultOfT.cs create mode 100644 packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/ExperimentalSamplesGetAllCollectionResult.cs create mode 100644 packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/ExperimentalSamplesGetAllCollectionResultOfT.cs create mode 100644 packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/ExperimentalSamples.RestClient.cs create mode 100644 packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/ExperimentalSamples.cs create mode 100644 packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/LifecycleModel.Serialization.cs create mode 100644 packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/LifecycleModel.cs create mode 100644 packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PagePreviewDetails.Serialization.cs create mode 100644 packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PagePreviewDetails.cs create mode 100644 packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PreviewChoice.Serialization.cs create mode 100644 packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PreviewChoice.cs create mode 100644 packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PreviewDetails.Serialization.cs create mode 100644 packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PreviewDetails.cs create mode 100644 packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PreviewExtensibleChoice.cs diff --git a/packages/http-client-csharp/.tspd/docs/usage.md b/packages/http-client-csharp/.tspd/docs/usage.md index 5213dbabe43..5a598048e1e 100644 --- a/packages/http-client-csharp/.tspd/docs/usage.md +++ b/packages/http-client-csharp/.tspd/docs/usage.md @@ -7,10 +7,10 @@ For detailed instructions on how to customize the generated C# code, see the [Customization Guide](https://github.com/microsoft/typespec/blob/main/packages/http-client-csharp/.tspd/docs/customization.md). -### Experimental operations +### Experimental types and members -Use `TypeSpec.HttpClient.@experimental` to assign a public diagnostic ID to an operation and -identify experiments used by its implementation: +Use `TypeSpec.HttpClient.@experimental` to assign a public diagnostic ID to a generated +type or member and identify experiments used by its implementation: ```typespec import "@typespec/http-client"; @@ -23,20 +23,51 @@ import "@typespec/http-client"; op bar(): void; ``` -The C# emitter adds `[Experimental("C")]` to the generated synchronous and asynchronous -protocol and convenience methods. It disables dependency diagnostics `A` and `B` before -each generated method or request-helper declaration and restores them after that method. -This covers experimental types in parameter and return types, including generic arguments, -as well as references in the implementation. These suppressions do not affect neighboring -members or callers. +The C# emitter adds `[Experimental("C")]` to the corresponding generated declaration: + +| TypeSpec target | Generated C# target | +| ----------------------------------------------------- | ------------------------------------------------------------- | +| Model | Model class or struct | +| Model property | Property | +| Enum or union emitted as an enum | Enum or extensible-enum struct | +| Enum member or union variant emitted as an enum value | Enum field or extensible-enum property | +| Operation | Synchronous and asynchronous protocol and convenience methods | +| Namespace or interface emitted as a client | Client class | + +For example, models and their properties can be separate experiments: + +```typespec +@TypeSpec.HttpClient.experimental(#{ diagnosticId: "MODEL001" }) +model Preview { + @TypeSpec.HttpClient.experimental(#{ diagnosticId: "PROPERTY001" }) + value?: string; +} +``` + +Model factory methods expose the model's diagnostic. Partial serialization declarations +do not repeat the model attribute. Generated code suppresses diagnostics when referring +to source-annotated experimental types and members, so serialization, factories, and client +implementations can compile without changing the experimental status of other public APIs. + +For operation dependencies, suppressions surround each method declaration and body. +For type/member dependencies and generated references to experimental declarations, +suppressions are scoped to the generated file. Both include parameter and return types, +generic arguments, and implementation references; neither suppresses diagnostics in +consumer code. Dependencies identify diagnostics rather than individual types: one diagnostic can apply -to multiple types or members, including those defined in external libraries. +to multiple types or members, including those defined in external libraries. External +experiments still require explicit `dependsOn` entries; they are not discovered by reflection. Both metadata fields are optional. Without `diagnosticId`, no public experimental attribute -is added; without `dependsOn`, no dependency suppressions are added. Emitter scopes apply to -both fields. C# currently consumes this metadata on operations, not on models or properties. +is added. Without `dependsOn`, no additional dependency diagnostics are requested; generated +references to source-annotated experiments are still handled. Emitter scopes apply to both fields. + +Some TypeSpec declarations have no corresponding C# declaration, such as scalars or unions +mapped to built-in C# types. C# also does not allow `ExperimentalAttribute` on parameters. +The emitter reports `experimental-target-not-supported` for these annotations instead of +silently dropping them or assigning their diagnostic to an unrelated API. Graduation is an explicit source change: dependencies becoming generally available, or removing entries from `dependsOn`, does not remove `[Experimental("C")]`. Remove the -operation's `@experimental` decorator when the public API is ready to graduate. +declaration's `@experimental` decorator when the public API is ready to graduate. diff --git a/packages/http-client-csharp/emitter/src/lib/client-converter.ts b/packages/http-client-csharp/emitter/src/lib/client-converter.ts index 69a49c614d6..40a1f98b502 100644 --- a/packages/http-client-csharp/emitter/src/lib/client-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/client-converter.ts @@ -19,6 +19,7 @@ import type { InputParameter, InputType, } from "../type/input-type.js"; +import { getExperimentalDetails } from "./experimental.js"; import { createDiagnostic } from "./lib.js"; import { fromMethodParameter, @@ -101,6 +102,7 @@ function fromSdkClient( parent: undefined, children: undefined, isMultiServiceClient: isMultiService, + experimental: diagnostics.pipe(getExperimentalDetails(sdkContext, client.__raw.type)), }; sdkContext.__typeCache.updateSdkClientReferences(client, inputClient); diff --git a/packages/http-client-csharp/emitter/src/lib/experimental.ts b/packages/http-client-csharp/emitter/src/lib/experimental.ts new file mode 100644 index 00000000000..5bf6d60d572 --- /dev/null +++ b/packages/http-client-csharp/emitter/src/lib/experimental.ts @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import type { SdkContext } from "@azure-tools/typespec-client-generator-core"; +import type { Diagnostic, Type } from "@typespec/compiler"; +import { createDiagnosticCollector } from "@typespec/compiler"; +import { $ } from "@typespec/compiler/typekit"; +import "@typespec/http-client/typekit"; +import type { InputExperimentalDetails } from "../type/input-type.js"; +import { createDiagnostic } from "./lib.js"; + +export function getExperimentalDetails( + context: SdkContext, + target: Type | undefined, + hasGeneratedDeclaration = true, +): [InputExperimentalDetails | undefined, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + if (!target) { + return diagnostics.wrap(undefined); + } + + const lifecycle = diagnostics.pipe( + $(context.program).client.getFeatureLifecycleDetails.withDiagnostics(target, { + emitterName: "@typespec/http-client-csharp", + }), + ); + if (lifecycle && !hasGeneratedDeclaration) { + diagnostics.add(createDiagnostic({ code: "experimental-target-not-supported", target })); + return diagnostics.wrap(undefined); + } + + return diagnostics.wrap( + lifecycle + ? { diagnosticId: lifecycle.diagnosticId, dependsOn: [...lifecycle.dependsOn] } + : undefined, + ); +} diff --git a/packages/http-client-csharp/emitter/src/lib/lib.ts b/packages/http-client-csharp/emitter/src/lib/lib.ts index b82bab80f0b..38161428c87 100644 --- a/packages/http-client-csharp/emitter/src/lib/lib.ts +++ b/packages/http-client-csharp/emitter/src/lib/lib.ts @@ -10,6 +10,13 @@ export type DiagnosticMessagesMap = { }; const diags: { [code: string]: DiagnosticDefinition } = { + "experimental-target-not-supported": { + severity: "error", + messages: { + default: + "This experimental declaration does not produce a C# type or member that supports ExperimentalAttribute. Apply @experimental to an emitted model, enum, property, enum member, operation, or client instead.", + }, + }, "no-apiVersion": { severity: "error", messages: { diff --git a/packages/http-client-csharp/emitter/src/lib/operation-converter.ts b/packages/http-client-csharp/emitter/src/lib/operation-converter.ts index 6945baac2b9..3a92236db3a 100644 --- a/packages/http-client-csharp/emitter/src/lib/operation-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/operation-converter.ts @@ -39,10 +39,8 @@ import { isErrorModel, NoTarget, } from "@typespec/compiler"; -import { $ } from "@typespec/compiler/typekit"; import { unsafe_getEventDefinitions } from "@typespec/events/experimental"; import type { HttpStatusCodeRange } from "@typespec/http"; -import "@typespec/http-client/typekit"; import { getResourceOperation } from "@typespec/rest"; import { isTerminalEvent } from "@typespec/sse"; import type { CSharpEmitterContext } from "../sdk-context.js"; @@ -80,6 +78,7 @@ import { parseHttpRequestMethod } from "../type/request-method.js"; import { ResponseLocation } from "../type/response-location.js"; import { getExternalDocs, getOperationId } from "./decorators.js"; import { fromSdkHttpExamples } from "./example-converter.js"; +import { getExperimentalDetails } from "./experimental.js"; import { createDiagnostic } from "./lib.js"; import { fromSdkType } from "./type-converter.js"; import { getClientNamespaceString, isReadOnly } from "./utils.js"; @@ -219,13 +218,6 @@ export function fromSdkServiceMethodOperation( generateConvenience = false; } - const lifecycle = diagnostics.pipe( - $(sdkContext.program).client.getFeatureLifecycleDetails.withDiagnostics( - method.operation.__raw.operation, - { emitterName: "@typespec/http-client-csharp" }, - ), - ); - operation = { name: method.name, isExactName: method.isExactName, @@ -261,9 +253,9 @@ export function fromSdkServiceMethodOperation( namespace: method.__raw?.namespace ? getClientNamespace(sdkContext, method.__raw.namespace) : undefined, - experimental: lifecycle - ? { diagnosticId: lifecycle.diagnosticId, dependsOn: [...lifecycle.dependsOn] } - : undefined, + experimental: diagnostics.pipe( + getExperimentalDetails(sdkContext, method.operation.__raw.operation), + ), }; sdkContext.__typeCache.updateSdkOperationReferences(method.operation, operation); @@ -722,6 +714,7 @@ export function fromMethodParameter( decorators: p.decorators, paramAlias, isExactName: p.isExactName, + experimental: diagnostics.pipe(getExperimentalDetails(sdkContext, p.__raw, false)), }; sdkContext.__typeCache.updateSdkMethodParameterReferences(p, retVar); diff --git a/packages/http-client-csharp/emitter/src/lib/type-converter.ts b/packages/http-client-csharp/emitter/src/lib/type-converter.ts index 0d98baeb7a8..f304689d93d 100644 --- a/packages/http-client-csharp/emitter/src/lib/type-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/type-converter.ts @@ -43,6 +43,7 @@ import type { InputType, InputUnionType, } from "../type/input-type.js"; +import { getExperimentalDetails } from "./experimental.js"; import { createDiagnostic } from "./lib.js"; import { isReadOnly } from "./utils.js"; @@ -189,6 +190,21 @@ export function fromSdkType( break; } + retVar.experimental = diagnostics.pipe( + getExperimentalDetails( + sdkContext, + sdkType.__raw, + !retVar.external && + ((retVar.kind === "model" && !retVar.isFileType) || + retVar.kind === "enum" || + retVar.kind === "enumvalue"), + ), + ); + if (sdkType.__raw?.kind === "Union" && retVar.kind !== "enum") { + for (const variant of sdkType.__raw.variants.values()) { + diagnostics.pipe(getExperimentalDetails(sdkContext, variant, false)); + } + } sdkContext.__typeCache.updateSdkTypeReferences(sdkType, retVar); // we have to cast to any because TypeScript's type narrowing does not automatically infer the return type for conditional types return diagnostics.wrap(retVar as any); @@ -300,6 +316,7 @@ function fromSdkModelProperty( isHttpMetadata: isHttpMetadata(sdkContext, sdkProperty), encode: sdkProperty.encode, isExactName: sdkProperty.isExactName, + experimental: diagnostics.pipe(getExperimentalDetails(sdkContext, sdkProperty.__raw)), } as InputModelProperty; if (sdkProperty.serializationOptions?.multipart?.isFilePart === true) { @@ -520,6 +537,7 @@ function createEnumValueType( doc: sdkType.doc, decorators: sdkType.decorators, isExactName: sdkType.isExactName, + experimental: diagnostics.pipe(getExperimentalDetails(sdkContext, sdkType.__raw)), }); } diff --git a/packages/http-client-csharp/emitter/src/type/input-operation.ts b/packages/http-client-csharp/emitter/src/type/input-operation.ts index 50632ee0576..fba46b91ed2 100644 --- a/packages/http-client-csharp/emitter/src/type/input-operation.ts +++ b/packages/http-client-csharp/emitter/src/type/input-operation.ts @@ -3,7 +3,7 @@ import type { DecoratorInfo } from "@azure-tools/typespec-client-generator-core"; import type { InputHttpOperationExample } from "./input-examples.js"; -import type { InputHttpParameter } from "./input-type.js"; +import type { InputExperimentalDetails, InputHttpParameter } from "./input-type.js"; import type { OperationResponse } from "./operation-response.js"; import type { RequestMethod } from "./request-method.js"; @@ -31,8 +31,3 @@ export interface InputOperation { namespace?: string; experimental?: InputExperimentalDetails; } - -export interface InputExperimentalDetails { - diagnosticId?: string; - dependsOn: string[]; -} diff --git a/packages/http-client-csharp/emitter/src/type/input-type.ts b/packages/http-client-csharp/emitter/src/type/input-type.ts index 73e2ade590a..691e831176a 100644 --- a/packages/http-client-csharp/emitter/src/type/input-type.ts +++ b/packages/http-client-csharp/emitter/src/type/input-type.ts @@ -59,6 +59,12 @@ export interface InputNamespace extends DecoratedType { interface DecoratedType { decorators?: DecoratorInfo[]; + experimental?: InputExperimentalDetails; +} + +export interface InputExperimentalDetails { + diagnosticId?: string; + dependsOn: string[]; } interface InputTypeBase extends DecoratedType { diff --git a/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts b/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts index a07a6881984..f37e3edd037 100644 --- a/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts @@ -24,11 +24,97 @@ describe("Test emitting decorator list", () => { runner = await createEmitterTestHost(); }); - describe("experimental operations", () => { + describe("experimental declarations", () => { beforeEach(async () => { await runner.addTypeSpecLibrary(HttpClientTestLibrary); }); + const declarations = [ + { + name: "model", + code: `DECORATOR model Payload { value: string; } op read(): Payload;`, + select: (root: ReturnType[0]) => root.models[0], + }, + { + name: "model property", + code: `model Payload { DECORATOR value: string; } op read(): Payload;`, + select: (root: ReturnType[0]) => root.models[0].properties[0], + }, + { + name: "enum", + code: `DECORATOR enum Choice { One, Two } op read(): Choice;`, + select: (root: ReturnType[0]) => root.enums[0], + }, + { + name: "enum member", + code: `enum Choice { DECORATOR One, Two } op read(): Choice;`, + select: (root: ReturnType[0]) => root.enums[0].values[0], + }, + { + name: "extensible enum", + code: `DECORATOR union Choice { string, One: "one", Two: "two" } op read(): Choice;`, + select: (root: ReturnType[0]) => root.enums[0], + }, + { + name: "union variant", + code: `union Choice { string, DECORATOR One: "one", Two: "two" } op read(): Choice;`, + select: (root: ReturnType[0]) => root.enums[0].values[0], + }, + { + name: "interface client", + code: `DECORATOR interface Group { @route("/read") read(): void; }`, + select: (root: ReturnType[0]) => root.clients[0].children![0], + }, + { + name: "namespace client", + code: `DECORATOR namespace Group { @route("/read") op read(): void; }`, + select: (root: ReturnType[0]) => root.clients[0].children![0], + }, + ]; + + describe.each(declarations)("$name", ({ code, select }) => { + it.each([ + { scope: "@typespec/http-client-csharp", applies: true }, + { scope: "other-emitter", applies: false }, + { scope: "!other-emitter", applies: true }, + ])("preserves scoped metadata for $scope", async ({ scope, applies }) => { + const decorator = `@TypeSpec.HttpClient.experimental(#{ + emitterScope: "${scope}", diagnosticId: "TEST001", dependsOn: #["DEP001", "DEP002"] + })`; + const program = await typeSpecCompile(code.replace("DECORATOR", decorator), runner, { + IsHttpClientNeeded: true, + }); + const sdkContext = await createCSharpSdkContext(createEmitterContext(program)); + expectDiagnosticEmpty(sdkContext.diagnostics); + const [root, diagnostics] = createModel(sdkContext); + expectDiagnosticEmpty(diagnostics); + deepStrictEqual( + select(root).experimental, + applies ? { diagnosticId: "TEST001", dependsOn: ["DEP001", "DEP002"] } : undefined, + ); + }); + }); + + it.each([ + `@TypeSpec.HttpClient.experimental(#{ diagnosticId: "SCALAR001" }) + scalar CustomString extends string; op read(): CustomString;`, + `op read(@TypeSpec.HttpClient.experimental(#{ diagnosticId: "PARAM001" }) + @query value: string): void;`, + `@TypeSpec.HttpClient.experimental(#{ diagnosticId: "UNION001" }) + union Choice { text: string, count: int32 } op read(): Choice;`, + `union Choice { + @TypeSpec.HttpClient.experimental(#{ diagnosticId: "VARIANT001" }) + text: string, count: int32 + } op read(): Choice;`, + ])("reports annotations with no supported C# declaration", async (code) => { + const program = await typeSpecCompile(code, runner, { IsHttpClientNeeded: true }); + const sdkContext = await createCSharpSdkContext(createEmitterContext(program)); + const [, diagnostics] = createModel(sdkContext); + expectDiagnostics(diagnostics, { + code: "@typespec/http-client-csharp/experimental-target-not-supported", + }); + }); + it.each([ { scope: undefined, applies: true }, { scope: "@typespec/http-client-csharp", applies: true }, 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 d35fbaa8cf8..57fe8fb2cd0 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 @@ -46,6 +46,12 @@ private record ApiVersionFields(FieldProvider Field, PropertyProvider? Correspon private readonly FormattableString _publicCtorDescription; private readonly InputClient _inputClient; internal InputClient InputClient => _inputClient; + protected override IReadOnlyList BuildAttributes() + => ExperimentalApiHelpers.BuildAttributes(_inputClient.Experimental); + + protected override SuppressionStatement[] BuildDisabledFileWarnings() + => ExperimentalApiHelpers.GetSuppressions(_inputClient); + private readonly InputAuth? _inputAuth; private readonly ParameterProvider _endpointParameter; private readonly ParameterProvider _subClientEndpointParameter; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/CollectionResultDefinition.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/CollectionResultDefinition.cs index c6dde93b551..c2f994d2c70 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/CollectionResultDefinition.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/CollectionResultDefinition.cs @@ -18,6 +18,7 @@ using Microsoft.TypeSpec.Generator.Providers; using Microsoft.TypeSpec.Generator.Snippets; using Microsoft.TypeSpec.Generator.Statements; +using Microsoft.TypeSpec.Generator.Utilities; using static Microsoft.TypeSpec.Generator.Snippets.Snippet; namespace Microsoft.TypeSpec.Generator.ClientModel.Providers @@ -184,6 +185,11 @@ private PropertyProvider FindPropertyInModelHierarchy(TypeProvider model, string protected override string BuildNamespace() => Client.Type.Namespace; + protected override SuppressionStatement[] BuildDisabledFileWarnings() + => ExperimentalApiHelpers.MergeSuppressions( + ResponseModel.DisabledFileWarnings, + ExperimentalApiHelpers.GetSuppressions(Operation, Client.InputClient)); + protected override string BuildName() { var operationName = Operation.Name.ToIdentifierName(); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs index b9a08ed4189..7d43716a22d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs @@ -8,7 +8,6 @@ using System.Linq; using Microsoft.TypeSpec.Generator.ClientModel.Primitives; using Microsoft.TypeSpec.Generator.ClientModel.Snippets; -using Microsoft.TypeSpec.Generator.ClientModel.Utilities; using Microsoft.TypeSpec.Generator.EmitterRpc; using Microsoft.TypeSpec.Generator.Expressions; using Microsoft.TypeSpec.Generator.Input; @@ -53,6 +52,9 @@ public RestClientProvider(InputClient inputClient, ClientProvider clientProvider public ClientProvider ClientProvider { get; } + protected override SuppressionStatement[] BuildDisabledFileWarnings() + => ExperimentalApiHelpers.GetSuppressions(_inputClient); + protected override string BuildRelativeFilePath() => Path.Combine("src", "Generated", $"{Name}.RestClient.cs"); protected override string BuildName() => ClientProvider.Name; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmModelFactoryProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmModelFactoryProvider.cs index 2e2ff07b213..02dd7e29d8c 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmModelFactoryProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmModelFactoryProvider.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq; using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Providers; using Microsoft.TypeSpec.Generator.Statements; @@ -31,7 +32,8 @@ protected override MethodProvider[] BuildMethods() foreach (var method in methods) { - if (!MethodReferencesFileBinaryContent(method)) + if (!MethodReferencesFileBinaryContent(method) + || method.Signature.Attributes.Any(a => a.Type.Equals(typeof(ExperimentalAttribute)))) { continue; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmModelProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmModelProvider.cs index 660f0cdd3cf..95972f46508 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmModelProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmModelProvider.cs @@ -117,7 +117,8 @@ protected override PropertyProvider[] BuildProperties() foreach (var prop in properties) { - if (IsFileBinaryContentType(prop.Type)) + if (IsFileBinaryContentType(prop.Type) + && !prop.Attributes.Any(a => a.Type.Equals(typeof(ExperimentalAttribute)))) { prop.Update(attributes: [.. prop.Attributes, new AttributeStatement(typeof(ExperimentalAttribute), [Literal(FileBinaryContentDiagnosticId)])]); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Utilities/ExperimentalApiHelpers.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Utilities/ExperimentalApiHelpers.cs deleted file mode 100644 index 9f45a6283a7..00000000000 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Utilities/ExperimentalApiHelpers.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using Microsoft.TypeSpec.Generator.Input; -using Microsoft.TypeSpec.Generator.Providers; -using Microsoft.TypeSpec.Generator.Statements; -using static Microsoft.TypeSpec.Generator.Snippets.Snippet; - -namespace Microsoft.TypeSpec.Generator.ClientModel.Utilities -{ - internal static class ExperimentalApiHelpers - { - public static AttributeStatement? BuildAttribute(InputOperation operation) - => operation.Experimental?.DiagnosticId is { } diagnosticId - ? new AttributeStatement(typeof(ExperimentalAttribute), [Literal(diagnosticId)]) - : null; - - public static void AddDependencySuppressions(MethodProvider method, InputOperation operation) - { - if (operation.Experimental?.DependsOn is not { Count: > 0 } dependencies) - { - return; - } - - // An empty #pragma warning disable would suppress every diagnostic. - if (dependencies.Any(string.IsNullOrWhiteSpace)) - { - throw new ArgumentException("Experimental dependency diagnostic identifiers cannot be empty or whitespace.", nameof(operation)); - } - - method.Update(suppressions: - [ - .. method.Suppressions, - .. dependencies.Distinct(StringComparer.Ordinal) - .Select(id => new SuppressionStatement(null, Literal(id), "This method depends on experimental functionality.")) - ]); - } - } -} 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 26a8e45fd8e..c30a12aa1cc 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 @@ -5,6 +5,7 @@ using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; @@ -25,6 +26,28 @@ namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.ClientProvide { public class ClientProviderTests { + [Test] + public void ExperimentalClientAndModelReferences() + { + var model = InputFactory.Experimental(InputFactory.Model("Payload"), "MODEL001"); + var operation = InputFactory.Operation("Read", responses: [InputFactory.OperationResponse(bodytype: model)]); + var clientInput = InputFactory.Experimental( + InputFactory.Client("Experiment", methods: [InputFactory.BasicServiceMethod("Read", operation)]), + "CLIENT001", "DEP001"); + MockHelpers.LoadMockGenerator(inputModels: () => [model], clients: () => [clientInput]); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(clientInput)!; + + Assert.AreEqual(Literal("CLIENT001").ToDisplayString(), + client.Attributes.Single(a => a.Type.Equals(typeof(ExperimentalAttribute))).Arguments[0].ToDisplayString()); + foreach (var provider in new TypeProvider[] { client, client.RestClient }) + { + CollectionAssert.IsSubsetOf( + new[] { "CLIENT001", "DEP001", "MODEL001" }.Select(id => Literal(id).ToDisplayString()), + provider.DisabledFileWarnings.Select(s => s.Code.ToDisplayString())); + } + Assert.IsFalse(client.RestClient.Attributes.Any(a => a.Type.Equals(typeof(ExperimentalAttribute)))); + } + [TestCase("Foo", "Foo", ExpectedResult = true)] [TestCase("Foo", "Bar", ExpectedResult = false)] [TestCase("Foo", "_Foo", ExpectedResult = false)] diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/CollectionResultDefinitions/CollectionResultDefinitionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/CollectionResultDefinitions/CollectionResultDefinitionTests.cs index f76c1e3c87a..9d40ed4f8c5 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/CollectionResultDefinitions/CollectionResultDefinitionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/CollectionResultDefinitions/CollectionResultDefinitionTests.cs @@ -6,6 +6,7 @@ using Microsoft.TypeSpec.Generator.ClientModel.Providers; using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Primitives; +using Microsoft.TypeSpec.Generator.Snippets; using Microsoft.TypeSpec.Generator.Tests.Common; using NUnit.Framework; @@ -13,6 +14,27 @@ namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.CollectionRes { public class CollectionResultDefinitionTests { + [Test] + public void ExperimentalPagingDependenciesAreSuppressedInAllHelpers() + { + var item = InputFactory.Experimental(InputFactory.Model("Item"), "ITEM001"); + var envelope = InputFactory.Model("Page", properties: [InputFactory.Property("items", InputFactory.Array(item))]); + var operation = InputFactory.Operation("List", responses: [InputFactory.OperationResponse(bodytype: envelope)]); + operation.Update(experimental: new InputExperimentalDetails("API001", ["DEPENDENCY001"])); + var serviceMethod = InputFactory.PagingServiceMethod("List", operation, pagingMetadata: InputFactory.PagingMetadata(["items"], null, null)); + var client = InputFactory.Experimental(InputFactory.Client("TestClient", methods: [serviceMethod]), "CLIENT001"); + MockHelpers.LoadMockGenerator(inputModels: () => [item, envelope], clients: () => [client]); + var helpers = ScmCodeModelGenerator.Instance.OutputLibrary.TypeProviders.OfType().ToArray(); + + Assert.AreEqual(4, helpers.Length); + foreach (var helper in helpers) + { + CollectionAssert.IsSubsetOf( + new[] { "ITEM001", "CLIENT001", "API001", "DEPENDENCY001" }.Select(id => Snippet.Literal(id).ToDisplayString()), + helper.DisabledFileWarnings.Select(s => s.Code.ToDisplayString())); + } + } + [SetUp] public void Setup() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ModelFactoryProviders/ScmModelFactoryProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ModelFactoryProviders/ScmModelFactoryProviderTests.cs index 8ca50191b93..cac43a50c53 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ModelFactoryProviders/ScmModelFactoryProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ModelFactoryProviders/ScmModelFactoryProviderTests.cs @@ -9,6 +9,7 @@ using System.Linq; using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Primitives; +using Microsoft.TypeSpec.Generator.Snippets; using Microsoft.TypeSpec.Generator.Tests.Common; using NUnit.Framework; @@ -16,6 +17,18 @@ namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.ModelFactoryP { public class ScmModelFactoryProviderTests { + [Test] + public void ExperimentalModelFactoryUsesThePublicModelDiagnostic() + { + var input = InputFactory.Experimental(InputFactory.Model("Payload"), "MODEL001", "DEP001"); + MockHelpers.LoadMockGenerator(inputModels: () => [input]); + var factory = ScmCodeModelGenerator.Instance.TypeFactory.CreateModelFactory([input]); + var method = factory.Methods.Single(m => m.Signature.Name == "Payload"); + + Assert.AreEqual(Snippet.Literal("MODEL001").ToDisplayString(), + method.Signature.Attributes.Single(a => a.Type.Equals(typeof(ExperimentalAttribute))).Arguments[0].ToDisplayString()); + } + [SetUp] public void SetUp() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs index 35c8351e05e..9932efedb05 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs @@ -16,7 +16,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.TypeSpec.Generator.ClientModel.Providers; -using Microsoft.TypeSpec.Generator.ClientModel.Utilities; +using Microsoft.TypeSpec.Generator.Utilities; using Microsoft.TypeSpec.Generator.EmitterRpc; using Microsoft.TypeSpec.Generator.Expressions; using Microsoft.TypeSpec.Generator.Input; @@ -105,6 +105,24 @@ public void ExperimentalOperationRejectsEmptyDependencyDiagnostic(string diagnos Assert.Throws(() => new ScmMethodProviderCollection(serviceMethod, client)); } + [Test] + public void ExperimentalDependenciesAlreadySuppressedForClientAreNotRestoredByMethods() + { + var model = InputFactory.Experimental(InputFactory.Model("Payload"), "MODEL001"); + var operation = InputFactory.Operation("Read", responses: [InputFactory.OperationResponse(bodytype: model)]); + operation.Update(experimental: new InputExperimentalDetails("API001", ["MODEL001"])); + var method = InputFactory.BasicServiceMethod("Read", operation); + var inputClient = InputFactory.Client("TestClient", methods: [method]); + MockHelpers.LoadMockGenerator(inputModels: () => [model], clients: () => [inputClient]); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + + foreach (var provider in new ScmMethodProviderCollection(method, client).Append(client.RestClient.GetCreateRequestMethod(operation))) + { + Assert.IsTrue(provider.EnclosingType.DisabledFileWarnings.Any(s => s.Code.ToDisplayString() == Snippet.Literal("MODEL001").ToDisplayString())); + Assert.AreEqual(0, provider.Suppressions.Count, "A method-local restore would undo the generated file's suppression."); + } + } + [TestCase(null)] [TestCase("C")] public void ExperimentalMultipartOperationHasOnePublicDiagnostic(string? diagnosticId) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmModelProvider/ScmModelProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmModelProvider/ScmModelProviderTests.cs index d5b58f5a7ca..dfe9960dbaa 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmModelProvider/ScmModelProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmModelProvider/ScmModelProviderTests.cs @@ -18,6 +18,25 @@ namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.ScmModelProvi { public class ScmModelProviderTests { + [Test] + public void ExperimentalModelAttributeIsNotRepeatedOnSerializationPartials() + { + var property = InputFactory.Experimental(InputFactory.Property("value", InputPrimitiveType.String), "PROPERTY001"); + var inputModel = InputFactory.Experimental(InputFactory.Model("Payload", properties: [property]), "MODEL001", "DEP001"); + MockHelpers.LoadMockGenerator(inputModels: () => [inputModel]); + var model = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel)!; + + Assert.AreEqual(1, model.Attributes.Count(a => a.Type.Equals(typeof(ExperimentalAttribute)))); + Assert.IsNotEmpty(model.SerializationProviders); + foreach (var serialization in model.SerializationProviders) + { + Assert.IsFalse(serialization.Attributes.Any(a => a.Type.Equals(typeof(ExperimentalAttribute)))); + CollectionAssert.IsSubsetOf( + new[] { "MODEL001", "DEP001", "PROPERTY001" }.Select(id => Snippet.Literal(id).ToDisplayString()), + serialization.DisabledFileWarnings.Select(s => s.Code.ToDisplayString())); + } + } + private sealed class DerivedScmModelProvider : ScmModel { public DerivedScmModelProvider(InputModelType inputModel) : base(inputModel) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputClient.cs index 3ce5d3502c1..5b868eadb25 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputClient.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputClient.cs @@ -51,6 +51,7 @@ public InputClient() : this(string.Empty, string.Empty, string.Empty, string.Emp public InputClient? Parent { get; internal set; } public IReadOnlyList Children { get; internal set; } public IReadOnlyList Decorators { get; internal set; } = new List(); + public InputExperimentalDetails? Experimental { get; internal set; } public IReadOnlyList ApiVersions { get; internal set; } public string Key diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputProperty.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputProperty.cs index 6a402bc5485..753c12d85ca 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputProperty.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputProperty.cs @@ -30,6 +30,7 @@ protected InputProperty(string name, string? summary, string? doc, InputType typ public string? Access { get; internal set; } public string SerializedName { get; internal set; } public IReadOnlyList Decorators { get; internal set; } = new List(); + public InputExperimentalDetails? Experimental { get; internal set; } public InputModelType? EnclosingType { get; internal set; } public bool IsApiVersion { get; internal set; } public InputConstant? DefaultValue { get; internal set; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputType.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputType.cs index e118bd6809d..61432c48787 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputType.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputType.cs @@ -22,6 +22,7 @@ protected InputType(string name) public string Name { get; internal set; } public IReadOnlyList Decorators { get; internal set; } = new List(); public InputExternalTypeMetadata? External { get; internal set; } + public InputExperimentalDetails? Experimental { get; internal set; } /// /// Whether the name should be used exactly as-is, without casing transformations. /// diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputClientConverter.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputClientConverter.cs index eee7cc8ab6b..871acfaacf8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputClientConverter.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputClientConverter.cs @@ -48,6 +48,7 @@ public override void Write(Utf8JsonWriter writer, InputClient value, JsonSeriali InputClient? parent = null; IReadOnlyList? children = null; IReadOnlyList? apiVersions = null; + InputExperimentalDetails? experimental = null; while (reader.TokenType != JsonTokenType.EndObject) { @@ -61,6 +62,7 @@ public override void Write(Utf8JsonWriter writer, InputClient value, JsonSeriali || reader.TryReadComplexType("parameters", options, ref parameters) || reader.TryReadInt32("initializedBy", ref initializedByValue) || reader.TryReadComplexType("decorators", options, ref decorators) + || reader.TryReadComplexType("experimental", options, ref experimental) || reader.TryReadString("crossLanguageDefinitionId", ref crossLanguageDefinitionId) || reader.TryReadComplexType("parent", options, ref parent) || reader.TryReadComplexType("children", options, ref children) @@ -86,6 +88,7 @@ public override void Write(Utf8JsonWriter writer, InputClient value, JsonSeriali client.Parent = parent; client.Children = children ?? []; client.ApiVersions = apiVersions ?? []; + client.Experimental = experimental; return client; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputEnumTypeConverter.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputEnumTypeConverter.cs index 77795bddc92..7bb2c8d6502 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputEnumTypeConverter.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputEnumTypeConverter.cs @@ -64,6 +64,7 @@ public static InputEnumType CreateEnumType(ref Utf8JsonReader reader, string? id InputExternalTypeMetadata? external = null; bool isExactName = false; IReadOnlyList? apiVersions = null; + InputExperimentalDetails? experimental = null; while (reader.TokenType != JsonTokenType.EndObject) { var isKnownProperty = reader.TryReadString("name", ref name) @@ -80,6 +81,7 @@ public static InputEnumType CreateEnumType(ref Utf8JsonReader reader, string? id || reader.TryReadComplexType("values", options, ref values) || reader.TryReadComplexType("decorators", options, ref decorators) || reader.TryReadComplexType("external", options, ref external) + || reader.TryReadComplexType("experimental", options, ref experimental) || reader.TryReadBoolean("isExactName", ref isExactName); if (!isKnownProperty) @@ -107,6 +109,7 @@ public static InputEnumType CreateEnumType(ref Utf8JsonReader reader, string? id enumType.External = external; enumType.IsExactName = isExactName; enumType.ApiVersions = apiVersions ?? []; + enumType.Experimental = experimental; return enumType; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputEnumTypeValueConverter.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputEnumTypeValueConverter.cs index d0e067588bb..6ecd7e61d28 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputEnumTypeValueConverter.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputEnumTypeValueConverter.cs @@ -32,6 +32,7 @@ internal static InputEnumTypeValue CreateEnumTypeValue(ref Utf8JsonReader reader string? doc = null; IReadOnlyList? decorators = null; bool isExactName = false; + InputExperimentalDetails? experimental = null; while (reader.TokenType != JsonTokenType.EndObject) { var isKnownProperty = reader.TryReadReferenceId(ref id) @@ -42,6 +43,7 @@ internal static InputEnumTypeValue CreateEnumTypeValue(ref Utf8JsonReader reader || reader.TryReadString("summary", ref summary) || reader.TryReadString("doc", ref doc) || reader.TryReadComplexType("decorators", options, ref decorators) + || reader.TryReadComplexType("experimental", options, ref experimental) || reader.TryReadBoolean("isExactName", ref isExactName); if (!isKnownProperty) @@ -79,6 +81,7 @@ InputPrimitiveTypeKind.Decimal or InputPrimitiveTypeKind.Decimal128 => new InputEnumTypeFloatValue(name, rawValue.Value.GetSingle(), valueType, summary, doc, enumType) { Decorators = decorators ?? [], IsExactName = isExactName }, _ => throw new JsonException($"Unsupported enum valueType kind '{valueType.Kind}' for enum '{enumType.Name}' value '{name}'.") }; + enumValue.Experimental = experimental; if (id != null) { resolver.AddReference(id, enumValue); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputModelPropertyConverter.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputModelPropertyConverter.cs index 72198a8a754..72a9e8b704a 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputModelPropertyConverter.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputModelPropertyConverter.cs @@ -68,6 +68,7 @@ internal static InputModelProperty ReadInputModelProperty(ref Utf8JsonReader rea string? encodeString = null; bool isExactName = false; IReadOnlyList? apiVersions = null; + InputExperimentalDetails? experimental = null; while (reader.TokenType != JsonTokenType.EndObject) { @@ -84,6 +85,7 @@ internal static InputModelProperty ReadInputModelProperty(ref Utf8JsonReader rea || reader.TryReadString("access", ref access) || reader.TryReadBoolean("discriminator", ref isDiscriminator) || reader.TryReadComplexType("decorators", options, ref decorators) + || reader.TryReadComplexType("experimental", options, ref experimental) || reader.TryReadString("serializedName", ref serializedName) || reader.TryReadBoolean("isApiVersion", ref isApiVersion) || reader.TryReadComplexType("defaultValue", options, ref defaultValue) @@ -114,6 +116,7 @@ internal static InputModelProperty ReadInputModelProperty(ref Utf8JsonReader rea property.Encode = Enum.TryParse(encodeString, ignoreCase: true, out var encode) ? encode : null; property.IsExactName = isExactName; property.ApiVersions = apiVersions ?? []; + property.Experimental = experimental; return property; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputModelTypeConverter.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputModelTypeConverter.cs index 94077f29051..c916917122f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputModelTypeConverter.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputModelTypeConverter.cs @@ -77,6 +77,7 @@ internal static InputModelType CreateModelType(ref Utf8JsonReader reader, string bool isExactName = false; bool isFileType = false; IReadOnlyList? apiVersions = null; + InputExperimentalDetails? experimental = null; // read all possible properties and throw away the unknown properties while (reader.TokenType != JsonTokenType.EndObject) @@ -99,6 +100,7 @@ internal static InputModelType CreateModelType(ref Utf8JsonReader reader, string || reader.TryReadComplexType("decorators", options, ref decorators) || reader.TryReadComplexType("serializationOptions", options, ref serializationOptions) || reader.TryReadComplexType("external", options, ref external) + || reader.TryReadComplexType("experimental", options, ref experimental) || reader.TryReadBoolean("isExactName", ref isExactName) || reader.TryReadBoolean("isFileType", ref isFileType) || reader.TryReadBoolean(nameof(InputModelType.ModelAsStruct), ref modelAsStruct); // TODO -- change this to fetch from the decorator list instead when the decorator is ready @@ -153,6 +155,7 @@ internal static InputModelType CreateModelType(ref Utf8JsonReader reader, string } model.External = external; model.IsFileType = isFileType; + model.Experimental = experimental; // if this model has a base, it means this model is a derived model of the base model, add it into the list. if (baseModel != null) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/test/TypeSpecInputConverterTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/test/TypeSpecInputConverterTests.cs index 530f8b54371..eda6eedab83 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/test/TypeSpecInputConverterTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/test/TypeSpecInputConverterTests.cs @@ -10,6 +10,59 @@ namespace Microsoft.TypeSpec.Generator.Input.Tests { public class TypeSpecInputConverterTests { + [TestCase(true)] + [TestCase(false)] + public void LoadsExperimentalTypesAndMembers(bool annotated) + { + string Metadata(string id) => annotated + ? $"\"experimental\": {{\"diagnosticId\":\"{id}\",\"dependsOn\":[\"DEP001\"]}}," + : ""; + var content = $$""" + { + "name": "Test", + "models": [{ + "$id": "model", "name": "Payload", {{Metadata("MODEL001")}} + "properties": [{ + "$id": "property", "name": "value", {{Metadata("PROPERTY001")}} + "type": {"kind":"string"} + }] + }], + "enums": [{ + "$id": "enum", "name": "Choice", {{Metadata("ENUM001")}} + "valueType": {"kind":"string"}, + "values": [{ + "$id": "value", "name": "One", "value": "one", {{Metadata("VALUE001")}} + "valueType": {"kind":"string"}, "enumType": {"$ref":"enum"} + }] + }], + "clients": [{ + "$id": "client", "name": "TestClient", {{Metadata("CLIENT001")}} + "methods": [] + }] + } + """; + var input = TypeSpecSerialization.Deserialize(content)!; + var details = new[] + { + input.Models[0].Experimental, + input.Models[0].Properties[0].Experimental, + input.Enums[0].Experimental, + input.Enums[0].Values[0].Experimental, + input.Clients[0].Experimental + }; + if (annotated) + { + CollectionAssert.AreEqual( + new[] { "MODEL001", "PROPERTY001", "ENUM001", "VALUE001", "CLIENT001" }, + details.Select(d => d?.DiagnosticId)); + Assert.IsTrue(details.All(d => d!.DependsOn.SequenceEqual(["DEP001"]))); + } + else + { + Assert.IsTrue(details.All(d => d is null)); + } + } + [TestCase("""{"diagnosticId":"C","dependsOn":["A","B"]}""", "C", new[] { "A", "B" })] [TestCase("""{"diagnosticId":"C"}""", "C", new string[0])] [TestCase("""{"dependsOn":["A"]}""", null, new[] { "A" })] diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Primitives/EnumTypeMember.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Primitives/EnumTypeMember.cs index ccdea34eb9b..35b94bb8f9d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Primitives/EnumTypeMember.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Primitives/EnumTypeMember.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using Microsoft.TypeSpec.Generator.Providers; +using Microsoft.TypeSpec.Generator.Input; namespace Microsoft.TypeSpec.Generator.Primitives { @@ -19,5 +20,6 @@ public EnumTypeMember(string name, FieldProvider field, object value) public FieldProvider Field { get; } public object Value { get; } + internal InputExperimentalDetails? Experimental { get; init; } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ApiVersionEnumProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ApiVersionEnumProvider.cs index 6bceafce8a5..193ef391f29 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ApiVersionEnumProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ApiVersionEnumProvider.cs @@ -99,9 +99,10 @@ protected override IReadOnlyList BuildEnumValues() name, this, DocHelpers.GetFormattableDescription(inputValue.Summary, inputValue.Doc) ?? $"{name}", - initializationValue); + initializationValue, + attributes: ExperimentalApiHelpers.BuildAttributes(inputValue.Experimental)); - values.Add(new EnumTypeMember(name, field, inputValue.Value)); + values.Add(new EnumTypeMember(name, field, inputValue.Value) { Experimental = inputValue.Experimental }); } } @@ -116,17 +117,17 @@ private List BuildCustomEnumMembers(IReadOnlyList { var member = customMembers[i]; var modifiers = FieldModifiers.Public | FieldModifiers.Static; + allowedValues.TryGetValue(member.OriginalName ?? member.Name, out var enumValue); var field = new FieldProvider( modifiers, EnumUnderlyingType, member.Name, this, $"", - member.InitializationValue); - object? inputValue = allowedValues.TryGetValue(member.OriginalName ?? member.Name, out var enumValue) - ? enumValue.Value - : member.Name; - values.Add(new EnumTypeMember(member.Name, field, inputValue)); + member.InitializationValue, + attributes: ExperimentalApiHelpers.BuildAttributes(enumValue?.Experimental)); + object inputValue = enumValue?.Value ?? member.Name; + values.Add(new EnumTypeMember(member.Name, field, inputValue) { Experimental = enumValue?.Experimental }); } return values; @@ -180,8 +181,9 @@ private List BuildApiVersionEnumValuesForBackwardCompatibility(L member.Name, member.Field.EnclosingType, member.Field.Description, - Literal(i + 1)); - allMembers[i] = new EnumTypeMember(member.Name, updatedField, member.Value); + Literal(i + 1), + attributes: member.Field.Attributes); + allMembers[i] = new EnumTypeMember(member.Name, updatedField, member.Value) { Experimental = member.Experimental }; } return allMembers; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ExtensibleEnumProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ExtensibleEnumProvider.cs index 7cdb0e11e9f..904def1ca0c 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ExtensibleEnumProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ExtensibleEnumProvider.cs @@ -74,7 +74,7 @@ protected override IReadOnlyList BuildEnumValues() DocHelpers.GetFormattableDescription(inputValue.Summary, inputValue.Doc), initializationValue); - values[i] = new EnumTypeMember(valueName, field, inputValue.Value); + values[i] = new EnumTypeMember(valueName, field, inputValue.Value) { Experimental = inputValue.Experimental }; } return values; @@ -101,7 +101,8 @@ protected internal override PropertyProvider[] BuildProperties() type: Type, name: name, body: new AutoPropertyBody(false, InitializationExpression: New.Instance(Type, field)), - this); + this, + attributes: ExperimentalApiHelpers.BuildAttributes(enumValue.Experimental)); } return properties; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/FixedEnumProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/FixedEnumProvider.cs index 56ae107d419..c243be1fb4d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/FixedEnumProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/FixedEnumProvider.cs @@ -112,9 +112,10 @@ protected override IReadOnlyList BuildEnumValues() name, this, DocHelpers.GetFormattableDescription(inputValue.Summary, inputValue.Doc) ?? $"{name}", - initializationValue); + initializationValue, + attributes: ExperimentalApiHelpers.BuildAttributes(inputValue.Experimental)); - values[i] = new EnumTypeMember(name, field, inputValue.Value); + values[i] = new EnumTypeMember(name, field, inputValue.Value) { Experimental = inputValue.Experimental }; } return values; } @@ -155,8 +156,9 @@ protected override IReadOnlyList BuildEnumValues() existingMember.Name, existingMember.Field.EnclosingType, existingMember.Field.Description, - initializationValue); - allMembers.Add(new EnumTypeMember(existingMember.Name, updatedField, memberValue)); + initializationValue, + attributes: existingMember.Field.Attributes); + allMembers.Add(new EnumTypeMember(existingMember.Name, updatedField, memberValue) { Experimental = existingMember.Experimental }); } else if (customMemberLastContractNames.Contains(field.Name)) { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs index cbc6ac9c06c..07c108e95ad 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs @@ -36,6 +36,9 @@ protected internal ModelFactoryProvider(IEnumerable models) protected override string BuildRelativeFilePath() => Path.Combine("src", "Generated", $"{Name}.cs"); + protected internal override SuppressionStatement[] BuildDisabledFileWarnings() + => ExperimentalApiHelpers.GetSuppressions(_models); + protected override TypeSignatureModifiers BuildDeclarationModifiers() => TypeSignatureModifiers.Static | TypeSignatureModifiers.Partial | TypeSignatureModifiers.Class; @@ -74,7 +77,8 @@ protected internal override MethodProvider[] BuildMethods() MethodSignatureModifiers.Static | MethodSignatureModifiers.Public, modelProvider.Type, $"A new {modelProvider.Type:C} instance for mocking.", - GetParameters(modelProvider, fullConstructor)); + GetParameters(modelProvider, fullConstructor), + Attributes: ExperimentalApiHelpers.BuildAttributes(model.Experimental)); var parameters = new List(signature.Parameters.Count); foreach (var param in signature.Parameters) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/PropertyProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/PropertyProvider.cs index 758c7dbe09f..5ff15415af8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/PropertyProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/PropertyProvider.cs @@ -143,7 +143,7 @@ private PropertyProvider(InputProperty inputProperty, CSharpType propertyType, T Body = new AutoPropertyBody(propHasSetter, setterModifier, GetPropertyInitializationValue(propertyType, inputProperty)); WireInfo = new PropertyWireInformation(inputProperty); - Attributes = []; + Attributes = ExperimentalApiHelpers.BuildAttributes(inputProperty.Experimental); InitializeParameter(DocHelpers.GetFormattableDescription(inputProperty.Summary, inputProperty.Doc) ?? FormattableStringHelpers.Empty); BuildDocs(); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs index 564e3bbd43f..6ca203f19a2 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs @@ -286,7 +286,11 @@ LastContractView is { } lastContractView && protected virtual CSharpType? BuildBaseType() => null; private IReadOnlyList? _disabledFileWarnings; - public IReadOnlyList DisabledFileWarnings => _disabledFileWarnings ??= BuildDisabledFileWarnings(); + public IReadOnlyList DisabledFileWarnings => _disabledFileWarnings ??= + ExperimentalApiHelpers.MergeSuppressions( + BuildDisabledFileWarnings(), + ExperimentalApiHelpers.GetSuppressions(_inputType ?? SerializationProviderOwner?._inputType), + NestedTypes.SelectMany(type => type.DisabledFileWarnings)); private protected virtual bool FilterCustomizedMembers => true; @@ -720,7 +724,13 @@ private IReadOnlyList AssignSerializationProviderOwners(IEnumerabl protected virtual CSharpType BuildEnumUnderlyingType() => throw new InvalidOperationException("Not an EnumProvider type"); - protected virtual IReadOnlyList BuildAttributes() => []; + protected virtual IReadOnlyList BuildAttributes() + { + var attribute = ExperimentalApiHelpers.BuildAttribute(_inputType?.Experimental); + return attribute is null || CustomCodeView?.Attributes.Any(a => a.Type.Equals(attribute.Type)) == true + ? [] + : [attribute]; + } private CSharpType? _enumUnderlyingType; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/ExperimentalApiHelpers.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/ExperimentalApiHelpers.cs new file mode 100644 index 00000000000..48428196bf0 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/ExperimentalApiHelpers.cs @@ -0,0 +1,221 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.TypeSpec.Generator.Input; +using Microsoft.TypeSpec.Generator.Providers; +using Microsoft.TypeSpec.Generator.Statements; +using static Microsoft.TypeSpec.Generator.Snippets.Snippet; + +namespace Microsoft.TypeSpec.Generator.Utilities +{ + public static class ExperimentalApiHelpers + { + public static AttributeStatement? BuildAttribute(InputOperation operation) + => BuildAttribute(operation.Experimental); + + public static AttributeStatement? BuildAttribute(InputExperimentalDetails? details) + { + if (details?.DiagnosticId is not { } diagnosticId) + { + return null; + } + ValidateDiagnosticId(diagnosticId); + return new AttributeStatement(typeof(ExperimentalAttribute), [Literal(diagnosticId)]); + } + + public static AttributeStatement[] BuildAttributes(InputExperimentalDetails? details) + => BuildAttribute(details) is { } attribute ? [attribute] : []; + + public static void AddDependencySuppressions(MethodProvider method, InputOperation operation) + { + if (operation.Experimental?.DependsOn is not { Count: > 0 } dependencies) + { + return; + } + + var fileSuppressions = method.EnclosingType.DisabledFileWarnings + .Select(s => s.Code.ToDisplayString()).ToHashSet(StringComparer.Ordinal); + method.Update(suppressions: MergeSuppressions( + method.Suppressions, + CreateSuppressions(dependencies).Where(s => !fileSuppressions.Contains(s.Code.ToDisplayString())))); + } + + public static SuppressionStatement[] GetSuppressions(InputType? type) + => GetSuppressions(type is null ? [] : new[] { type }); + + public static SuppressionStatement[] GetSuppressions(IEnumerable types) + { + var collector = new DiagnosticCollector(); + foreach (var type in types) + { + collector.AddDeclaration(type); + } + return CreateSuppressions(collector.Ids.Order(StringComparer.Ordinal)); + } + + public static SuppressionStatement[] GetSuppressions(InputClient client) + { + var collector = new DiagnosticCollector(); + collector.AddMetadata(client.Experimental); + collector.AddId(client.Parent?.Experimental?.DiagnosticId); + foreach (var child in client.Children) + { + collector.AddId(child.Experimental?.DiagnosticId); + } + foreach (var parameter in client.Parameters) + { + collector.AddProperty(parameter); + } + foreach (var method in client.Methods) + { + foreach (var parameter in method.Parameters.Concat(method.Operation.Parameters)) + { + collector.AddProperty(parameter); + } + collector.AddReference(method.Response.Type); + foreach (var response in method.Operation.Responses) + { + collector.AddReference(response.BodyType); + } + } + return CreateSuppressions(collector.Ids.Order(StringComparer.Ordinal)); + } + + public static SuppressionStatement[] GetSuppressions(InputOperation operation, InputClient? client = null) + { + var collector = new DiagnosticCollector(); + collector.AddMetadata(client?.Experimental); + collector.AddMetadata(operation.Experimental); + foreach (var parameter in operation.Parameters) + { + collector.AddProperty(parameter); + } + foreach (var response in operation.Responses) + { + collector.AddReference(response.BodyType); + } + return CreateSuppressions(collector.Ids.Order(StringComparer.Ordinal)); + } + + public static SuppressionStatement[] MergeSuppressions(params IEnumerable[] suppressions) + => [.. suppressions.SelectMany(s => s).DistinctBy(s => s.Code.ToDisplayString())]; + + private static SuppressionStatement[] CreateSuppressions(IEnumerable diagnosticIds) + => [.. diagnosticIds.Distinct(StringComparer.Ordinal).Select(id => + { + ValidateDiagnosticId(id); + return new SuppressionStatement(null, Literal(id), "This generated code depends on experimental functionality."); + })]; + + private static void ValidateDiagnosticId(string diagnosticId) + { + // An empty #pragma warning disable would suppress every diagnostic. + if (string.IsNullOrWhiteSpace(diagnosticId)) + { + throw new ArgumentException("Experimental diagnostic identifiers cannot be empty or whitespace.", nameof(diagnosticId)); + } + } + + private sealed class DiagnosticCollector + { + private readonly HashSet _declarations = []; + private readonly HashSet _references = []; + public HashSet Ids { get; } = new(StringComparer.Ordinal); + + public void AddId(string? id) + { + if (id is not null) + { + ValidateDiagnosticId(id); + Ids.Add(id); + } + } + + public void AddMetadata(InputExperimentalDetails? details) + { + AddId(details?.DiagnosticId); + foreach (var dependency in details?.DependsOn ?? []) + { + AddId(dependency); + } + } + + public void AddProperty(InputProperty property) + { + AddMetadata(property.Experimental); + AddReference(property.Type); + AddReference(property.DefaultValue?.Type); + } + + public void AddDeclaration(InputType type) + { + if (!_declarations.Add(type)) + { + return; + } + AddMetadata(type.Experimental); + if (type is InputModelType model) + { + foreach (var property in model.Properties) + { + AddProperty(property); + } + if (model.BaseModel is { } baseModel) + { + AddDeclaration(baseModel); + } + AddReference(model.AdditionalProperties); + foreach (var derived in model.DerivedModels) + { + AddReference(derived); + } + } + else if (type is InputEnumType enumType) + { + foreach (var value in enumType.Values) + { + AddMetadata(value.Experimental); + } + } + } + + public void AddReference(InputType? type) + { + if (type is null || !_references.Add(type)) + { + return; + } + AddId(type.Experimental?.DiagnosticId); + switch (type) + { + case InputArrayType array: + AddReference(array.ValueType); + break; + case InputStreamingType streaming: + AddReference(streaming.ValueType); + break; + case InputDictionaryType dictionary: + AddReference(dictionary.KeyType); + AddReference(dictionary.ValueType); + break; + case InputNullableType nullable: + AddReference(nullable.Type); + break; + case InputUnionType union: + foreach (var variant in union.VariantTypes) + { + AddReference(variant); + } + break; + case InputEnumTypeValue value: + AddReference(value.EnumType); + break; + } + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs index 111188784fd..133203c1774 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs @@ -19,6 +19,90 @@ namespace Microsoft.TypeSpec.Generator.Tests.Providers { public class TypeProviderTests { + [TestCase(false)] + [TestCase(true)] + public void ExperimentalModelsAndProperties(bool modelAsStruct) + { + MockHelpers.LoadMockGenerator(); + var property = InputFactory.Experimental(InputFactory.Property("value", InputPrimitiveType.String), "PROPERTY001", "PROPERTYDEP"); + var model = InputFactory.Experimental( + InputFactory.Model("Payload", modelAsStruct: modelAsStruct, properties: [property]), + "MODEL001", "MODELDEP"); + var provider = CodeModelGenerator.Instance.TypeFactory.CreateModel(model)!; + + Assert.AreEqual(Snippet.Literal("MODEL001").ToDisplayString(), + provider.Attributes.Single(a => a.Type.Equals(typeof(ExperimentalAttribute))).Arguments[0].ToDisplayString()); + Assert.AreEqual(Snippet.Literal("PROPERTY001").ToDisplayString(), + provider.Properties.Single(p => p.Name == "Value").Attributes.Single(a => a.Type.Equals(typeof(ExperimentalAttribute))).Arguments[0].ToDisplayString()); + CollectionAssert.AreEquivalent( + new[] { "MODEL001", "MODELDEP", "PROPERTY001", "PROPERTYDEP" }.Select(id => Snippet.Literal(id).ToDisplayString()), + provider.DisabledFileWarnings.Select(s => s.Code.ToDisplayString())); + } + + [TestCase(false)] + [TestCase(true)] + public void ExperimentalEnumsAndMembers(bool isExtensible) + { + MockHelpers.LoadMockGenerator(); + var input = InputFactory.Experimental( + InputFactory.StringEnum("Choice", [("One", "one"), ("Two", "two")], isExtensible: isExtensible), + "ENUM001", "ENUMDEP"); + InputFactory.Experimental(input.Values[0], "MEMBER001", "MEMBERDEP"); + var provider = CodeModelGenerator.Instance.TypeFactory.CreateEnum(input)!; + var attributes = isExtensible + ? provider.Properties.Single(p => p.Name == "One").Attributes + : provider.EnumValues.Single(v => v.Name == "One").Field.Attributes; + + Assert.AreEqual(Snippet.Literal("ENUM001").ToDisplayString(), + provider.Attributes.Single(a => a.Type.Equals(typeof(ExperimentalAttribute))).Arguments[0].ToDisplayString()); + Assert.AreEqual(Snippet.Literal("MEMBER001").ToDisplayString(), + attributes.Single(a => a.Type.Equals(typeof(ExperimentalAttribute))).Arguments[0].ToDisplayString()); + CollectionAssert.AreEquivalent( + new[] { "ENUM001", "ENUMDEP", "MEMBER001", "MEMBERDEP" }.Select(id => Snippet.Literal(id).ToDisplayString()), + provider.DisabledFileWarnings.Select(s => s.Code.ToDisplayString())); + var controlAttributes = isExtensible + ? provider.Properties.Single(p => p.Name == "Two").Attributes + : provider.EnumValues.Single(v => v.Name == "Two").Field.Attributes; + Assert.IsFalse(controlAttributes.Any(a => a.Type.Equals(typeof(ExperimentalAttribute)))); + } + + [Test] + public void ExperimentalReferencesAreSuppressedWithoutGraduatingOrPromotingTypes() + { + MockHelpers.LoadMockGenerator(); + var dependency = InputFactory.Experimental(InputFactory.Model("Dependency"), "DEP001"); + var model = InputFactory.Model("Payload", properties: [InputFactory.Property("dependency", InputFactory.Array(dependency))]); + var provider = CodeModelGenerator.Instance.TypeFactory.CreateModel(model)!; + + Assert.IsFalse(provider.Attributes.Any(a => a.Type.Equals(typeof(ExperimentalAttribute)))); + Assert.AreEqual(Snippet.Literal("DEP001").ToDisplayString(), provider.DisabledFileWarnings.Single().Code.ToDisplayString()); + Assert.IsTrue(CodeModelGenerator.Instance.TypeFactory.CreateModel(dependency)!.Attributes.Any(a => a.Type.Equals(typeof(ExperimentalAttribute)))); + } + + [Test] + public void ExperimentalApiVersionMembers() + { + MockHelpers.LoadMockGenerator(); + var input = InputFactory.StringEnum("Versions", [("2024-01-01", "2024-01-01")], usage: InputModelTypeUsage.ApiVersionEnum); + InputFactory.Experimental(input.Values[0], "VERSION001"); + var provider = CodeModelGenerator.Instance.TypeFactory.CreateEnum(input)!; + + Assert.AreEqual(Snippet.Literal("VERSION001").ToDisplayString(), + provider.EnumValues.Single().Field.Attributes.Single(a => a.Type.Equals(typeof(ExperimentalAttribute))).Arguments[0].ToDisplayString()); + } + + [TestCase("MODEL001")] + [TestCase(null)] + public void ExperimentalModelDependenciesDoNotControlPublicStatus(string? diagnosticId) + { + MockHelpers.LoadMockGenerator(); + var input = InputFactory.Experimental(InputFactory.Model("Payload"), diagnosticId, "DEP001", "DEP001"); + var provider = CodeModelGenerator.Instance.TypeFactory.CreateModel(input)!; + + Assert.AreEqual(diagnosticId is null ? 0 : 1, provider.Attributes.Count(a => a.Type.Equals(typeof(ExperimentalAttribute)))); + Assert.AreEqual(1, provider.DisabledFileWarnings.Count(s => s.Code.ToDisplayString() == Snippet.Literal("DEP001").ToDisplayString())); + } + [SetUp] public void Setup() { 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 8c53ad05e54..d834c0e0adb 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 @@ -12,6 +12,24 @@ namespace Microsoft.TypeSpec.Generator.Tests.Common { public static class InputFactory { + public static T Experimental(T type, string? diagnosticId, params string[] dependsOn) where T : InputType + { + type.Experimental = new InputExperimentalDetails(diagnosticId, dependsOn); + return type; + } + + public static InputModelProperty Experimental(InputModelProperty property, string? diagnosticId, params string[] dependsOn) + { + property.Experimental = new InputExperimentalDetails(diagnosticId, dependsOn); + return property; + } + + public static InputClient Experimental(InputClient client, string? diagnosticId, params string[] dependsOn) + { + client.Experimental = new InputExperimentalDetails(diagnosticId, dependsOn); + return client; + } + public static class EnumMember { public static InputEnumTypeValue Int32(string name, int value, InputEnumType enumType, bool isExactName = false) diff --git a/packages/http-client-csharp/generator/TestProjects/Local.Tests/CustomizationTests.cs b/packages/http-client-csharp/generator/TestProjects/Local.Tests/CustomizationTests.cs index b9e72cad196..e7810487ce6 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local.Tests/CustomizationTests.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local.Tests/CustomizationTests.cs @@ -11,6 +11,33 @@ namespace TestProjects.Local.Tests { public class CustomizationTests { + [TestCase("SampleTypeSpec.PreviewDetails", "SAMPLE0003")] + [TestCase("SampleTypeSpec.PreviewChoice", "SAMPLE0004")] + [TestCase("SampleTypeSpec.PreviewExtensibleChoice", "SAMPLE0005")] + [TestCase("SampleTypeSpec.ExperimentalSamples", "SAMPLE0009")] + [TestCase("SampleTypeSpec.LifecycleModel", null)] + public void ExperimentalTypeDiagnostics(string typeName, string? diagnosticId) + { + var type = typeof(SampleTypeSpecClient).Assembly.GetType(typeName); + Assert.IsNotNull(type); + Assert.AreEqual(diagnosticId, type!.GetCustomAttribute()?.DiagnosticId); + } + + [TestCase("SampleTypeSpec.PreviewChoice", "Two", "SAMPLE0007")] + [TestCase("SampleTypeSpec.PreviewExtensibleChoice", "Two", "SAMPLE0006")] + [TestCase("SampleTypeSpec.LifecycleModel", "Preview", "SAMPLE0008")] + [TestCase("SampleTypeSpec.SampleTypeSpecModelFactory", "PreviewDetails", "SAMPLE0003")] + [TestCase("SampleTypeSpec.PreviewChoice", "One", null)] + [TestCase("SampleTypeSpec.PreviewExtensibleChoice", "One", null)] + [TestCase("SampleTypeSpec.SampleTypeSpecClientOptions+ServiceVersion", "V2024_08_16_Preview", "SAMPLE0010")] + public void ExperimentalMemberDiagnostics(string typeName, string memberName, string? diagnosticId) + { + var type = typeof(SampleTypeSpecClient).Assembly.GetType(typeName); + Assert.IsNotNull(type); + var member = type!.GetMember(memberName).Single(); + Assert.AreEqual(diagnosticId, member.GetCustomAttribute()?.DiagnosticId); + } + [TestCase("HelloDemo2", "SAMPLE0001", 2)] [TestCase("HelloDemo2Async", "SAMPLE0001", 2)] [TestCase("DynamicModelOperation", "SAMPLE0002", 2)] diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/Sample-TypeSpec.tsp b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/Sample-TypeSpec.tsp index d1aada67e56..b900c260de4 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/Sample-TypeSpec.tsp +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/Sample-TypeSpec.tsp @@ -42,9 +42,78 @@ alias SampleOAuth2 = OAuth2Auth<[ enum Versions { `2024-07-16-preview`, + + @TypeSpec.HttpClient.experimental(#{ + emitterScope: "@typespec/http-client-csharp", + diagnosticId: "SAMPLE0010", + }) `2024-08-16-preview`, } +@TypeSpec.HttpClient.experimental(#{ + emitterScope: "@typespec/http-client-csharp", + diagnosticId: "SAMPLE0003", + dependsOn: #["SAMPLE0004"], +}) +model PreviewDetails { + choice: PreviewChoice; +} + +@TypeSpec.HttpClient.experimental(#{ + emitterScope: "@typespec/http-client-csharp", + diagnosticId: "SAMPLE0004", +}) +enum PreviewChoice { + One: "one", + + @TypeSpec.HttpClient.experimental(#{ + emitterScope: "@typespec/http-client-csharp", + diagnosticId: "SAMPLE0007", + }) + Two: "two", +} + +@TypeSpec.HttpClient.experimental(#{ + emitterScope: "@typespec/http-client-csharp", + diagnosticId: "SAMPLE0005", +}) +union PreviewExtensibleChoice { + string, + One: "one", + + @TypeSpec.HttpClient.experimental(#{ + emitterScope: "@typespec/http-client-csharp", + diagnosticId: "SAMPLE0006", + }) + Two: "two", +} + +model LifecycleModel { + @TypeSpec.HttpClient.experimental(#{ + emitterScope: "@typespec/http-client-csharp", + diagnosticId: "SAMPLE0008", + dependsOn: #["SAMPLE0003"], + }) + preview?: PreviewDetails; + + choice: PreviewExtensibleChoice; +} + +@TypeSpec.HttpClient.experimental(#{ + emitterScope: "@typespec/http-client-csharp", + diagnosticId: "SAMPLE0009", +}) +interface ExperimentalSamples { + @route("/experimental") + @get + read(): LifecycleModel; + + @route("/experimental/pages") + @get + @list + list(): Page; +} + @doc("float fixed enum") enum FloatFixedEnumWithIntValue { One: 1.0, @@ -957,3 +1026,7 @@ op receiveJsonLines(): JsonlStream; @get @route("/streaming/sse/receive") op receiveSse(): SSEStream; + +@route("/experimental/jsonl") +@get +op receiveExperimentalJsonLines(): JsonlStream; diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/ExperimentalSamplesGetAllAsyncCollectionResult.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/ExperimentalSamplesGetAllAsyncCollectionResult.cs new file mode 100644 index 00000000000..f59be9a8037 --- /dev/null +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/ExperimentalSamplesGetAllAsyncCollectionResult.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Threading.Tasks; + +#pragma warning disable SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning disable SAMPLE0009 // This generated code depends on experimental functionality. +namespace SampleTypeSpec +{ + internal partial class ExperimentalSamplesGetAllAsyncCollectionResult : AsyncCollectionResult + { + private readonly ExperimentalSamples _client; + private readonly RequestOptions _options; + + /// Initializes a new instance of ExperimentalSamplesGetAllAsyncCollectionResult, which is used to iterate over the pages of a collection. + /// The ExperimentalSamples client used to send requests. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + public ExperimentalSamplesGetAllAsyncCollectionResult(ExperimentalSamples client, RequestOptions options) + { + _client = client; + _options = options; + } + + /// Gets the raw pages of the collection. + /// The raw pages of the collection. + public override async IAsyncEnumerable GetRawPagesAsync() + { + PipelineMessage message = _client.CreateGetAllRequest(_options); + yield return await GetNextResponseAsync(message).ConfigureAwait(false); + } + + /// Gets the continuation token from the specified page. + /// + /// The continuation token for the specified page. + public override ContinuationToken GetContinuationToken(ClientResult page) + { + return null; + } + + /// Sends the request in the pipeline message and returns the response. + /// The pipeline message containing the request to send. + private async ValueTask GetNextResponseAsync(PipelineMessage message) + { + return ClientResult.FromResponse(await _client.Pipeline.ProcessMessageAsync(message, _options).ConfigureAwait(false)); + } + } +} +#pragma warning restore SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning restore SAMPLE0009 // This generated code depends on experimental functionality. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/ExperimentalSamplesGetAllAsyncCollectionResultOfT.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/ExperimentalSamplesGetAllAsyncCollectionResultOfT.cs new file mode 100644 index 00000000000..562396dbb8c --- /dev/null +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/ExperimentalSamplesGetAllAsyncCollectionResultOfT.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Threading.Tasks; + +#pragma warning disable SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning disable SAMPLE0009 // This generated code depends on experimental functionality. +namespace SampleTypeSpec +{ + internal partial class ExperimentalSamplesGetAllAsyncCollectionResultOfT : AsyncCollectionResult + { + private readonly ExperimentalSamples _client; + private readonly RequestOptions _options; + + /// Initializes a new instance of ExperimentalSamplesGetAllAsyncCollectionResultOfT, which is used to iterate over the pages of a collection. + /// The ExperimentalSamples client used to send requests. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + public ExperimentalSamplesGetAllAsyncCollectionResultOfT(ExperimentalSamples client, RequestOptions options) + { + _client = client; + _options = options; + } + + /// Gets the raw pages of the collection. + /// The raw pages of the collection. + public override async IAsyncEnumerable GetRawPagesAsync() + { + PipelineMessage message = _client.CreateGetAllRequest(_options); + yield return await GetNextResponseAsync(message).ConfigureAwait(false); + } + + /// Gets the continuation token from the specified page. + /// + /// The continuation token for the specified page. + public override ContinuationToken GetContinuationToken(ClientResult page) + { + return null; + } + + /// Gets the values from the specified page. + /// + /// The values from the specified page. + protected override async IAsyncEnumerable GetValuesFromPageAsync(ClientResult page) + { + foreach (PreviewDetails item in ((PagePreviewDetails)page).Items) + { + yield return item; + await Task.Yield(); + } + } + + /// Sends the request in the pipeline message and returns the response. + /// The pipeline message containing the request to send. + private async ValueTask GetNextResponseAsync(PipelineMessage message) + { + return ClientResult.FromResponse(await _client.Pipeline.ProcessMessageAsync(message, _options).ConfigureAwait(false)); + } + } +} +#pragma warning restore SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning restore SAMPLE0009 // This generated code depends on experimental functionality. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/ExperimentalSamplesGetAllCollectionResult.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/ExperimentalSamplesGetAllCollectionResult.cs new file mode 100644 index 00000000000..36b62d78943 --- /dev/null +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/ExperimentalSamplesGetAllCollectionResult.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; + +#pragma warning disable SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning disable SAMPLE0009 // This generated code depends on experimental functionality. +namespace SampleTypeSpec +{ + internal partial class ExperimentalSamplesGetAllCollectionResult : CollectionResult + { + private readonly ExperimentalSamples _client; + private readonly RequestOptions _options; + + /// Initializes a new instance of ExperimentalSamplesGetAllCollectionResult, which is used to iterate over the pages of a collection. + /// The ExperimentalSamples client used to send requests. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + public ExperimentalSamplesGetAllCollectionResult(ExperimentalSamples client, RequestOptions options) + { + _client = client; + _options = options; + } + + /// Gets the raw pages of the collection. + /// The raw pages of the collection. + public override IEnumerable GetRawPages() + { + PipelineMessage message = _client.CreateGetAllRequest(_options); + yield return GetNextResponse(message); + } + + /// Gets the continuation token from the specified page. + /// + /// The continuation token for the specified page. + public override ContinuationToken GetContinuationToken(ClientResult page) + { + return null; + } + + /// Sends the request in the pipeline message and returns the response. + /// The pipeline message containing the request to send. + private ClientResult GetNextResponse(PipelineMessage message) + { + return ClientResult.FromResponse(_client.Pipeline.ProcessMessage(message, _options)); + } + } +} +#pragma warning restore SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning restore SAMPLE0009 // This generated code depends on experimental functionality. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/ExperimentalSamplesGetAllCollectionResultOfT.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/ExperimentalSamplesGetAllCollectionResultOfT.cs new file mode 100644 index 00000000000..8264764d4ca --- /dev/null +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/ExperimentalSamplesGetAllCollectionResultOfT.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; + +#pragma warning disable SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning disable SAMPLE0009 // This generated code depends on experimental functionality. +namespace SampleTypeSpec +{ + internal partial class ExperimentalSamplesGetAllCollectionResultOfT : CollectionResult + { + private readonly ExperimentalSamples _client; + private readonly RequestOptions _options; + + /// Initializes a new instance of ExperimentalSamplesGetAllCollectionResultOfT, which is used to iterate over the pages of a collection. + /// The ExperimentalSamples client used to send requests. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + public ExperimentalSamplesGetAllCollectionResultOfT(ExperimentalSamples client, RequestOptions options) + { + _client = client; + _options = options; + } + + /// Gets the raw pages of the collection. + /// The raw pages of the collection. + public override IEnumerable GetRawPages() + { + PipelineMessage message = _client.CreateGetAllRequest(_options); + yield return GetNextResponse(message); + } + + /// Gets the continuation token from the specified page. + /// + /// The continuation token for the specified page. + public override ContinuationToken GetContinuationToken(ClientResult page) + { + return null; + } + + /// Gets the values from the specified page. + /// + /// The values from the specified page. + protected override IEnumerable GetValuesFromPage(ClientResult page) + { + return ((PagePreviewDetails)page).Items; + } + + /// Sends the request in the pipeline message and returns the response. + /// The pipeline message containing the request to send. + private ClientResult GetNextResponse(PipelineMessage message) + { + return ClientResult.FromResponse(_client.Pipeline.ProcessMessage(message, _options)); + } + } +} +#pragma warning restore SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning restore SAMPLE0009 // This generated code depends on experimental functionality. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/ExperimentalSamples.RestClient.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/ExperimentalSamples.RestClient.cs new file mode 100644 index 00000000000..5aecfa4224c --- /dev/null +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/ExperimentalSamples.RestClient.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System.ClientModel.Primitives; + +#pragma warning disable SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning disable SAMPLE0009 // This generated code depends on experimental functionality. +namespace SampleTypeSpec +{ + /// + public partial class ExperimentalSamples + { + private static PipelineMessageClassifier _pipelineMessageClassifier200; + + private static PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); + + internal PipelineMessage CreateReadRequest(RequestOptions options) + { + ClientUriBuilder uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/experimental", false); + PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier200); + PipelineRequest request = message.Request; + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + internal PipelineMessage CreateGetAllRequest(RequestOptions options) + { + ClientUriBuilder uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/experimental/pages", false); + PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier200); + PipelineRequest request = message.Request; + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + } +} +#pragma warning restore SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning restore SAMPLE0009 // This generated code depends on experimental functionality. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/ExperimentalSamples.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/ExperimentalSamples.cs new file mode 100644 index 00000000000..f45f4ca05b3 --- /dev/null +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/ExperimentalSamples.cs @@ -0,0 +1,144 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +#pragma warning disable SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning disable SAMPLE0009 // This generated code depends on experimental functionality. +namespace SampleTypeSpec +{ + /// The ExperimentalSamples sub-client. + [Experimental("SAMPLE0009")] + public partial class ExperimentalSamples + { + private readonly Uri _endpoint; + + /// Initializes a new instance of ExperimentalSamples for mocking. + protected ExperimentalSamples() + { + } + + /// Initializes a new instance of ExperimentalSamples. + /// The HTTP pipeline for sending and receiving REST requests and responses. + /// Service endpoint. + internal ExperimentalSamples(ClientPipeline pipeline, Uri endpoint) + { + _endpoint = endpoint; + Pipeline = pipeline; + } + + /// The HTTP pipeline for sending and receiving REST requests and responses. + public ClientPipeline Pipeline { get; } + + /// + /// [Protocol Method] Read + /// + /// + /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. + /// + /// + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual ClientResult Read(RequestOptions options) + { + using PipelineMessage message = CreateReadRequest(options); + return ClientResult.FromResponse(Pipeline.ProcessMessage(message, options)); + } + + /// + /// [Protocol Method] Read + /// + /// + /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. + /// + /// + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual async Task ReadAsync(RequestOptions options) + { + using PipelineMessage message = CreateReadRequest(options); + return ClientResult.FromResponse(await Pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + /// Read. + /// The cancellation token that can be used to cancel the operation. + /// Service returned a non-success status code. + public virtual ClientResult Read(CancellationToken cancellationToken = default) + { + ClientResult result = Read(cancellationToken.ToRequestOptions()); + return ClientResult.FromValue((LifecycleModel)result, result.GetRawResponse()); + } + + /// Read. + /// The cancellation token that can be used to cancel the operation. + /// Service returned a non-success status code. + public virtual async Task> ReadAsync(CancellationToken cancellationToken = default) + { + ClientResult result = await ReadAsync(cancellationToken.ToRequestOptions()).ConfigureAwait(false); + return ClientResult.FromValue((LifecycleModel)result, result.GetRawResponse()); + } + + /// + /// [Protocol Method] GetAll + /// + /// + /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. + /// + /// + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual CollectionResult GetAll(RequestOptions options) + { + return new ExperimentalSamplesGetAllCollectionResult(this, options); + } + + /// + /// [Protocol Method] GetAll + /// + /// + /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. + /// + /// + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual AsyncCollectionResult GetAllAsync(RequestOptions options) + { + return new ExperimentalSamplesGetAllAsyncCollectionResult(this, options); + } + + /// GetAll. + /// The cancellation token that can be used to cancel the operation. + /// Service returned a non-success status code. + public virtual CollectionResult GetAll(CancellationToken cancellationToken = default) + { + return new ExperimentalSamplesGetAllCollectionResultOfT(this, cancellationToken.ToRequestOptions()); + } + + /// GetAll. + /// The cancellation token that can be used to cancel the operation. + /// Service returned a non-success status code. + public virtual AsyncCollectionResult GetAllAsync(CancellationToken cancellationToken = default) + { + return new ExperimentalSamplesGetAllAsyncCollectionResultOfT(this, cancellationToken.ToRequestOptions()); + } + } +} +#pragma warning restore SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning restore SAMPLE0009 // This generated code depends on experimental functionality. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/LifecycleModel.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/LifecycleModel.Serialization.cs new file mode 100644 index 00000000000..c17e51fd071 --- /dev/null +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/LifecycleModel.Serialization.cs @@ -0,0 +1,172 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Text.Json; + +#pragma warning disable SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning disable SAMPLE0005 // This generated code depends on experimental functionality. +#pragma warning disable SAMPLE0008 // This generated code depends on experimental functionality. +namespace SampleTypeSpec +{ + /// The LifecycleModel. + public partial class LifecycleModel : IJsonModel + { + /// Initializes a new instance of for deserialization. + internal LifecycleModel() + { + } + + /// The data to parse. + /// The client options for reading and writing models. + protected virtual LifecycleModel PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) + { + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + switch (format) + { + case "J": + using (JsonDocument document = JsonDocument.Parse(data, ModelSerializationExtensions.JsonDocumentOptions)) + { + return DeserializeLifecycleModel(document.RootElement, options); + } + default: + throw new FormatException($"The model {nameof(LifecycleModel)} does not support reading '{options.Format}' format."); + } + } + + /// The client options for reading and writing models. + protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options) + { + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + switch (format) + { + case "J": + return ModelReaderWriter.Write(this, options, SampleTypeSpecContext.Default); + default: + throw new FormatException($"The model {nameof(LifecycleModel)} does not support writing '{options.Format}' format."); + } + } + + /// The client options for reading and writing models. + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); + + /// The data to parse. + /// The client options for reading and writing models. + LifecycleModel IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); + + /// The client options for reading and writing models. + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + /// The to deserialize the from. + public static explicit operator LifecycleModel(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content, ModelSerializationExtensions.JsonDocumentOptions); + return DeserializeLifecycleModel(document.RootElement, ModelSerializationExtensions.WireOptions); + } + + /// The JSON writer. + /// The client options for reading and writing models. + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + { + writer.WriteStartObject(); + JsonModelWriteCore(writer, options); + writer.WriteEndObject(); + } + + /// The JSON writer. + /// The client options for reading and writing models. + protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options) + { + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if (format != "J") + { + throw new FormatException($"The model {nameof(LifecycleModel)} does not support writing '{format}' format."); + } + if (Optional.IsDefined(Preview)) + { + writer.WritePropertyName("preview"u8); + writer.WriteObjectValue(Preview, options); + } + writer.WritePropertyName("choice"u8); + writer.WriteStringValue(Choice.ToString()); + if (options.Format != "W" && _additionalBinaryDataProperties != null) + { + foreach (var item in _additionalBinaryDataProperties) + { + writer.WritePropertyName(item.Key); +#if NET6_0_OR_GREATER + writer.WriteRawValue(item.Value); +#else + using (JsonDocument document = JsonDocument.Parse(item.Value)) + { + JsonSerializer.Serialize(writer, document.RootElement); + } +#endif + } + } + } + + /// The JSON reader. + /// The client options for reading and writing models. + LifecycleModel IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); + + /// The JSON reader. + /// The client options for reading and writing models. + protected virtual LifecycleModel JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + { + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if (format != "J") + { + throw new FormatException($"The model {nameof(LifecycleModel)} does not support reading '{format}' format."); + } + using JsonDocument document = JsonDocument.ParseValue(ref reader); + return DeserializeLifecycleModel(document.RootElement, options); + } + + /// The JSON element to deserialize. + /// The client options for reading and writing models. + internal static LifecycleModel DeserializeLifecycleModel(JsonElement element, ModelReaderWriterOptions options) + { + if (element.ValueKind == JsonValueKind.Null) + { + return null; + } + PreviewDetails preview = default; + PreviewExtensibleChoice choice = default; + IDictionary additionalBinaryDataProperties = new ChangeTrackingDictionary(); + foreach (var prop in element.EnumerateObject()) + { + if (prop.NameEquals("preview"u8)) + { + if (prop.Value.ValueKind == JsonValueKind.Null) + { + continue; + } + preview = PreviewDetails.DeserializePreviewDetails(prop.Value, options); + continue; + } + if (prop.NameEquals("choice"u8)) + { + choice = new PreviewExtensibleChoice(prop.Value.GetString()); + continue; + } + if (options.Format != "W") + { + additionalBinaryDataProperties.Add(prop.Name, prop.Value.GetUtf8Bytes()); + } + } + return new LifecycleModel(preview, choice, additionalBinaryDataProperties); + } + } +} +#pragma warning restore SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning restore SAMPLE0005 // This generated code depends on experimental functionality. +#pragma warning restore SAMPLE0008 // This generated code depends on experimental functionality. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/LifecycleModel.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/LifecycleModel.cs new file mode 100644 index 00000000000..3090e862234 --- /dev/null +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/LifecycleModel.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +#pragma warning disable SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning disable SAMPLE0005 // This generated code depends on experimental functionality. +#pragma warning disable SAMPLE0008 // This generated code depends on experimental functionality. +namespace SampleTypeSpec +{ + /// The LifecycleModel. + public partial class LifecycleModel + { + /// Keeps track of any properties unknown to the library. + private protected readonly IDictionary _additionalBinaryDataProperties; + + /// Initializes a new instance of . + /// + internal LifecycleModel(PreviewExtensibleChoice choice) + { + Choice = choice; + } + + /// Initializes a new instance of . + /// + /// + /// Keeps track of any properties unknown to the library. + internal LifecycleModel(PreviewDetails preview, PreviewExtensibleChoice choice, IDictionary additionalBinaryDataProperties) + { + Preview = preview; + Choice = choice; + _additionalBinaryDataProperties = additionalBinaryDataProperties; + } + + /// Gets the Preview. + [Experimental("SAMPLE0008")] + public PreviewDetails Preview { get; } + + /// Gets the Choice. + public PreviewExtensibleChoice Choice { get; } + } +} +#pragma warning restore SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning restore SAMPLE0005 // This generated code depends on experimental functionality. +#pragma warning restore SAMPLE0008 // This generated code depends on experimental functionality. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PagePreviewDetails.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PagePreviewDetails.Serialization.cs new file mode 100644 index 00000000000..6f178c4c0e1 --- /dev/null +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PagePreviewDetails.Serialization.cs @@ -0,0 +1,163 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Text.Json; + +#pragma warning disable SAMPLE0003 // This generated code depends on experimental functionality. +namespace SampleTypeSpec +{ + /// The PagePreviewDetails. + internal partial class PagePreviewDetails : IJsonModel + { + /// Initializes a new instance of for deserialization. + internal PagePreviewDetails() + { + } + + /// The data to parse. + /// The client options for reading and writing models. + protected virtual PagePreviewDetails PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) + { + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + switch (format) + { + case "J": + using (JsonDocument document = JsonDocument.Parse(data, ModelSerializationExtensions.JsonDocumentOptions)) + { + return DeserializePagePreviewDetails(document.RootElement, options); + } + default: + throw new FormatException($"The model {nameof(PagePreviewDetails)} does not support reading '{options.Format}' format."); + } + } + + /// The client options for reading and writing models. + protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options) + { + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + switch (format) + { + case "J": + return ModelReaderWriter.Write(this, options, SampleTypeSpecContext.Default); + default: + throw new FormatException($"The model {nameof(PagePreviewDetails)} does not support writing '{options.Format}' format."); + } + } + + /// The client options for reading and writing models. + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); + + /// The data to parse. + /// The client options for reading and writing models. + PagePreviewDetails IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); + + /// The client options for reading and writing models. + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + /// The to deserialize the from. + public static explicit operator PagePreviewDetails(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content, ModelSerializationExtensions.JsonDocumentOptions); + return DeserializePagePreviewDetails(document.RootElement, ModelSerializationExtensions.WireOptions); + } + + /// The JSON writer. + /// The client options for reading and writing models. + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + { + writer.WriteStartObject(); + JsonModelWriteCore(writer, options); + writer.WriteEndObject(); + } + + /// The JSON writer. + /// The client options for reading and writing models. + protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options) + { + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if (format != "J") + { + throw new FormatException($"The model {nameof(PagePreviewDetails)} does not support writing '{format}' format."); + } + writer.WritePropertyName("items"u8); + writer.WriteStartArray(); + foreach (PreviewDetails item in Items) + { + writer.WriteObjectValue(item, options); + } + writer.WriteEndArray(); + if (options.Format != "W" && _additionalBinaryDataProperties != null) + { + foreach (var item in _additionalBinaryDataProperties) + { + writer.WritePropertyName(item.Key); +#if NET6_0_OR_GREATER + writer.WriteRawValue(item.Value); +#else + using (JsonDocument document = JsonDocument.Parse(item.Value)) + { + JsonSerializer.Serialize(writer, document.RootElement); + } +#endif + } + } + } + + /// The JSON reader. + /// The client options for reading and writing models. + PagePreviewDetails IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); + + /// The JSON reader. + /// The client options for reading and writing models. + protected virtual PagePreviewDetails JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + { + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if (format != "J") + { + throw new FormatException($"The model {nameof(PagePreviewDetails)} does not support reading '{format}' format."); + } + using JsonDocument document = JsonDocument.ParseValue(ref reader); + return DeserializePagePreviewDetails(document.RootElement, options); + } + + /// The JSON element to deserialize. + /// The client options for reading and writing models. + internal static PagePreviewDetails DeserializePagePreviewDetails(JsonElement element, ModelReaderWriterOptions options) + { + if (element.ValueKind == JsonValueKind.Null) + { + return null; + } + IList items = default; + IDictionary additionalBinaryDataProperties = new ChangeTrackingDictionary(); + foreach (var prop in element.EnumerateObject()) + { + if (prop.NameEquals("items"u8)) + { + List array = new List(); + foreach (var item in prop.Value.EnumerateArray()) + { + array.Add(PreviewDetails.DeserializePreviewDetails(item, options)); + } + items = array; + continue; + } + if (options.Format != "W") + { + additionalBinaryDataProperties.Add(prop.Name, prop.Value.GetUtf8Bytes()); + } + } + return new PagePreviewDetails(items, additionalBinaryDataProperties); + } + } +} +#pragma warning restore SAMPLE0003 // This generated code depends on experimental functionality. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PagePreviewDetails.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PagePreviewDetails.cs new file mode 100644 index 00000000000..9addb6b64f2 --- /dev/null +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PagePreviewDetails.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Linq; + +#pragma warning disable SAMPLE0003 // This generated code depends on experimental functionality. +namespace SampleTypeSpec +{ + /// The PagePreviewDetails. + internal partial class PagePreviewDetails + { + /// Keeps track of any properties unknown to the library. + private protected readonly IDictionary _additionalBinaryDataProperties; + + /// Initializes a new instance of . + /// + internal PagePreviewDetails(IEnumerable items) + { + Items = items.ToList(); + } + + /// Initializes a new instance of . + /// + /// Keeps track of any properties unknown to the library. + internal PagePreviewDetails(IList items, IDictionary additionalBinaryDataProperties) + { + Items = items; + _additionalBinaryDataProperties = additionalBinaryDataProperties; + } + + /// Gets the Items. + public IList Items { get; } + } +} +#pragma warning restore SAMPLE0003 // This generated code depends on experimental functionality. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PreviewChoice.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PreviewChoice.Serialization.cs new file mode 100644 index 00000000000..e96882ea96a --- /dev/null +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PreviewChoice.Serialization.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; + +#pragma warning disable SAMPLE0004 // This generated code depends on experimental functionality. +#pragma warning disable SAMPLE0007 // This generated code depends on experimental functionality. +namespace SampleTypeSpec +{ + internal static partial class PreviewChoiceExtensions + { + /// The value to serialize. + public static string ToSerialString(this PreviewChoice value) => value switch + { + PreviewChoice.One => "one", + PreviewChoice.Two => "two", + _ => throw new ArgumentOutOfRangeException(nameof(value), value, "Unknown PreviewChoice value.") + }; + + /// The value to deserialize. + public static PreviewChoice ToPreviewChoice(this string value) + { + if (StringComparer.OrdinalIgnoreCase.Equals(value, "one")) + { + return PreviewChoice.One; + } + if (StringComparer.OrdinalIgnoreCase.Equals(value, "two")) + { + return PreviewChoice.Two; + } + throw new ArgumentOutOfRangeException(nameof(value), value, "Unknown PreviewChoice value."); + } + } +} +#pragma warning restore SAMPLE0004 // This generated code depends on experimental functionality. +#pragma warning restore SAMPLE0007 // This generated code depends on experimental functionality. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PreviewChoice.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PreviewChoice.cs new file mode 100644 index 00000000000..9656d887166 --- /dev/null +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PreviewChoice.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System.Diagnostics.CodeAnalysis; + +#pragma warning disable SAMPLE0004 // This generated code depends on experimental functionality. +#pragma warning disable SAMPLE0007 // This generated code depends on experimental functionality. +namespace SampleTypeSpec +{ + /// + [Experimental("SAMPLE0004")] + public enum PreviewChoice + { + /// One. + One, + /// Two. + [Experimental("SAMPLE0007")] + Two + } +} +#pragma warning restore SAMPLE0004 // This generated code depends on experimental functionality. +#pragma warning restore SAMPLE0007 // This generated code depends on experimental functionality. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PreviewDetails.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PreviewDetails.Serialization.cs new file mode 100644 index 00000000000..d7e75966c6d --- /dev/null +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PreviewDetails.Serialization.cs @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Text.Json; + +#pragma warning disable SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning disable SAMPLE0004 // This generated code depends on experimental functionality. +namespace SampleTypeSpec +{ + /// The PreviewDetails. + public partial class PreviewDetails : IJsonModel + { + /// Initializes a new instance of for deserialization. + internal PreviewDetails() + { + } + + /// The data to parse. + /// The client options for reading and writing models. + protected virtual PreviewDetails PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) + { + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + switch (format) + { + case "J": + using (JsonDocument document = JsonDocument.Parse(data, ModelSerializationExtensions.JsonDocumentOptions)) + { + return DeserializePreviewDetails(document.RootElement, options); + } + default: + throw new FormatException($"The model {nameof(PreviewDetails)} does not support reading '{options.Format}' format."); + } + } + + /// The client options for reading and writing models. + protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options) + { + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + switch (format) + { + case "J": + return ModelReaderWriter.Write(this, options, SampleTypeSpecContext.Default); + default: + throw new FormatException($"The model {nameof(PreviewDetails)} does not support writing '{options.Format}' format."); + } + } + + /// The client options for reading and writing models. + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); + + /// The data to parse. + /// The client options for reading and writing models. + PreviewDetails IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); + + /// The client options for reading and writing models. + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + /// The JSON writer. + /// The client options for reading and writing models. + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + { + writer.WriteStartObject(); + JsonModelWriteCore(writer, options); + writer.WriteEndObject(); + } + + /// The JSON writer. + /// The client options for reading and writing models. + protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options) + { + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if (format != "J") + { + throw new FormatException($"The model {nameof(PreviewDetails)} does not support writing '{format}' format."); + } + writer.WritePropertyName("choice"u8); + writer.WriteStringValue(Choice.ToSerialString()); + if (options.Format != "W" && _additionalBinaryDataProperties != null) + { + foreach (var item in _additionalBinaryDataProperties) + { + writer.WritePropertyName(item.Key); +#if NET6_0_OR_GREATER + writer.WriteRawValue(item.Value); +#else + using (JsonDocument document = JsonDocument.Parse(item.Value)) + { + JsonSerializer.Serialize(writer, document.RootElement); + } +#endif + } + } + } + + /// The JSON reader. + /// The client options for reading and writing models. + PreviewDetails IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); + + /// The JSON reader. + /// The client options for reading and writing models. + protected virtual PreviewDetails JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + { + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if (format != "J") + { + throw new FormatException($"The model {nameof(PreviewDetails)} does not support reading '{format}' format."); + } + using JsonDocument document = JsonDocument.ParseValue(ref reader); + return DeserializePreviewDetails(document.RootElement, options); + } + + /// The JSON element to deserialize. + /// The client options for reading and writing models. + internal static PreviewDetails DeserializePreviewDetails(JsonElement element, ModelReaderWriterOptions options) + { + if (element.ValueKind == JsonValueKind.Null) + { + return null; + } + PreviewChoice choice = default; + IDictionary additionalBinaryDataProperties = new ChangeTrackingDictionary(); + foreach (var prop in element.EnumerateObject()) + { + if (prop.NameEquals("choice"u8)) + { + choice = prop.Value.GetString().ToPreviewChoice(); + continue; + } + if (options.Format != "W") + { + additionalBinaryDataProperties.Add(prop.Name, prop.Value.GetUtf8Bytes()); + } + } + return new PreviewDetails(choice, additionalBinaryDataProperties); + } + } +} +#pragma warning restore SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning restore SAMPLE0004 // This generated code depends on experimental functionality. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PreviewDetails.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PreviewDetails.cs new file mode 100644 index 00000000000..f4bea99bc4d --- /dev/null +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PreviewDetails.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +#pragma warning disable SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning disable SAMPLE0004 // This generated code depends on experimental functionality. +namespace SampleTypeSpec +{ + /// The PreviewDetails. + [Experimental("SAMPLE0003")] + public partial class PreviewDetails + { + /// Keeps track of any properties unknown to the library. + private protected readonly IDictionary _additionalBinaryDataProperties; + + /// Initializes a new instance of . + /// + internal PreviewDetails(PreviewChoice choice) + { + Choice = choice; + } + + /// Initializes a new instance of . + /// + /// Keeps track of any properties unknown to the library. + internal PreviewDetails(PreviewChoice choice, IDictionary additionalBinaryDataProperties) + { + Choice = choice; + _additionalBinaryDataProperties = additionalBinaryDataProperties; + } + + /// Gets the Choice. + public PreviewChoice Choice { get; } + } +} +#pragma warning restore SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning restore SAMPLE0004 // This generated code depends on experimental functionality. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PreviewExtensibleChoice.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PreviewExtensibleChoice.cs new file mode 100644 index 00000000000..2af0806965c --- /dev/null +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PreviewExtensibleChoice.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; + +#pragma warning disable SAMPLE0005 // This generated code depends on experimental functionality. +#pragma warning disable SAMPLE0006 // This generated code depends on experimental functionality. +namespace SampleTypeSpec +{ + /// + [Experimental("SAMPLE0005")] + public readonly partial struct PreviewExtensibleChoice : IEquatable + { + private readonly string _value; + private const string OneValue = "one"; + private const string TwoValue = "two"; + + /// Initializes a new instance of . + /// The value. + /// is null. + public PreviewExtensibleChoice(string value) + { + Argument.AssertNotNull(value, nameof(value)); + + _value = value; + } + + /// Gets the One. + public static PreviewExtensibleChoice One { get; } = new PreviewExtensibleChoice(OneValue); + + /// Gets the Two. + [Experimental("SAMPLE0006")] + public static PreviewExtensibleChoice Two { get; } = new PreviewExtensibleChoice(TwoValue); + + /// Determines if two values are the same. + /// The left value to compare. + /// The right value to compare. + public static bool operator ==(PreviewExtensibleChoice left, PreviewExtensibleChoice right) => left.Equals(right); + + /// Determines if two values are not the same. + /// The left value to compare. + /// The right value to compare. + public static bool operator !=(PreviewExtensibleChoice left, PreviewExtensibleChoice right) => !left.Equals(right); + + /// Converts a string to a . + /// The value. + public static implicit operator PreviewExtensibleChoice(string value) => new PreviewExtensibleChoice(value); + + /// Converts a string to a . + /// The value. + public static implicit operator PreviewExtensibleChoice?(string value) => value == null ? null : new PreviewExtensibleChoice(value); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object obj) => obj is PreviewExtensibleChoice other && Equals(other); + + /// + public bool Equals(PreviewExtensibleChoice other) => string.Equals(_value, other._value, StringComparison.InvariantCultureIgnoreCase); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => _value != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(_value) : 0; + + /// + public override string ToString() => _value; + } +} +#pragma warning restore SAMPLE0005 // This generated code depends on experimental functionality. +#pragma warning restore SAMPLE0006 // This generated code depends on experimental functionality. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/SampleTypeSpecContext.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/SampleTypeSpecContext.cs index b58f7f1b7f7..a958054dc1d 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/SampleTypeSpecContext.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/SampleTypeSpecContext.cs @@ -21,6 +21,7 @@ namespace SampleTypeSpec [ModelReaderWriterBuildable(typeof(Friend))] [ModelReaderWriterBuildable(typeof(GetNotebookResponse))] [ModelReaderWriterBuildable(typeof(GetWidgetMetricsResponse))] + [ModelReaderWriterBuildable(typeof(LifecycleModel))] [ModelReaderWriterBuildable(typeof(ListWithContinuationTokenHeaderResponseResponse))] [ModelReaderWriterBuildable(typeof(ListWithContinuationTokenResponse))] [ModelReaderWriterBuildable(typeof(ListWithNextLinkResponse))] @@ -28,9 +29,13 @@ namespace SampleTypeSpec [ModelReaderWriterBuildable(typeof(ModelWithEmbeddedNonBodyParameters))] [ModelReaderWriterBuildable(typeof(ModelWithRequiredNullableProperties))] [ModelReaderWriterBuildable(typeof(NullableDynamicModel))] + [ModelReaderWriterBuildable(typeof(PagePreviewDetails))] [ModelReaderWriterBuildable(typeof(PageThing))] [ModelReaderWriterBuildable(typeof(Pet))] [ModelReaderWriterBuildable(typeof(Plant))] +#pragma warning disable SAMPLE0003 // global::SampleTypeSpec.PreviewDetails is experimental and may change in future versions. + [ModelReaderWriterBuildable(typeof(PreviewDetails))] +#pragma warning restore SAMPLE0003 // global::SampleTypeSpec.PreviewDetails is experimental and may change in future versions. [ModelReaderWriterBuildable(typeof(RenamedModelCustom))] [ModelReaderWriterBuildable(typeof(ReturnsAnonymousModelResponse))] [ModelReaderWriterBuildable(typeof(RoundTripModel))] diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.RestClient.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.RestClient.cs index a737e1e8b31..73241c04026 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.RestClient.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.RestClient.cs @@ -9,6 +9,8 @@ using System.ClientModel; using System.ClientModel.Primitives; +#pragma warning disable SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning disable SAMPLE0009 // This generated code depends on experimental functionality. namespace SampleTypeSpec { /// @@ -411,7 +413,7 @@ internal PipelineMessage CreateEmbeddedParametersRequest(string requiredHeader, return message; } -#pragma warning disable SCME0001 // This method depends on experimental functionality. +#pragma warning disable SCME0001 // This generated code depends on experimental functionality. internal PipelineMessage CreateDynamicModelOperationRequest(BinaryContent content, RequestOptions options) { ClientUriBuilder uri = new ClientUriBuilder(); @@ -424,7 +426,7 @@ internal PipelineMessage CreateDynamicModelOperationRequest(BinaryContent conten message.Apply(options); return message; } -#pragma warning restore SCME0001 // This method depends on experimental functionality. +#pragma warning restore SCME0001 // This generated code depends on experimental functionality. internal PipelineMessage CreateGetXmlAdvancedModelRequest(RequestOptions options) { @@ -501,5 +503,19 @@ internal PipelineMessage CreateReceiveSseRequest(RequestOptions options) message.Apply(options); return message; } + + internal PipelineMessage CreateReceiveExperimentalJsonLinesRequest(RequestOptions options) + { + ClientUriBuilder uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/experimental/jsonl", false); + PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier200); + PipelineRequest request = message.Request; + request.Headers.Set("Accept", "application/jsonl"); + message.Apply(options); + return message; + } } } +#pragma warning restore SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning restore SAMPLE0009 // This generated code depends on experimental functionality. 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 9d60265288c..3cb5087d0bb 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 @@ -16,6 +16,8 @@ using System.Threading.Tasks; using SampleTypeSpec.Models.Custom; +#pragma warning disable SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning disable SAMPLE0009 // This generated code depends on experimental functionality. namespace SampleTypeSpec { /// This is a sample typespec project. @@ -34,6 +36,7 @@ public partial class SampleTypeSpecClient } }; private readonly string _apiVersion; + private ExperimentalSamples _cachedExperimentalSamples; private AnimalOperations _cachedAnimalOperations; private PetOperations _cachedPetOperations; private DogOperations _cachedDogOperations; @@ -1697,7 +1700,7 @@ public virtual async Task EmbeddedParametersAsync(ModelWithEmbedde /// is null. /// Service returned a non-success status code. /// The response returned from the service. -#pragma warning disable SCME0001 // This method depends on experimental functionality. +#pragma warning disable SCME0001 // This generated code depends on experimental functionality. [Experimental("SAMPLE0002")] public virtual ClientResult DynamicModelOperation(BinaryContent content, RequestOptions options = null) { @@ -1706,7 +1709,7 @@ public virtual ClientResult DynamicModelOperation(BinaryContent content, Request using PipelineMessage message = CreateDynamicModelOperationRequest(content, options); return ClientResult.FromResponse(Pipeline.ProcessMessage(message, options)); } -#pragma warning restore SCME0001 // This method depends on experimental functionality. +#pragma warning restore SCME0001 // This generated code depends on experimental functionality. /// /// [Protocol Method] An operation with a dynamic model @@ -1721,7 +1724,7 @@ public virtual ClientResult DynamicModelOperation(BinaryContent content, Request /// is null. /// Service returned a non-success status code. /// The response returned from the service. -#pragma warning disable SCME0001 // This method depends on experimental functionality. +#pragma warning disable SCME0001 // This generated code depends on experimental functionality. [Experimental("SAMPLE0002")] public virtual async Task DynamicModelOperationAsync(BinaryContent content, RequestOptions options = null) { @@ -1730,14 +1733,14 @@ public virtual async Task DynamicModelOperationAsync(BinaryContent using PipelineMessage message = CreateDynamicModelOperationRequest(content, options); return ClientResult.FromResponse(await Pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); } -#pragma warning restore SCME0001 // This method depends on experimental functionality. +#pragma warning restore SCME0001 // This generated code depends on experimental functionality. /// An operation with a dynamic model. /// /// The cancellation token that can be used to cancel the operation. /// is null. /// Service returned a non-success status code. -#pragma warning disable SCME0001 // This method depends on experimental functionality. +#pragma warning disable SCME0001 // This generated code depends on experimental functionality. [Experimental("SAMPLE0002")] public virtual ClientResult DynamicModelOperation(DynamicModel body, CancellationToken cancellationToken = default) { @@ -1745,14 +1748,14 @@ public virtual ClientResult DynamicModelOperation(DynamicModel body, Cancellatio return DynamicModelOperation(body, cancellationToken.ToRequestOptions()); } -#pragma warning restore SCME0001 // This method depends on experimental functionality. +#pragma warning restore SCME0001 // This generated code depends on experimental functionality. /// An operation with a dynamic model. /// /// The cancellation token that can be used to cancel the operation. /// is null. /// Service returned a non-success status code. -#pragma warning disable SCME0001 // This method depends on experimental functionality. +#pragma warning disable SCME0001 // This generated code depends on experimental functionality. [Experimental("SAMPLE0002")] public virtual async Task DynamicModelOperationAsync(DynamicModel body, CancellationToken cancellationToken = default) { @@ -1760,7 +1763,7 @@ public virtual async Task DynamicModelOperationAsync(DynamicModel return await DynamicModelOperationAsync(body, cancellationToken.ToRequestOptions()).ConfigureAwait(false); } -#pragma warning restore SCME0001 // This method depends on experimental functionality. +#pragma warning restore SCME0001 // This generated code depends on experimental functionality. /// /// [Protocol Method] Get an advanced XML model with various property types @@ -2069,6 +2072,40 @@ public virtual async Task>> ReceiveS return AsyncStreamingResult.CreateSse(await Pipeline.ProcessMessageAsync(message, cancellationToken.ToRequestOptions()).ConfigureAwait(false), (@_, data) => ModelReaderWriter.Read(BinaryData.FromBytes(data.ToArray()), ModelSerializationExtensions.WireOptions, SampleTypeSpecContext.Default), item => item.Data.ToString() == "[DONE]", cancellationToken); } + /// + /// [Protocol Method] ReceiveExperimentalJsonLines + /// + /// + /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. + /// + /// + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual async Task> ReceiveExperimentalJsonLinesAsync(RequestOptions options) + { + using PipelineMessage message = CreateReceiveExperimentalJsonLinesRequest(options); + message.BufferResponse = false; + return AsyncStreamingResult.CreateJsonLines(await Pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + /// ReceiveExperimentalJsonLines. + /// The cancellation token that can be used to cancel the operation. + /// Service returned a non-success status code. + public virtual async Task> ReceiveExperimentalJsonLinesAsync(CancellationToken cancellationToken = default) + { + using PipelineMessage message = CreateReceiveExperimentalJsonLinesRequest(cancellationToken.ToRequestOptions()); + message.BufferResponse = false; + return AsyncStreamingResult.CreateJsonLines(await Pipeline.ProcessMessageAsync(message, cancellationToken.ToRequestOptions()).ConfigureAwait(false), data => ModelReaderWriter.Read(data, ModelSerializationExtensions.WireOptions, SampleTypeSpecContext.Default), cancellationToken); + } + + /// Initializes a new instance of ExperimentalSamples. + public virtual ExperimentalSamples GetExperimentalSamplesClient() + { + return Volatile.Read(ref _cachedExperimentalSamples) ?? Interlocked.CompareExchange(ref _cachedExperimentalSamples, new ExperimentalSamples(Pipeline, _endpoint), null) ?? _cachedExperimentalSamples; + } + /// Initializes a new instance of AnimalOperations. public virtual AnimalOperations GetAnimalOperationsClient() { @@ -2114,3 +2151,5 @@ public virtual Notebooks GetNotebooksClient(string notebook) } } } +#pragma warning restore SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning restore SAMPLE0009 // This generated code depends on experimental functionality. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClientOptions.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClientOptions.cs index e26fb92bc89..08db66ff4bd 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClientOptions.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClientOptions.cs @@ -10,6 +10,7 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Configuration; +#pragma warning disable SAMPLE0010 // This generated code depends on experimental functionality. namespace SampleTypeSpec { /// Client options for . @@ -54,7 +55,9 @@ public enum ServiceVersion /// V2024_07_16_Preview. V2024_07_16_Preview = 1, /// V2024_08_16_Preview. + [Experimental("SAMPLE0010")] V2024_08_16_Preview = 2 } } } +#pragma warning restore SAMPLE0010 // This generated code depends on experimental functionality. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecModelFactory.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecModelFactory.cs index b51746632cb..158ad600db5 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecModelFactory.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecModelFactory.cs @@ -12,6 +12,10 @@ using System.Linq; using SampleTypeSpec.Models.Custom; +#pragma warning disable SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning disable SAMPLE0004 // This generated code depends on experimental functionality. +#pragma warning disable SAMPLE0005 // This generated code depends on experimental functionality. +#pragma warning disable SAMPLE0008 // This generated code depends on experimental functionality. namespace SampleTypeSpec { /// A factory class for creating instances of the models for mocking. @@ -438,6 +442,24 @@ public static StreamingItem StreamingItem(string message = default) return new StreamingItem(message, additionalBinaryDataProperties: null); } + /// The PreviewDetails. + /// + /// A new instance for mocking. + [Experimental("SAMPLE0003")] + public static PreviewDetails PreviewDetails(PreviewChoice choice = default) + { + return new PreviewDetails(choice, additionalBinaryDataProperties: null); + } + + /// The LifecycleModel. + /// + /// + /// A new instance for mocking. + public static LifecycleModel LifecycleModel(PreviewDetails preview = default, PreviewExtensibleChoice choice = default) + { + return new LifecycleModel(preview, choice, additionalBinaryDataProperties: null); + } + /// /// Base animal with discriminator /// Please note this is the abstract base class. The derived classes available for instantiation are: and . @@ -540,3 +562,7 @@ public static NullableDynamicModel NullableDynamicModel(AnotherDynamicModel mode } } } +#pragma warning restore SAMPLE0003 // This generated code depends on experimental functionality. +#pragma warning restore SAMPLE0004 // This generated code depends on experimental functionality. +#pragma warning restore SAMPLE0005 // This generated code depends on experimental functionality. +#pragma warning restore SAMPLE0008 // This generated code depends on experimental functionality. 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 bf9a5e01557..82ff4ffbde2 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 @@ -716,12 +716,12 @@ { "$id": "57", "kind": "enum", - "name": "DaysOfWeekExtensibleEnum", + "name": "PreviewChoice", "apiVersions": [ "2024-07-16-preview", "2024-08-16-preview" ], - "crossLanguageDefinitionId": "SampleTypeSpec.DaysOfWeekExtensibleEnum", + "crossLanguageDefinitionId": "SampleTypeSpec.PreviewChoice", "valueType": { "$id": "58", "kind": "string", @@ -733,8 +733,8 @@ { "$id": "59", "kind": "enumvalue", - "name": "Monday", - "value": "Monday", + "name": "One", + "value": "one", "valueType": { "$ref": "58" }, @@ -747,8 +747,8 @@ { "$id": "60", "kind": "enumvalue", - "name": "Tuesday", - "value": "Tuesday", + "name": "Two", + "value": "two", "valueType": { "$ref": "58" }, @@ -756,74 +756,196 @@ "$ref": "57" }, "decorators": [], + "isExactName": false, + "experimental": { + "diagnosticId": "SAMPLE0007", + "dependsOn": [] + } + } + ], + "namespace": "SampleTypeSpec", + "isFixed": true, + "isFlags": false, + "usage": "Output,Json", + "decorators": [], + "isExactName": false, + "experimental": { + "diagnosticId": "SAMPLE0004", + "dependsOn": [] + } + }, + { + "$id": "61", + "kind": "enum", + "name": "PreviewExtensibleChoice", + "apiVersions": [ + "2024-07-16-preview", + "2024-08-16-preview" + ], + "crossLanguageDefinitionId": "SampleTypeSpec.PreviewExtensibleChoice", + "valueType": { + "$id": "62", + "kind": "string", + "name": "string", + "crossLanguageDefinitionId": "TypeSpec.string", + "decorators": [] + }, + "values": [ + { + "$id": "63", + "kind": "enumvalue", + "name": "One", + "value": "one", + "valueType": { + "$ref": "62" + }, + "enumType": { + "$ref": "61" + }, + "decorators": [], + "isExactName": false + }, + { + "$id": "64", + "kind": "enumvalue", + "name": "Two", + "value": "two", + "valueType": { + "$ref": "62" + }, + "enumType": { + "$ref": "61" + }, + "decorators": [], + "isExactName": false, + "experimental": { + "diagnosticId": "SAMPLE0006", + "dependsOn": [] + } + } + ], + "namespace": "SampleTypeSpec", + "isFixed": false, + "isFlags": false, + "usage": "Output,Json", + "decorators": [], + "isExactName": false, + "experimental": { + "diagnosticId": "SAMPLE0005", + "dependsOn": [] + } + }, + { + "$id": "65", + "kind": "enum", + "name": "DaysOfWeekExtensibleEnum", + "apiVersions": [ + "2024-07-16-preview", + "2024-08-16-preview" + ], + "crossLanguageDefinitionId": "SampleTypeSpec.DaysOfWeekExtensibleEnum", + "valueType": { + "$id": "66", + "kind": "string", + "name": "string", + "crossLanguageDefinitionId": "TypeSpec.string", + "decorators": [] + }, + "values": [ + { + "$id": "67", + "kind": "enumvalue", + "name": "Monday", + "value": "Monday", + "valueType": { + "$ref": "66" + }, + "enumType": { + "$ref": "65" + }, + "decorators": [], + "isExactName": false + }, + { + "$id": "68", + "kind": "enumvalue", + "name": "Tuesday", + "value": "Tuesday", + "valueType": { + "$ref": "66" + }, + "enumType": { + "$ref": "65" + }, + "decorators": [], "isExactName": false }, { - "$id": "61", + "$id": "69", "kind": "enumvalue", "name": "Wednesday", "value": "Wednesday", "valueType": { - "$ref": "58" + "$ref": "66" }, "enumType": { - "$ref": "57" + "$ref": "65" }, "decorators": [], "isExactName": false }, { - "$id": "62", + "$id": "70", "kind": "enumvalue", "name": "Thursday", "value": "Thursday", "valueType": { - "$ref": "58" + "$ref": "66" }, "enumType": { - "$ref": "57" + "$ref": "65" }, "decorators": [], "isExactName": false }, { - "$id": "63", + "$id": "71", "kind": "enumvalue", "name": "Friday", "value": "Friday", "valueType": { - "$ref": "58" + "$ref": "66" }, "enumType": { - "$ref": "57" + "$ref": "65" }, "decorators": [], "isExactName": false }, { - "$id": "64", + "$id": "72", "kind": "enumvalue", "name": "Saturday", "value": "Saturday", "valueType": { - "$ref": "58" + "$ref": "66" }, "enumType": { - "$ref": "57" + "$ref": "65" }, "decorators": [], "isExactName": false }, { - "$id": "65", + "$id": "73", "kind": "enumvalue", "name": "Sunday", "value": "Sunday", "valueType": { - "$ref": "58" + "$ref": "66" }, "enumType": { - "$ref": "57" + "$ref": "65" }, "decorators": [], "isExactName": false @@ -837,7 +959,7 @@ "isExactName": false }, { - "$id": "66", + "$id": "74", "kind": "enum", "name": "Versions", "apiVersions": [ @@ -846,7 +968,7 @@ ], "crossLanguageDefinitionId": "SampleTypeSpec.Versions", "valueType": { - "$id": "67", + "$id": "75", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -854,32 +976,36 @@ }, "values": [ { - "$id": "68", + "$id": "76", "kind": "enumvalue", "name": "2024-07-16-preview", "value": "2024-07-16-preview", "valueType": { - "$ref": "67" + "$ref": "75" }, "enumType": { - "$ref": "66" + "$ref": "74" }, "decorators": [], "isExactName": false }, { - "$id": "69", + "$id": "77", "kind": "enumvalue", "name": "2024-08-16-preview", "value": "2024-08-16-preview", "valueType": { - "$ref": "67" + "$ref": "75" }, "enumType": { - "$ref": "66" + "$ref": "74" }, "decorators": [], - "isExactName": false + "isExactName": false, + "experimental": { + "diagnosticId": "SAMPLE0010", + "dependsOn": [] + } } ], "namespace": "SampleTypeSpec", @@ -892,13 +1018,13 @@ ], "constants": [ { - "$id": "70", + "$id": "78", "kind": "constant", "name": "ThingRequiredLiteralString", "namespace": "SampleTypeSpec", "usage": "Input,Output,Spread,Json", "valueType": { - "$id": "71", + "$id": "79", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -909,13 +1035,13 @@ "isExactName": false }, { - "$id": "72", + "$id": "80", "kind": "constant", "name": "ThingRequiredLiteralInt", "namespace": "SampleTypeSpec", "usage": "Input,Output,Spread,Json", "valueType": { - "$id": "73", + "$id": "81", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -926,13 +1052,13 @@ "isExactName": false }, { - "$id": "74", + "$id": "82", "kind": "constant", "name": "ThingRequiredLiteralFloat", "namespace": "SampleTypeSpec", "usage": "Input,Output,Spread,Json", "valueType": { - "$id": "75", + "$id": "83", "kind": "float32", "name": "float32", "crossLanguageDefinitionId": "TypeSpec.float32", @@ -943,13 +1069,13 @@ "isExactName": false }, { - "$id": "76", + "$id": "84", "kind": "constant", "name": "ThingRequiredLiteralBool", "namespace": "SampleTypeSpec", "usage": "Input,Output,Spread,Json", "valueType": { - "$id": "77", + "$id": "85", "kind": "boolean", "name": "boolean", "crossLanguageDefinitionId": "TypeSpec.boolean", @@ -960,13 +1086,13 @@ "isExactName": false }, { - "$id": "78", + "$id": "86", "kind": "constant", "name": "ThingOptionalLiteralBool", "namespace": "SampleTypeSpec", "usage": "Input,Output,Spread,Json", "valueType": { - "$id": "79", + "$id": "87", "kind": "boolean", "name": "boolean", "crossLanguageDefinitionId": "TypeSpec.boolean", @@ -977,13 +1103,13 @@ "isExactName": false }, { - "$id": "80", + "$id": "88", "kind": "constant", "name": "PetKind", "namespace": "SampleTypeSpec", "usage": "Input,Output,Json", "valueType": { - "$id": "81", + "$id": "89", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -994,13 +1120,13 @@ "isExactName": false }, { - "$id": "82", + "$id": "90", "kind": "constant", "name": "DogKind", "namespace": "SampleTypeSpec", "usage": "Input,Output,Json", "valueType": { - "$id": "83", + "$id": "91", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1011,13 +1137,13 @@ "isExactName": false }, { - "$id": "84", + "$id": "92", "kind": "constant", "name": "TreeSpecies", "namespace": "SampleTypeSpec", "usage": "Input,Output,Json,Xml", "valueType": { - "$id": "85", + "$id": "93", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1028,13 +1154,13 @@ "isExactName": false }, { - "$id": "86", + "$id": "94", "kind": "constant", "name": "GetTreeAsJsonResponseContentType", "namespace": "", "usage": "None", "valueType": { - "$id": "87", + "$id": "95", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1045,13 +1171,13 @@ "isExactName": false }, { - "$id": "88", + "$id": "96", "kind": "constant", "name": "HelloAgainRequestContentType", "namespace": "", "usage": "None", "valueType": { - "$id": "89", + "$id": "97", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1062,13 +1188,13 @@ "isExactName": false }, { - "$id": "90", + "$id": "98", "kind": "constant", "name": "HelloAgainRequestContentType1", "namespace": "", "usage": "None", "valueType": { - "$id": "91", + "$id": "99", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1079,13 +1205,13 @@ "isExactName": false }, { - "$id": "92", + "$id": "100", "kind": "constant", "name": "GetTreeAsJsonResponseContentType1", "namespace": "", "usage": "None", "valueType": { - "$id": "93", + "$id": "101", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1096,13 +1222,13 @@ "isExactName": false }, { - "$id": "94", + "$id": "102", "kind": "constant", "name": "GetTreeAsJsonResponseContentType2", "namespace": "", "usage": "None", "valueType": { - "$id": "95", + "$id": "103", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1113,13 +1239,13 @@ "isExactName": false }, { - "$id": "96", + "$id": "104", "kind": "constant", "name": "GetTreeAsJsonResponseContentType3", "namespace": "", "usage": "None", "valueType": { - "$id": "97", + "$id": "105", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1130,13 +1256,13 @@ "isExactName": false }, { - "$id": "98", + "$id": "106", "kind": "constant", "name": "GetTreeAsJsonResponseContentType4", "namespace": "", "usage": "None", "valueType": { - "$id": "99", + "$id": "107", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1147,13 +1273,13 @@ "isExactName": false }, { - "$id": "100", + "$id": "108", "kind": "constant", "name": "GetTreeAsJsonResponseContentType5", "namespace": "", "usage": "None", "valueType": { - "$id": "101", + "$id": "109", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1164,13 +1290,13 @@ "isExactName": false }, { - "$id": "102", + "$id": "110", "kind": "constant", "name": "GetTreeAsJsonResponseContentType6", "namespace": "", "usage": "None", "valueType": { - "$id": "103", + "$id": "111", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1181,13 +1307,13 @@ "isExactName": false }, { - "$id": "104", + "$id": "112", "kind": "constant", "name": "HelloLiteralRequestP1", "namespace": "", "usage": "None", "valueType": { - "$id": "105", + "$id": "113", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1198,13 +1324,13 @@ "isExactName": false }, { - "$id": "106", + "$id": "114", "kind": "constant", "name": "HelloLiteralRequestP11", "namespace": "", "usage": "None", "valueType": { - "$id": "107", + "$id": "115", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1215,13 +1341,13 @@ "isExactName": false }, { - "$id": "108", + "$id": "116", "kind": "constant", "name": "ThingRequiredLiteralInt1", "namespace": "", "usage": "None", "valueType": { - "$id": "109", + "$id": "117", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -1232,13 +1358,13 @@ "isExactName": false }, { - "$id": "110", + "$id": "118", "kind": "constant", "name": "ThingRequiredLiteralInt2", "namespace": "", "usage": "None", "valueType": { - "$id": "111", + "$id": "119", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -1249,13 +1375,13 @@ "isExactName": false }, { - "$id": "112", + "$id": "120", "kind": "constant", "name": "ThingOptionalLiteralBool1", "namespace": "", "usage": "None", "valueType": { - "$id": "113", + "$id": "121", "kind": "boolean", "name": "boolean", "crossLanguageDefinitionId": "TypeSpec.boolean", @@ -1266,13 +1392,13 @@ "isExactName": false }, { - "$id": "114", + "$id": "122", "kind": "constant", "name": "ThingOptionalLiteralBool2", "namespace": "", "usage": "None", "valueType": { - "$id": "115", + "$id": "123", "kind": "boolean", "name": "boolean", "crossLanguageDefinitionId": "TypeSpec.boolean", @@ -1283,13 +1409,13 @@ "isExactName": false }, { - "$id": "116", + "$id": "124", "kind": "constant", "name": "GetTreeAsJsonResponseContentType7", "namespace": "", "usage": "None", "valueType": { - "$id": "117", + "$id": "125", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1300,13 +1426,13 @@ "isExactName": false }, { - "$id": "118", + "$id": "126", "kind": "constant", "name": "GetTreeAsJsonResponseContentType8", "namespace": "", "usage": "None", "valueType": { - "$id": "119", + "$id": "127", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1317,13 +1443,13 @@ "isExactName": false }, { - "$id": "120", + "$id": "128", "kind": "constant", "name": "GetTreeAsJsonResponseContentType9", "namespace": "", "usage": "None", "valueType": { - "$id": "121", + "$id": "129", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1334,13 +1460,13 @@ "isExactName": false }, { - "$id": "122", + "$id": "130", "kind": "constant", "name": "GetTreeAsJsonResponseContentType10", "namespace": "", "usage": "None", "valueType": { - "$id": "123", + "$id": "131", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1351,13 +1477,13 @@ "isExactName": false }, { - "$id": "124", + "$id": "132", "kind": "constant", "name": "GetTreeAsJsonResponseContentType11", "namespace": "", "usage": "None", "valueType": { - "$id": "125", + "$id": "133", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1368,13 +1494,13 @@ "isExactName": false }, { - "$id": "126", + "$id": "134", "kind": "constant", "name": "AnonymousBodyRequestRequiredQueryParam", "namespace": "", "usage": "None", "valueType": { - "$id": "127", + "$id": "135", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1385,13 +1511,13 @@ "isExactName": false }, { - "$id": "128", + "$id": "136", "kind": "constant", "name": "AnonymousBodyRequestRequiredQueryParam1", "namespace": "", "usage": "None", "valueType": { - "$id": "129", + "$id": "137", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1402,13 +1528,13 @@ "isExactName": false }, { - "$id": "130", + "$id": "138", "kind": "constant", "name": "AnonymousBodyRequestRequiredHeader", "namespace": "", "usage": "None", "valueType": { - "$id": "131", + "$id": "139", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1419,13 +1545,13 @@ "isExactName": false }, { - "$id": "132", + "$id": "140", "kind": "constant", "name": "AnonymousBodyRequestRequiredHeader1", "namespace": "", "usage": "None", "valueType": { - "$id": "133", + "$id": "141", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1436,13 +1562,13 @@ "isExactName": false }, { - "$id": "134", + "$id": "142", "kind": "constant", "name": "GetTreeAsJsonResponseContentType12", "namespace": "", "usage": "None", "valueType": { - "$id": "135", + "$id": "143", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1453,13 +1579,13 @@ "isExactName": false }, { - "$id": "136", + "$id": "144", "kind": "constant", "name": "GetTreeAsJsonResponseContentType13", "namespace": "", "usage": "None", "valueType": { - "$id": "137", + "$id": "145", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1470,13 +1596,13 @@ "isExactName": false }, { - "$id": "138", + "$id": "146", "kind": "constant", "name": "ThingRequiredLiteralString1", "namespace": "", "usage": "None", "valueType": { - "$id": "139", + "$id": "147", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -1487,13 +1613,13 @@ "isExactName": false }, { - "$id": "140", + "$id": "148", "kind": "constant", "name": "ThingRequiredLiteralInt3", "namespace": "", "usage": "None", "valueType": { - "$id": "141", + "$id": "149", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -1504,13 +1630,13 @@ "isExactName": false }, { - "$id": "142", + "$id": "150", "kind": "constant", "name": "ThingRequiredLiteralFloat1", "namespace": "", "usage": "None", "valueType": { - "$id": "143", + "$id": "151", "kind": "float32", "name": "float32", "crossLanguageDefinitionId": "TypeSpec.float32", @@ -1521,13 +1647,13 @@ "isExactName": false }, { - "$id": "144", + "$id": "152", "kind": "constant", "name": "ThingRequiredLiteralBool1", "namespace": "", "usage": "None", "valueType": { - "$id": "145", + "$id": "153", "kind": "boolean", "name": "boolean", "crossLanguageDefinitionId": "TypeSpec.boolean", @@ -1538,13 +1664,13 @@ "isExactName": false }, { - "$id": "146", + "$id": "154", "kind": "constant", "name": "ThingOptionalLiteralBool3", "namespace": "", "usage": "None", "valueType": { - "$id": "147", + "$id": "155", "kind": "boolean", "name": "boolean", "crossLanguageDefinitionId": "TypeSpec.boolean", @@ -1554,78 +1680,10 @@ "decorators": [], "isExactName": false }, - { - "$id": "148", - "kind": "constant", - "name": "GetTreeAsJsonResponseContentType14", - "namespace": "", - "usage": "None", - "valueType": { - "$id": "149", - "kind": "string", - "name": "string", - "crossLanguageDefinitionId": "TypeSpec.string", - "decorators": [] - }, - "value": "application/json", - "decorators": [], - "isExactName": false - }, - { - "$id": "150", - "kind": "constant", - "name": "GetTreeAsJsonResponseContentType15", - "namespace": "", - "usage": "None", - "valueType": { - "$id": "151", - "kind": "string", - "name": "string", - "crossLanguageDefinitionId": "TypeSpec.string", - "decorators": [] - }, - "value": "application/json", - "decorators": [], - "isExactName": false - }, - { - "$id": "152", - "kind": "constant", - "name": "GetTreeAsJsonResponseContentType16", - "namespace": "", - "usage": "None", - "valueType": { - "$id": "153", - "kind": "string", - "name": "string", - "crossLanguageDefinitionId": "TypeSpec.string", - "decorators": [] - }, - "value": "application/json", - "decorators": [], - "isExactName": false - }, - { - "$id": "154", - "kind": "constant", - "name": "GetTreeAsJsonResponseContentType17", - "namespace": "", - "usage": "None", - "valueType": { - "$id": "155", - "kind": "string", - "name": "string", - "crossLanguageDefinitionId": "TypeSpec.string", - "decorators": [] - }, - "value": "application/json", - "decorators": [], - "isExactName": false - }, { "$id": "156", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType18", + "name": "GetTreeAsJsonResponseContentType14", "namespace": "", "usage": "None", "valueType": { @@ -1642,7 +1700,7 @@ { "$id": "158", "kind": "constant", - "name": "HelloAgainRequestContentType2", + "name": "GetTreeAsJsonResponseContentType15", "namespace": "", "usage": "None", "valueType": { @@ -1652,14 +1710,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "text/plain", + "value": "application/json", "decorators": [], "isExactName": false }, { "$id": "160", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType19", + "name": "GetTreeAsJsonResponseContentType16", "namespace": "", "usage": "None", "valueType": { @@ -1676,7 +1734,7 @@ { "$id": "162", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType20", + "name": "GetTreeAsJsonResponseContentType17", "namespace": "", "usage": "None", "valueType": { @@ -1693,7 +1751,7 @@ { "$id": "164", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType21", + "name": "GetTreeAsJsonResponseContentType18", "namespace": "", "usage": "None", "valueType": { @@ -1710,7 +1768,7 @@ { "$id": "166", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType22", + "name": "HelloAgainRequestContentType2", "namespace": "", "usage": "None", "valueType": { @@ -1720,14 +1778,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/json", + "value": "text/plain", "decorators": [], "isExactName": false }, { "$id": "168", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType23", + "name": "GetTreeAsJsonResponseContentType19", "namespace": "", "usage": "None", "valueType": { @@ -1744,7 +1802,7 @@ { "$id": "170", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType24", + "name": "GetTreeAsJsonResponseContentType20", "namespace": "", "usage": "None", "valueType": { @@ -1761,7 +1819,7 @@ { "$id": "172", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType25", + "name": "GetTreeAsJsonResponseContentType21", "namespace": "", "usage": "None", "valueType": { @@ -1778,7 +1836,7 @@ { "$id": "174", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType26", + "name": "GetTreeAsJsonResponseContentType22", "namespace": "", "usage": "None", "valueType": { @@ -1795,7 +1853,7 @@ { "$id": "176", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType27", + "name": "GetTreeAsJsonResponseContentType23", "namespace": "", "usage": "None", "valueType": { @@ -1812,7 +1870,7 @@ { "$id": "178", "kind": "constant", - "name": "GetXmlAdvancedModelResponseContentType", + "name": "GetTreeAsJsonResponseContentType24", "namespace": "", "usage": "None", "valueType": { @@ -1822,14 +1880,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/xml", + "value": "application/json", "decorators": [], "isExactName": false }, { "$id": "180", "kind": "constant", - "name": "GetXmlAdvancedModelResponseContentType1", + "name": "GetTreeAsJsonResponseContentType25", "namespace": "", "usage": "None", "valueType": { @@ -1839,14 +1897,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/xml", + "value": "application/json", "decorators": [], "isExactName": false }, { "$id": "182", "kind": "constant", - "name": "GetXmlAdvancedModelResponseContentType2", + "name": "GetTreeAsJsonResponseContentType26", "namespace": "", "usage": "None", "valueType": { @@ -1856,14 +1914,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/xml", + "value": "application/json", "decorators": [], "isExactName": false }, { "$id": "184", "kind": "constant", - "name": "GetXmlAdvancedModelResponseContentType3", + "name": "GetTreeAsJsonResponseContentType27", "namespace": "", "usage": "None", "valueType": { @@ -1873,14 +1931,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/xml", + "value": "application/json", "decorators": [], "isExactName": false }, { "$id": "186", "kind": "constant", - "name": "GetXmlAdvancedModelResponseContentType4", + "name": "GetXmlAdvancedModelResponseContentType", "namespace": "", "usage": "None", "valueType": { @@ -1897,7 +1955,7 @@ { "$id": "188", "kind": "constant", - "name": "GetXmlAdvancedModelResponseContentType5", + "name": "GetXmlAdvancedModelResponseContentType1", "namespace": "", "usage": "None", "valueType": { @@ -1914,7 +1972,7 @@ { "$id": "190", "kind": "constant", - "name": "UploadCatRequestContentType", + "name": "GetXmlAdvancedModelResponseContentType2", "namespace": "", "usage": "None", "valueType": { @@ -1924,14 +1982,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "multipart/form-data", + "value": "application/xml", "decorators": [], "isExactName": false }, { "$id": "192", "kind": "constant", - "name": "UploadCatRequestContentType1", + "name": "GetXmlAdvancedModelResponseContentType3", "namespace": "", "usage": "None", "valueType": { @@ -1941,14 +1999,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "multipart/form-data", + "value": "application/xml", "decorators": [], "isExactName": false }, { "$id": "194", "kind": "constant", - "name": "SendJsonLinesRequestContentType", + "name": "GetXmlAdvancedModelResponseContentType4", "namespace": "", "usage": "None", "valueType": { @@ -1958,15 +2016,15 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/jsonl", + "value": "application/xml", "decorators": [], "isExactName": false }, { "$id": "196", "kind": "constant", - "name": "JsonlStreamStreamingItemContentType", - "namespace": "TypeSpec.Http.Streams", + "name": "GetXmlAdvancedModelResponseContentType5", + "namespace": "", "usage": "None", "valueType": { "$id": "197", @@ -1975,14 +2033,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/jsonl", + "value": "application/xml", "decorators": [], "isExactName": false }, { "$id": "198", "kind": "constant", - "name": "SendJsonLinesRequestContentType1", + "name": "UploadCatRequestContentType", "namespace": "", "usage": "None", "valueType": { @@ -1992,14 +2050,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/jsonl", + "value": "multipart/form-data", "decorators": [], "isExactName": false }, { "$id": "200", "kind": "constant", - "name": "SendJsonLinesRequestContentType2", + "name": "UploadCatRequestContentType1", "namespace": "", "usage": "None", "valueType": { @@ -2009,14 +2067,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/jsonl", + "value": "multipart/form-data", "decorators": [], "isExactName": false }, { "$id": "202", "kind": "constant", - "name": "ReceiveSseResponseContentType", + "name": "SendJsonLinesRequestContentType", "namespace": "", "usage": "None", "valueType": { @@ -2026,15 +2084,15 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "text/event-stream", + "value": "application/jsonl", "decorators": [], "isExactName": false }, { "$id": "204", "kind": "constant", - "name": "ReceiveSse", - "namespace": "", + "name": "JsonlStreamStreamingItemContentType", + "namespace": "TypeSpec.Http.Streams", "usage": "None", "valueType": { "$id": "205", @@ -2043,14 +2101,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "[DONE]", + "value": "application/jsonl", "decorators": [], "isExactName": false }, { "$id": "206", "kind": "constant", - "name": "ReceiveSseResponseContentType1", + "name": "SendJsonLinesRequestContentType1", "namespace": "", "usage": "None", "valueType": { @@ -2060,14 +2118,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "text/event-stream", + "value": "application/jsonl", "decorators": [], "isExactName": false }, { "$id": "208", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType28", + "name": "SendJsonLinesRequestContentType2", "namespace": "", "usage": "None", "valueType": { @@ -2077,14 +2135,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/json", + "value": "application/jsonl", "decorators": [], "isExactName": false }, { "$id": "210", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType29", + "name": "ReceiveSseResponseContentType", "namespace": "", "usage": "None", "valueType": { @@ -2094,14 +2152,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/json", + "value": "text/event-stream", "decorators": [], "isExactName": false }, { "$id": "212", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType30", + "name": "ReceiveSse", "namespace": "", "usage": "None", "valueType": { @@ -2111,14 +2169,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/json", + "value": "[DONE]", "decorators": [], "isExactName": false }, { "$id": "214", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType31", + "name": "ReceiveSseResponseContentType1", "namespace": "", "usage": "None", "valueType": { @@ -2128,14 +2186,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/json", + "value": "text/event-stream", "decorators": [], "isExactName": false }, { "$id": "216", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType32", + "name": "SendJsonLinesRequestContentType3", "namespace": "", "usage": "None", "valueType": { @@ -2145,14 +2203,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/json", + "value": "application/jsonl", "decorators": [], "isExactName": false }, { "$id": "218", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType33", + "name": "SendJsonLinesRequestContentType4", "namespace": "", "usage": "None", "valueType": { @@ -2162,14 +2220,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/json", + "value": "application/jsonl", "decorators": [], "isExactName": false }, { "$id": "220", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType34", + "name": "GetTreeAsJsonResponseContentType28", "namespace": "", "usage": "None", "valueType": { @@ -2186,7 +2244,7 @@ { "$id": "222", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType35", + "name": "GetTreeAsJsonResponseContentType29", "namespace": "", "usage": "None", "valueType": { @@ -2203,7 +2261,7 @@ { "$id": "224", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType36", + "name": "GetTreeAsJsonResponseContentType30", "namespace": "", "usage": "None", "valueType": { @@ -2220,7 +2278,7 @@ { "$id": "226", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType37", + "name": "GetTreeAsJsonResponseContentType31", "namespace": "", "usage": "None", "valueType": { @@ -2237,7 +2295,7 @@ { "$id": "228", "kind": "constant", - "name": "GetXmlAdvancedModelResponseContentType6", + "name": "GetTreeAsJsonResponseContentType32", "namespace": "", "usage": "None", "valueType": { @@ -2247,14 +2305,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/xml", + "value": "application/json", "decorators": [], "isExactName": false }, { "$id": "230", "kind": "constant", - "name": "GetXmlAdvancedModelResponseContentType7", + "name": "GetTreeAsJsonResponseContentType33", "namespace": "", "usage": "None", "valueType": { @@ -2264,14 +2322,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/xml", + "value": "application/json", "decorators": [], "isExactName": false }, { "$id": "232", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType38", + "name": "GetTreeAsJsonResponseContentType34", "namespace": "", "usage": "None", "valueType": { @@ -2288,7 +2346,7 @@ { "$id": "234", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType39", + "name": "GetTreeAsJsonResponseContentType35", "namespace": "", "usage": "None", "valueType": { @@ -2305,7 +2363,7 @@ { "$id": "236", "kind": "constant", - "name": "GetXmlAdvancedModelResponseContentType8", + "name": "GetTreeAsJsonResponseContentType36", "namespace": "", "usage": "None", "valueType": { @@ -2315,14 +2373,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/xml", + "value": "application/json", "decorators": [], "isExactName": false }, { "$id": "238", "kind": "constant", - "name": "GetXmlAdvancedModelResponseContentType9", + "name": "GetTreeAsJsonResponseContentType37", "namespace": "", "usage": "None", "valueType": { @@ -2332,14 +2390,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/xml", + "value": "application/json", "decorators": [], "isExactName": false }, { "$id": "240", "kind": "constant", - "name": "GetXmlAdvancedModelResponseContentType10", + "name": "GetTreeAsJsonResponseContentType38", "namespace": "", "usage": "None", "valueType": { @@ -2349,14 +2407,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/xml", + "value": "application/json", "decorators": [], "isExactName": false }, { "$id": "242", "kind": "constant", - "name": "GetXmlAdvancedModelResponseContentType11", + "name": "GetTreeAsJsonResponseContentType39", "namespace": "", "usage": "None", "valueType": { @@ -2366,14 +2424,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/xml", + "value": "application/json", "decorators": [], "isExactName": false }, { "$id": "244", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType40", + "name": "GetXmlAdvancedModelResponseContentType6", "namespace": "", "usage": "None", "valueType": { @@ -2383,14 +2441,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/json", + "value": "application/xml", "decorators": [], "isExactName": false }, { "$id": "246", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType41", + "name": "GetXmlAdvancedModelResponseContentType7", "namespace": "", "usage": "None", "valueType": { @@ -2400,14 +2458,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/json", + "value": "application/xml", "decorators": [], "isExactName": false }, { "$id": "248", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType42", + "name": "GetTreeAsJsonResponseContentType40", "namespace": "", "usage": "None", "valueType": { @@ -2424,7 +2482,7 @@ { "$id": "250", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType43", + "name": "GetTreeAsJsonResponseContentType41", "namespace": "", "usage": "None", "valueType": { @@ -2441,7 +2499,7 @@ { "$id": "252", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType44", + "name": "GetXmlAdvancedModelResponseContentType8", "namespace": "", "usage": "None", "valueType": { @@ -2451,14 +2509,14 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, - "value": "application/json", + "value": "application/xml", "decorators": [], "isExactName": false }, { "$id": "254", "kind": "constant", - "name": "GetTreeAsJsonResponseContentType45", + "name": "GetXmlAdvancedModelResponseContentType9", "namespace": "", "usage": "None", "valueType": { @@ -2468,6 +2526,142 @@ "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, + "value": "application/xml", + "decorators": [], + "isExactName": false + }, + { + "$id": "256", + "kind": "constant", + "name": "GetXmlAdvancedModelResponseContentType10", + "namespace": "", + "usage": "None", + "valueType": { + "$id": "257", + "kind": "string", + "name": "string", + "crossLanguageDefinitionId": "TypeSpec.string", + "decorators": [] + }, + "value": "application/xml", + "decorators": [], + "isExactName": false + }, + { + "$id": "258", + "kind": "constant", + "name": "GetXmlAdvancedModelResponseContentType11", + "namespace": "", + "usage": "None", + "valueType": { + "$id": "259", + "kind": "string", + "name": "string", + "crossLanguageDefinitionId": "TypeSpec.string", + "decorators": [] + }, + "value": "application/xml", + "decorators": [], + "isExactName": false + }, + { + "$id": "260", + "kind": "constant", + "name": "GetTreeAsJsonResponseContentType42", + "namespace": "", + "usage": "None", + "valueType": { + "$id": "261", + "kind": "string", + "name": "string", + "crossLanguageDefinitionId": "TypeSpec.string", + "decorators": [] + }, + "value": "application/json", + "decorators": [], + "isExactName": false + }, + { + "$id": "262", + "kind": "constant", + "name": "GetTreeAsJsonResponseContentType43", + "namespace": "", + "usage": "None", + "valueType": { + "$id": "263", + "kind": "string", + "name": "string", + "crossLanguageDefinitionId": "TypeSpec.string", + "decorators": [] + }, + "value": "application/json", + "decorators": [], + "isExactName": false + }, + { + "$id": "264", + "kind": "constant", + "name": "GetTreeAsJsonResponseContentType44", + "namespace": "", + "usage": "None", + "valueType": { + "$id": "265", + "kind": "string", + "name": "string", + "crossLanguageDefinitionId": "TypeSpec.string", + "decorators": [] + }, + "value": "application/json", + "decorators": [], + "isExactName": false + }, + { + "$id": "266", + "kind": "constant", + "name": "GetTreeAsJsonResponseContentType45", + "namespace": "", + "usage": "None", + "valueType": { + "$id": "267", + "kind": "string", + "name": "string", + "crossLanguageDefinitionId": "TypeSpec.string", + "decorators": [] + }, + "value": "application/json", + "decorators": [], + "isExactName": false + }, + { + "$id": "268", + "kind": "constant", + "name": "GetTreeAsJsonResponseContentType46", + "namespace": "", + "usage": "None", + "valueType": { + "$id": "269", + "kind": "string", + "name": "string", + "crossLanguageDefinitionId": "TypeSpec.string", + "decorators": [] + }, + "value": "application/json", + "decorators": [], + "isExactName": false + }, + { + "$id": "270", + "kind": "constant", + "name": "GetTreeAsJsonResponseContentType47", + "namespace": "", + "usage": "None", + "valueType": { + "$id": "271", + "kind": "string", + "name": "string", + "crossLanguageDefinitionId": "TypeSpec.string", + "decorators": [] + }, "value": "application/json", "decorators": [], "isExactName": false @@ -2475,7 +2669,7 @@ ], "models": [ { - "$id": "256", + "$id": "272", "kind": "model", "name": "Thing", "apiVersions": [ @@ -2495,7 +2689,7 @@ "isExactName": false, "properties": [ { - "$id": "257", + "$id": "273", "kind": "property", "name": "name", "apiVersions": [ @@ -2505,7 +2699,7 @@ "serializedName": "name", "doc": "name of the Thing", "type": { - "$id": "258", + "$id": "274", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -2526,7 +2720,7 @@ "isExactName": false }, { - "$id": "259", + "$id": "275", "kind": "property", "name": "requiredUnion", "apiVersions": [ @@ -2536,23 +2730,23 @@ "serializedName": "requiredUnion", "doc": "required Union", "type": { - "$id": "260", + "$id": "276", "kind": "union", "name": "ThingRequiredUnion", "variantTypes": [ { - "$id": "261", + "$id": "277", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, { - "$id": "262", + "$id": "278", "kind": "array", "name": "Array", "valueType": { - "$id": "263", + "$id": "279", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -2562,7 +2756,7 @@ "decorators": [] }, { - "$id": "264", + "$id": "280", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -2588,7 +2782,7 @@ "isExactName": false }, { - "$id": "265", + "$id": "281", "kind": "property", "name": "requiredLiteralString", "apiVersions": [ @@ -2598,7 +2792,7 @@ "serializedName": "requiredLiteralString", "doc": "required literal string", "type": { - "$ref": "70" + "$ref": "78" }, "optional": false, "readOnly": false, @@ -2615,7 +2809,7 @@ "isExactName": false }, { - "$id": "266", + "$id": "282", "kind": "property", "name": "requiredNullableString", "apiVersions": [ @@ -2625,10 +2819,10 @@ "serializedName": "requiredNullableString", "doc": "required nullable string", "type": { - "$id": "267", + "$id": "283", "kind": "nullable", "type": { - "$id": "268", + "$id": "284", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -2651,7 +2845,7 @@ "isExactName": false }, { - "$id": "269", + "$id": "285", "kind": "property", "name": "optionalNullableString", "apiVersions": [ @@ -2661,10 +2855,10 @@ "serializedName": "optionalNullableString", "doc": "required optional string", "type": { - "$id": "270", + "$id": "286", "kind": "nullable", "type": { - "$id": "271", + "$id": "287", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -2687,7 +2881,7 @@ "isExactName": false }, { - "$id": "272", + "$id": "288", "kind": "property", "name": "requiredLiteralInt", "apiVersions": [ @@ -2697,7 +2891,7 @@ "serializedName": "requiredLiteralInt", "doc": "required literal int", "type": { - "$ref": "72" + "$ref": "80" }, "optional": false, "readOnly": false, @@ -2714,7 +2908,7 @@ "isExactName": false }, { - "$id": "273", + "$id": "289", "kind": "property", "name": "requiredLiteralFloat", "apiVersions": [ @@ -2724,7 +2918,7 @@ "serializedName": "requiredLiteralFloat", "doc": "required literal float", "type": { - "$ref": "74" + "$ref": "82" }, "optional": false, "readOnly": false, @@ -2741,7 +2935,7 @@ "isExactName": false }, { - "$id": "274", + "$id": "290", "kind": "property", "name": "requiredLiteralBool", "apiVersions": [ @@ -2751,7 +2945,7 @@ "serializedName": "requiredLiteralBool", "doc": "required literal bool", "type": { - "$ref": "76" + "$ref": "84" }, "optional": false, "readOnly": false, @@ -2768,7 +2962,7 @@ "isExactName": false }, { - "$id": "275", + "$id": "291", "kind": "property", "name": "optionalLiteralString", "apiVersions": [ @@ -2795,7 +2989,7 @@ "isExactName": false }, { - "$id": "276", + "$id": "292", "kind": "property", "name": "requiredNullableLiteralString", "apiVersions": [ @@ -2805,7 +2999,7 @@ "serializedName": "requiredNullableLiteralString", "doc": "required nullable literal string", "type": { - "$id": "277", + "$id": "293", "kind": "nullable", "type": { "$ref": "5" @@ -2827,7 +3021,7 @@ "isExactName": false }, { - "$id": "278", + "$id": "294", "kind": "property", "name": "optionalLiteralInt", "apiVersions": [ @@ -2854,7 +3048,7 @@ "isExactName": false }, { - "$id": "279", + "$id": "295", "kind": "property", "name": "optionalLiteralFloat", "apiVersions": [ @@ -2881,7 +3075,7 @@ "isExactName": false }, { - "$id": "280", + "$id": "296", "kind": "property", "name": "optionalLiteralBool", "apiVersions": [ @@ -2891,7 +3085,7 @@ "serializedName": "optionalLiteralBool", "doc": "optional literal bool", "type": { - "$ref": "78" + "$ref": "86" }, "optional": true, "readOnly": false, @@ -2908,7 +3102,7 @@ "isExactName": false }, { - "$id": "281", + "$id": "297", "kind": "property", "name": "requiredBadDescription", "apiVersions": [ @@ -2918,7 +3112,7 @@ "serializedName": "requiredBadDescription", "doc": "description with xml <|endoftext|>", "type": { - "$id": "282", + "$id": "298", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -2939,7 +3133,7 @@ "isExactName": false }, { - "$id": "283", + "$id": "299", "kind": "property", "name": "optionalNullableList", "apiVersions": [ @@ -2949,14 +3143,14 @@ "serializedName": "optionalNullableList", "doc": "optional nullable collection", "type": { - "$id": "284", + "$id": "300", "kind": "nullable", "type": { - "$id": "285", + "$id": "301", "kind": "array", "name": "Array1", "valueType": { - "$id": "286", + "$id": "302", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -2982,7 +3176,7 @@ "isExactName": false }, { - "$id": "287", + "$id": "303", "kind": "property", "name": "requiredNullableList", "apiVersions": [ @@ -2992,10 +3186,10 @@ "serializedName": "requiredNullableList", "doc": "required nullable collection", "type": { - "$id": "288", + "$id": "304", "kind": "nullable", "type": { - "$ref": "285" + "$ref": "301" }, "namespace": "SampleTypeSpec" }, @@ -3014,7 +3208,7 @@ "isExactName": false }, { - "$id": "289", + "$id": "305", "kind": "property", "name": "propertyWithSpecialDocs", "apiVersions": [ @@ -3024,7 +3218,7 @@ "serializedName": "propertyWithSpecialDocs", "doc": "This tests:\n- Simple bullet point. This bullet point is going to be very long to test how text wrapping is handled in bullet points within documentation comments. It should properly indent the wrapped lines.\n- Another bullet point with **bold text**. This bullet point is also intentionally long to see how the formatting is preserved when the text wraps onto multiple lines in the generated documentation.\n- Third bullet point with *italic text*. Similar to the previous points, this one is extended to ensure that the wrapping and formatting are correctly applied in the output.\n- Complex bullet point with **bold** and *italic* combined. This bullet point combines both bold and italic formatting and is long enough to test the wrapping behavior in such cases.\n- **Bold bullet point**: A bullet point that is entirely bolded. This point is also made lengthy to observe how the bold formatting is maintained across wrapped lines.\n- *Italic bullet point*: A bullet point that is entirely italicized. This final point is extended to verify that italic formatting is correctly applied even when the text spans multiple lines.", "type": { - "$id": "290", + "$id": "306", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -3047,7 +3241,7 @@ ] }, { - "$id": "291", + "$id": "307", "kind": "model", "name": "RoundTripModel", "apiVersions": [ @@ -3067,7 +3261,7 @@ "isExactName": false, "properties": [ { - "$id": "292", + "$id": "308", "kind": "property", "name": "requiredString", "apiVersions": [ @@ -3077,7 +3271,7 @@ "serializedName": "requiredString", "doc": "Required string, illustrating a reference type property.", "type": { - "$id": "293", + "$id": "309", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -3098,7 +3292,7 @@ "isExactName": false }, { - "$id": "294", + "$id": "310", "kind": "property", "name": "requiredInt", "apiVersions": [ @@ -3108,7 +3302,7 @@ "serializedName": "requiredInt", "doc": "Required int, illustrating a value type property.", "type": { - "$id": "295", + "$id": "311", "kind": "int32", "name": "int32", "encode": "string", @@ -3130,7 +3324,7 @@ "isExactName": false }, { - "$id": "296", + "$id": "312", "kind": "property", "name": "requiredCollection", "apiVersions": [ @@ -3140,7 +3334,7 @@ "serializedName": "requiredCollection", "doc": "Required collection of enums", "type": { - "$id": "297", + "$id": "313", "kind": "array", "name": "ArrayStringFixedEnum", "valueType": { @@ -3164,7 +3358,7 @@ "isExactName": false }, { - "$id": "298", + "$id": "314", "kind": "property", "name": "requiredDictionary", "apiVersions": [ @@ -3174,10 +3368,10 @@ "serializedName": "requiredDictionary", "doc": "Required dictionary of enums", "type": { - "$id": "299", + "$id": "315", "kind": "dict", "keyType": { - "$id": "300", + "$id": "316", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -3203,7 +3397,7 @@ "isExactName": false }, { - "$id": "301", + "$id": "317", "kind": "property", "name": "requiredModel", "apiVersions": [ @@ -3213,7 +3407,7 @@ "serializedName": "requiredModel", "doc": "Required model", "type": { - "$ref": "256" + "$ref": "272" }, "optional": false, "readOnly": false, @@ -3230,7 +3424,7 @@ "isExactName": false }, { - "$id": "302", + "$id": "318", "kind": "property", "name": "intExtensibleEnum", "apiVersions": [ @@ -3257,7 +3451,7 @@ "isExactName": false }, { - "$id": "303", + "$id": "319", "kind": "property", "name": "intExtensibleEnumCollection", "apiVersions": [ @@ -3267,7 +3461,7 @@ "serializedName": "intExtensibleEnumCollection", "doc": "this is a collection of int based extensible enum", "type": { - "$id": "304", + "$id": "320", "kind": "array", "name": "ArrayIntExtensibleEnum", "valueType": { @@ -3291,7 +3485,7 @@ "isExactName": false }, { - "$id": "305", + "$id": "321", "kind": "property", "name": "floatExtensibleEnum", "apiVersions": [ @@ -3318,7 +3512,7 @@ "isExactName": false }, { - "$id": "306", + "$id": "322", "kind": "property", "name": "floatExtensibleEnumWithIntValue", "apiVersions": [ @@ -3345,7 +3539,7 @@ "isExactName": false }, { - "$id": "307", + "$id": "323", "kind": "property", "name": "floatExtensibleEnumCollection", "apiVersions": [ @@ -3355,7 +3549,7 @@ "serializedName": "floatExtensibleEnumCollection", "doc": "this is a collection of float based extensible enum", "type": { - "$id": "308", + "$id": "324", "kind": "array", "name": "ArrayFloatExtensibleEnum", "valueType": { @@ -3379,7 +3573,7 @@ "isExactName": false }, { - "$id": "309", + "$id": "325", "kind": "property", "name": "floatFixedEnum", "apiVersions": [ @@ -3406,7 +3600,7 @@ "isExactName": false }, { - "$id": "310", + "$id": "326", "kind": "property", "name": "floatFixedEnumWithIntValue", "apiVersions": [ @@ -3433,7 +3627,7 @@ "isExactName": false }, { - "$id": "311", + "$id": "327", "kind": "property", "name": "floatFixedEnumCollection", "apiVersions": [ @@ -3443,7 +3637,7 @@ "serializedName": "floatFixedEnumCollection", "doc": "this is a collection of float based fixed enum", "type": { - "$id": "312", + "$id": "328", "kind": "array", "name": "ArrayFloatFixedEnum", "valueType": { @@ -3467,7 +3661,7 @@ "isExactName": false }, { - "$id": "313", + "$id": "329", "kind": "property", "name": "intFixedEnum", "apiVersions": [ @@ -3494,7 +3688,7 @@ "isExactName": false }, { - "$id": "314", + "$id": "330", "kind": "property", "name": "intFixedEnumCollection", "apiVersions": [ @@ -3504,7 +3698,7 @@ "serializedName": "intFixedEnumCollection", "doc": "this is a collection of int based fixed enum", "type": { - "$id": "315", + "$id": "331", "kind": "array", "name": "ArrayIntFixedEnum", "valueType": { @@ -3528,7 +3722,7 @@ "isExactName": false }, { - "$id": "316", + "$id": "332", "kind": "property", "name": "stringFixedEnum", "apiVersions": [ @@ -3555,7 +3749,7 @@ "isExactName": false }, { - "$id": "317", + "$id": "333", "kind": "property", "name": "requiredUnknown", "apiVersions": [ @@ -3565,7 +3759,7 @@ "serializedName": "requiredUnknown", "doc": "required unknown", "type": { - "$id": "318", + "$id": "334", "kind": "unknown", "name": "unknown", "crossLanguageDefinitionId": "", @@ -3586,7 +3780,7 @@ "isExactName": false }, { - "$id": "319", + "$id": "335", "kind": "property", "name": "optionalUnknown", "apiVersions": [ @@ -3596,7 +3790,7 @@ "serializedName": "optionalUnknown", "doc": "optional unknown", "type": { - "$id": "320", + "$id": "336", "kind": "unknown", "name": "unknown", "crossLanguageDefinitionId": "", @@ -3617,7 +3811,7 @@ "isExactName": false }, { - "$id": "321", + "$id": "337", "kind": "property", "name": "requiredRecordUnknown", "apiVersions": [ @@ -3627,17 +3821,17 @@ "serializedName": "requiredRecordUnknown", "doc": "required record of unknown", "type": { - "$id": "322", + "$id": "338", "kind": "dict", "keyType": { - "$id": "323", + "$id": "339", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, "valueType": { - "$id": "324", + "$id": "340", "kind": "unknown", "name": "unknown", "crossLanguageDefinitionId": "", @@ -3660,7 +3854,7 @@ "isExactName": false }, { - "$id": "325", + "$id": "341", "kind": "property", "name": "optionalRecordUnknown", "apiVersions": [ @@ -3670,7 +3864,7 @@ "serializedName": "optionalRecordUnknown", "doc": "optional record of unknown", "type": { - "$ref": "322" + "$ref": "338" }, "optional": true, "readOnly": false, @@ -3687,7 +3881,7 @@ "isExactName": false }, { - "$id": "326", + "$id": "342", "kind": "property", "name": "readOnlyRequiredRecordUnknown", "apiVersions": [ @@ -3697,7 +3891,7 @@ "serializedName": "readOnlyRequiredRecordUnknown", "doc": "required readonly record of unknown", "type": { - "$ref": "322" + "$ref": "338" }, "optional": false, "readOnly": true, @@ -3714,7 +3908,7 @@ "isExactName": false }, { - "$id": "327", + "$id": "343", "kind": "property", "name": "readOnlyOptionalRecordUnknown", "apiVersions": [ @@ -3724,7 +3918,7 @@ "serializedName": "readOnlyOptionalRecordUnknown", "doc": "optional readonly record of unknown", "type": { - "$ref": "322" + "$ref": "338" }, "optional": true, "readOnly": true, @@ -3741,7 +3935,7 @@ "isExactName": false }, { - "$id": "328", + "$id": "344", "kind": "property", "name": "modelWithRequiredNullable", "apiVersions": [ @@ -3751,7 +3945,7 @@ "serializedName": "modelWithRequiredNullable", "doc": "this is a model with required nullable properties", "type": { - "$id": "329", + "$id": "345", "kind": "model", "name": "ModelWithRequiredNullableProperties", "apiVersions": [ @@ -3771,7 +3965,7 @@ "isExactName": false, "properties": [ { - "$id": "330", + "$id": "346", "kind": "property", "name": "requiredNullablePrimitive", "apiVersions": [ @@ -3781,10 +3975,10 @@ "serializedName": "requiredNullablePrimitive", "doc": "required nullable primitive type", "type": { - "$id": "331", + "$id": "347", "kind": "nullable", "type": { - "$id": "332", + "$id": "348", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -3807,7 +4001,7 @@ "isExactName": false }, { - "$id": "333", + "$id": "349", "kind": "property", "name": "requiredExtensibleEnum", "apiVersions": [ @@ -3817,7 +4011,7 @@ "serializedName": "requiredExtensibleEnum", "doc": "required nullable extensible enum type", "type": { - "$id": "334", + "$id": "350", "kind": "nullable", "type": { "$ref": "22" @@ -3839,7 +4033,7 @@ "isExactName": false }, { - "$id": "335", + "$id": "351", "kind": "property", "name": "requiredFixedEnum", "apiVersions": [ @@ -3849,7 +4043,7 @@ "serializedName": "requiredFixedEnum", "doc": "required nullable fixed enum type", "type": { - "$id": "336", + "$id": "352", "kind": "nullable", "type": { "$ref": "17" @@ -3887,7 +4081,7 @@ "isExactName": false }, { - "$id": "337", + "$id": "353", "kind": "property", "name": "requiredBytes", "apiVersions": [ @@ -3897,7 +4091,7 @@ "serializedName": "requiredBytes", "doc": "Required bytes", "type": { - "$id": "338", + "$id": "354", "kind": "bytes", "name": "bytes", "encode": "base64", @@ -3921,10 +4115,10 @@ ] }, { - "$ref": "329" + "$ref": "345" }, { - "$id": "339", + "$id": "355", "kind": "model", "name": "Wrapper", "apiVersions": [ @@ -3939,7 +4133,7 @@ "isExactName": false, "properties": [ { - "$id": "340", + "$id": "356", "kind": "property", "name": "p1", "apiVersions": [ @@ -3948,7 +4142,7 @@ ], "doc": "header parameter", "type": { - "$id": "341", + "$id": "357", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -3965,7 +4159,7 @@ "isExactName": false }, { - "$id": "342", + "$id": "358", "kind": "property", "name": "action", "apiVersions": [ @@ -3974,7 +4168,7 @@ ], "doc": "body parameter", "type": { - "$ref": "291" + "$ref": "307" }, "optional": false, "readOnly": false, @@ -3987,7 +4181,7 @@ "isExactName": false }, { - "$id": "343", + "$id": "359", "kind": "property", "name": "p2", "apiVersions": [ @@ -3996,7 +4190,7 @@ ], "doc": "path parameter", "type": { - "$id": "344", + "$id": "360", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -4015,7 +4209,7 @@ ] }, { - "$id": "345", + "$id": "361", "kind": "model", "name": "Friend", "apiVersions": [ @@ -4035,7 +4229,7 @@ "isExactName": false, "properties": [ { - "$id": "346", + "$id": "362", "kind": "property", "name": "name", "apiVersions": [ @@ -4045,7 +4239,7 @@ "serializedName": "name", "doc": "name of the NotFriend", "type": { - "$id": "347", + "$id": "363", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -4068,7 +4262,7 @@ ] }, { - "$id": "348", + "$id": "364", "kind": "model", "name": "RenamedModel", "apiVersions": [ @@ -4088,7 +4282,7 @@ "isExactName": false, "properties": [ { - "$id": "349", + "$id": "365", "kind": "property", "name": "otherName", "apiVersions": [ @@ -4098,7 +4292,7 @@ "serializedName": "otherName", "doc": "name of the ModelWithClientName", "type": { - "$id": "350", + "$id": "366", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -4121,7 +4315,7 @@ ] }, { - "$id": "351", + "$id": "367", "kind": "model", "name": "ReturnsAnonymousModelResponse", "apiVersions": [ @@ -4141,7 +4335,7 @@ "properties": [] }, { - "$id": "352", + "$id": "368", "kind": "model", "name": "ListWithNextLinkResponse", "apiVersions": [ @@ -4160,7 +4354,7 @@ "isExactName": false, "properties": [ { - "$id": "353", + "$id": "369", "kind": "property", "name": "things", "apiVersions": [ @@ -4169,11 +4363,11 @@ ], "serializedName": "things", "type": { - "$id": "354", + "$id": "370", "kind": "array", "name": "ArrayThing", "valueType": { - "$ref": "256" + "$ref": "272" }, "crossLanguageDefinitionId": "TypeSpec.Array", "decorators": [] @@ -4193,7 +4387,7 @@ "isExactName": false }, { - "$id": "355", + "$id": "371", "kind": "property", "name": "next", "apiVersions": [ @@ -4202,7 +4396,7 @@ ], "serializedName": "next", "type": { - "$id": "356", + "$id": "372", "kind": "url", "name": "url", "crossLanguageDefinitionId": "TypeSpec.url", @@ -4225,7 +4419,7 @@ ] }, { - "$id": "357", + "$id": "373", "kind": "model", "name": "ListWithStringNextLinkResponse", "apiVersions": [ @@ -4244,7 +4438,7 @@ "isExactName": false, "properties": [ { - "$id": "358", + "$id": "374", "kind": "property", "name": "things", "apiVersions": [ @@ -4253,7 +4447,7 @@ ], "serializedName": "things", "type": { - "$ref": "354" + "$ref": "370" }, "optional": false, "readOnly": false, @@ -4270,7 +4464,7 @@ "isExactName": false }, { - "$id": "359", + "$id": "375", "kind": "property", "name": "next", "apiVersions": [ @@ -4279,7 +4473,7 @@ ], "serializedName": "next", "type": { - "$id": "360", + "$id": "376", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -4302,7 +4496,7 @@ ] }, { - "$id": "361", + "$id": "377", "kind": "model", "name": "ListWithContinuationTokenResponse", "apiVersions": [ @@ -4321,7 +4515,7 @@ "isExactName": false, "properties": [ { - "$id": "362", + "$id": "378", "kind": "property", "name": "things", "apiVersions": [ @@ -4330,7 +4524,7 @@ ], "serializedName": "things", "type": { - "$ref": "354" + "$ref": "370" }, "optional": false, "readOnly": false, @@ -4347,7 +4541,7 @@ "isExactName": false }, { - "$id": "363", + "$id": "379", "kind": "property", "name": "nextToken", "apiVersions": [ @@ -4356,7 +4550,7 @@ ], "serializedName": "nextToken", "type": { - "$id": "364", + "$id": "380", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -4379,7 +4573,7 @@ ] }, { - "$id": "365", + "$id": "381", "kind": "model", "name": "ListWithContinuationTokenHeaderResponseResponse", "apiVersions": [], @@ -4395,7 +4589,7 @@ "isExactName": false, "properties": [ { - "$id": "366", + "$id": "382", "kind": "property", "name": "things", "apiVersions": [ @@ -4404,7 +4598,7 @@ ], "serializedName": "things", "type": { - "$ref": "354" + "$ref": "370" }, "optional": false, "readOnly": false, @@ -4423,7 +4617,7 @@ ] }, { - "$id": "367", + "$id": "383", "kind": "model", "name": "PageThing", "apiVersions": [ @@ -4442,7 +4636,7 @@ "isExactName": false, "properties": [ { - "$id": "368", + "$id": "384", "kind": "property", "name": "items", "apiVersions": [ @@ -4451,7 +4645,7 @@ ], "serializedName": "items", "type": { - "$ref": "354" + "$ref": "370" }, "optional": false, "readOnly": false, @@ -4470,7 +4664,7 @@ ] }, { - "$id": "369", + "$id": "385", "kind": "model", "name": "ModelWithEmbeddedNonBodyParameters", "apiVersions": [ @@ -4489,7 +4683,7 @@ "isExactName": false, "properties": [ { - "$id": "370", + "$id": "386", "kind": "property", "name": "name", "apiVersions": [ @@ -4499,7 +4693,7 @@ "serializedName": "name", "doc": "name of the ModelWithEmbeddedNonBodyParameters", "type": { - "$id": "371", + "$id": "387", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -4520,7 +4714,7 @@ "isExactName": false }, { - "$id": "372", + "$id": "388", "kind": "property", "name": "requiredHeader", "apiVersions": [ @@ -4530,7 +4724,7 @@ "serializedName": "requiredHeader", "doc": "required header parameter", "type": { - "$id": "373", + "$id": "389", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -4551,7 +4745,7 @@ "isExactName": false }, { - "$id": "374", + "$id": "390", "kind": "property", "name": "optionalHeader", "apiVersions": [ @@ -4561,7 +4755,7 @@ "serializedName": "optionalHeader", "doc": "optional header parameter", "type": { - "$id": "375", + "$id": "391", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -4582,7 +4776,7 @@ "isExactName": false }, { - "$id": "376", + "$id": "392", "kind": "property", "name": "requiredQuery", "apiVersions": [ @@ -4592,7 +4786,7 @@ "serializedName": "requiredQuery", "doc": "required query parameter", "type": { - "$id": "377", + "$id": "393", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -4613,7 +4807,7 @@ "isExactName": false }, { - "$id": "378", + "$id": "394", "kind": "property", "name": "optionalQuery", "apiVersions": [ @@ -4623,7 +4817,7 @@ "serializedName": "optionalQuery", "doc": "optional query parameter", "type": { - "$id": "379", + "$id": "395", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -4646,7 +4840,7 @@ ] }, { - "$id": "380", + "$id": "396", "kind": "model", "name": "DynamicModel", "apiVersions": [ @@ -4671,7 +4865,7 @@ "isExactName": false, "properties": [ { - "$id": "381", + "$id": "397", "kind": "property", "name": "name", "apiVersions": [ @@ -4680,7 +4874,7 @@ ], "serializedName": "name", "type": { - "$id": "382", + "$id": "398", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -4701,7 +4895,7 @@ "isExactName": false }, { - "$id": "383", + "$id": "399", "kind": "property", "name": "optionalUnknown", "apiVersions": [ @@ -4710,7 +4904,7 @@ ], "serializedName": "optionalUnknown", "type": { - "$id": "384", + "$id": "400", "kind": "unknown", "name": "unknown", "crossLanguageDefinitionId": "", @@ -4731,7 +4925,7 @@ "isExactName": false }, { - "$id": "385", + "$id": "401", "kind": "property", "name": "optionalInt", "apiVersions": [ @@ -4740,7 +4934,7 @@ ], "serializedName": "optionalInt", "type": { - "$id": "386", + "$id": "402", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -4761,7 +4955,7 @@ "isExactName": false }, { - "$id": "387", + "$id": "403", "kind": "property", "name": "optionalNullableList", "apiVersions": [ @@ -4770,10 +4964,10 @@ ], "serializedName": "optionalNullableList", "type": { - "$id": "388", + "$id": "404", "kind": "nullable", "type": { - "$ref": "285" + "$ref": "301" }, "namespace": "SampleTypeSpec" }, @@ -4792,7 +4986,7 @@ "isExactName": false }, { - "$id": "389", + "$id": "405", "kind": "property", "name": "requiredNullableList", "apiVersions": [ @@ -4801,10 +4995,10 @@ ], "serializedName": "requiredNullableList", "type": { - "$id": "390", + "$id": "406", "kind": "nullable", "type": { - "$ref": "285" + "$ref": "301" }, "namespace": "SampleTypeSpec" }, @@ -4823,7 +5017,7 @@ "isExactName": false }, { - "$id": "391", + "$id": "407", "kind": "property", "name": "optionalNullableDictionary", "apiVersions": [ @@ -4832,20 +5026,20 @@ ], "serializedName": "optionalNullableDictionary", "type": { - "$id": "392", + "$id": "408", "kind": "nullable", "type": { - "$id": "393", + "$id": "409", "kind": "dict", "keyType": { - "$id": "394", + "$id": "410", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, "valueType": { - "$id": "395", + "$id": "411", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -4870,7 +5064,7 @@ "isExactName": false }, { - "$id": "396", + "$id": "412", "kind": "property", "name": "requiredNullableDictionary", "apiVersions": [ @@ -4879,10 +5073,10 @@ ], "serializedName": "requiredNullableDictionary", "type": { - "$id": "397", + "$id": "413", "kind": "nullable", "type": { - "$ref": "393" + "$ref": "409" }, "namespace": "SampleTypeSpec" }, @@ -4901,7 +5095,7 @@ "isExactName": false }, { - "$id": "398", + "$id": "414", "kind": "property", "name": "primitiveDictionary", "apiVersions": [ @@ -4910,7 +5104,7 @@ ], "serializedName": "primitiveDictionary", "type": { - "$ref": "393" + "$ref": "409" }, "optional": false, "readOnly": false, @@ -4927,7 +5121,7 @@ "isExactName": false }, { - "$id": "399", + "$id": "415", "kind": "property", "name": "foo", "apiVersions": [ @@ -4936,7 +5130,7 @@ ], "serializedName": "foo", "type": { - "$id": "400", + "$id": "416", "kind": "model", "name": "AnotherDynamicModel", "apiVersions": [ @@ -4961,7 +5155,7 @@ "isExactName": false, "properties": [ { - "$id": "401", + "$id": "417", "kind": "property", "name": "bar", "apiVersions": [ @@ -4970,7 +5164,7 @@ ], "serializedName": "bar", "type": { - "$id": "402", + "$id": "418", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -5007,7 +5201,7 @@ "isExactName": false }, { - "$id": "403", + "$id": "419", "kind": "property", "name": "listFoo", "apiVersions": [ @@ -5016,11 +5210,11 @@ ], "serializedName": "listFoo", "type": { - "$id": "404", + "$id": "420", "kind": "array", "name": "ArrayAnotherDynamicModel", "valueType": { - "$ref": "400" + "$ref": "416" }, "crossLanguageDefinitionId": "TypeSpec.Array", "decorators": [] @@ -5040,7 +5234,7 @@ "isExactName": false }, { - "$id": "405", + "$id": "421", "kind": "property", "name": "listOfListFoo", "apiVersions": [ @@ -5049,11 +5243,11 @@ ], "serializedName": "listOfListFoo", "type": { - "$id": "406", + "$id": "422", "kind": "array", "name": "ArrayArray", "valueType": { - "$ref": "404" + "$ref": "420" }, "crossLanguageDefinitionId": "TypeSpec.Array", "decorators": [] @@ -5073,7 +5267,7 @@ "isExactName": false }, { - "$id": "407", + "$id": "423", "kind": "property", "name": "dictionaryFoo", "apiVersions": [ @@ -5082,17 +5276,17 @@ ], "serializedName": "dictionaryFoo", "type": { - "$id": "408", + "$id": "424", "kind": "dict", "keyType": { - "$id": "409", + "$id": "425", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, "valueType": { - "$ref": "400" + "$ref": "416" }, "decorators": [] }, @@ -5111,7 +5305,7 @@ "isExactName": false }, { - "$id": "410", + "$id": "426", "kind": "property", "name": "dictionaryOfDictionaryFoo", "apiVersions": [ @@ -5120,17 +5314,17 @@ ], "serializedName": "dictionaryOfDictionaryFoo", "type": { - "$id": "411", + "$id": "427", "kind": "dict", "keyType": { - "$id": "412", + "$id": "428", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, "valueType": { - "$ref": "408" + "$ref": "424" }, "decorators": [] }, @@ -5149,7 +5343,7 @@ "isExactName": false }, { - "$id": "413", + "$id": "429", "kind": "property", "name": "dictionaryListFoo", "apiVersions": [ @@ -5158,17 +5352,17 @@ ], "serializedName": "dictionaryListFoo", "type": { - "$id": "414", + "$id": "430", "kind": "dict", "keyType": { - "$id": "415", + "$id": "431", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, "valueType": { - "$ref": "404" + "$ref": "420" }, "decorators": [] }, @@ -5187,7 +5381,7 @@ "isExactName": false }, { - "$id": "416", + "$id": "432", "kind": "property", "name": "listOfDictionaryFoo", "apiVersions": [ @@ -5196,11 +5390,11 @@ ], "serializedName": "listOfDictionaryFoo", "type": { - "$id": "417", + "$id": "433", "kind": "array", "name": "ArrayRecord", "valueType": { - "$ref": "408" + "$ref": "424" }, "crossLanguageDefinitionId": "TypeSpec.Array", "decorators": [] @@ -5222,10 +5416,10 @@ ] }, { - "$ref": "400" + "$ref": "416" }, { - "$id": "418", + "$id": "434", "kind": "model", "name": "XmlAdvancedModel", "apiVersions": [ @@ -5254,7 +5448,7 @@ "isExactName": false, "properties": [ { - "$id": "419", + "$id": "435", "kind": "property", "name": "name", "apiVersions": [ @@ -5264,7 +5458,7 @@ "serializedName": "name", "doc": "A simple string property", "type": { - "$id": "420", + "$id": "436", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -5287,7 +5481,7 @@ "isExactName": false }, { - "$id": "421", + "$id": "437", "kind": "property", "name": "age", "apiVersions": [ @@ -5297,7 +5491,7 @@ "serializedName": "age", "doc": "An integer property", "type": { - "$id": "422", + "$id": "438", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -5320,7 +5514,7 @@ "isExactName": false }, { - "$id": "423", + "$id": "439", "kind": "property", "name": "enabled", "apiVersions": [ @@ -5330,7 +5524,7 @@ "serializedName": "enabled", "doc": "A boolean property", "type": { - "$id": "424", + "$id": "440", "kind": "boolean", "name": "boolean", "crossLanguageDefinitionId": "TypeSpec.boolean", @@ -5353,7 +5547,7 @@ "isExactName": false }, { - "$id": "425", + "$id": "441", "kind": "property", "name": "score", "apiVersions": [ @@ -5363,7 +5557,7 @@ "serializedName": "score", "doc": "A float property", "type": { - "$id": "426", + "$id": "442", "kind": "float32", "name": "float32", "crossLanguageDefinitionId": "TypeSpec.float32", @@ -5386,7 +5580,7 @@ "isExactName": false }, { - "$id": "427", + "$id": "443", "kind": "property", "name": "optionalString", "apiVersions": [ @@ -5396,7 +5590,7 @@ "serializedName": "optionalString", "doc": "An optional string", "type": { - "$id": "428", + "$id": "444", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -5419,7 +5613,7 @@ "isExactName": false }, { - "$id": "429", + "$id": "445", "kind": "property", "name": "optionalInt", "apiVersions": [ @@ -5429,7 +5623,7 @@ "serializedName": "optionalInt", "doc": "An optional integer", "type": { - "$id": "430", + "$id": "446", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -5452,7 +5646,7 @@ "isExactName": false }, { - "$id": "431", + "$id": "447", "kind": "property", "name": "nullableString", "apiVersions": [ @@ -5462,10 +5656,10 @@ "serializedName": "nullableString", "doc": "A nullable string", "type": { - "$id": "432", + "$id": "448", "kind": "nullable", "type": { - "$id": "433", + "$id": "449", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -5490,7 +5684,7 @@ "isExactName": false }, { - "$id": "434", + "$id": "450", "kind": "property", "name": "id", "apiVersions": [ @@ -5500,7 +5694,7 @@ "serializedName": "id", "doc": "A string as XML attribute", "type": { - "$id": "435", + "$id": "451", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -5528,7 +5722,7 @@ "isExactName": false }, { - "$id": "436", + "$id": "452", "kind": "property", "name": "version", "apiVersions": [ @@ -5538,7 +5732,7 @@ "serializedName": "version", "doc": "An integer as XML attribute", "type": { - "$id": "437", + "$id": "453", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -5566,7 +5760,7 @@ "isExactName": false }, { - "$id": "438", + "$id": "454", "kind": "property", "name": "isActive", "apiVersions": [ @@ -5576,7 +5770,7 @@ "serializedName": "isActive", "doc": "A boolean as XML attribute", "type": { - "$id": "439", + "$id": "455", "kind": "boolean", "name": "boolean", "crossLanguageDefinitionId": "TypeSpec.boolean", @@ -5604,7 +5798,7 @@ "isExactName": false }, { - "$id": "440", + "$id": "456", "kind": "property", "name": "originalName", "apiVersions": [ @@ -5614,7 +5808,7 @@ "serializedName": "RenamedProperty", "doc": "A property with a custom XML element name", "type": { - "$id": "441", + "$id": "457", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -5644,7 +5838,7 @@ "isExactName": false }, { - "$id": "442", + "$id": "458", "kind": "property", "name": "xmlIdentifier", "apiVersions": [ @@ -5654,7 +5848,7 @@ "serializedName": "xml-id", "doc": "An attribute with a custom XML name", "type": { - "$id": "443", + "$id": "459", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -5688,7 +5882,7 @@ "isExactName": false }, { - "$id": "444", + "$id": "460", "kind": "property", "name": "content", "apiVersions": [ @@ -5698,7 +5892,7 @@ "serializedName": "content", "doc": "Text content in the element (unwrapped string)", "type": { - "$id": "445", + "$id": "461", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -5726,7 +5920,7 @@ "isExactName": false }, { - "$id": "446", + "$id": "462", "kind": "property", "name": "unwrappedStrings", "apiVersions": [ @@ -5736,7 +5930,7 @@ "serializedName": "unwrappedStrings", "doc": "An unwrapped array of strings - items appear directly without wrapper", "type": { - "$ref": "262" + "$ref": "278" }, "optional": false, "readOnly": false, @@ -5761,7 +5955,7 @@ "isExactName": false }, { - "$id": "447", + "$id": "463", "kind": "property", "name": "unwrappedCounts", "apiVersions": [ @@ -5771,7 +5965,7 @@ "serializedName": "unwrappedCounts", "doc": "An unwrapped array of integers", "type": { - "$ref": "285" + "$ref": "301" }, "optional": false, "readOnly": false, @@ -5796,7 +5990,7 @@ "isExactName": false }, { - "$id": "448", + "$id": "464", "kind": "property", "name": "unwrappedItems", "apiVersions": [ @@ -5806,11 +6000,11 @@ "serializedName": "unwrappedItems", "doc": "An unwrapped array of models", "type": { - "$id": "449", + "$id": "465", "kind": "array", "name": "ArrayXmlItem", "valueType": { - "$id": "450", + "$id": "466", "kind": "model", "name": "XmlItem", "apiVersions": [ @@ -5839,7 +6033,7 @@ "isExactName": false, "properties": [ { - "$id": "451", + "$id": "467", "kind": "property", "name": "itemName", "apiVersions": [ @@ -5849,7 +6043,7 @@ "serializedName": "itemName", "doc": "The item name", "type": { - "$id": "452", + "$id": "468", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -5872,7 +6066,7 @@ "isExactName": false }, { - "$id": "453", + "$id": "469", "kind": "property", "name": "itemValue", "apiVersions": [ @@ -5882,7 +6076,7 @@ "serializedName": "itemValue", "doc": "The item value", "type": { - "$id": "454", + "$id": "470", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -5905,7 +6099,7 @@ "isExactName": false }, { - "$id": "455", + "$id": "471", "kind": "property", "name": "itemId", "apiVersions": [ @@ -5915,7 +6109,7 @@ "serializedName": "itemId", "doc": "Item ID as attribute", "type": { - "$id": "456", + "$id": "472", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -5970,7 +6164,7 @@ "isExactName": false }, { - "$id": "457", + "$id": "473", "kind": "property", "name": "wrappedColors", "apiVersions": [ @@ -5980,7 +6174,7 @@ "serializedName": "wrappedColors", "doc": "A wrapped array of strings (default)", "type": { - "$ref": "262" + "$ref": "278" }, "optional": false, "readOnly": false, @@ -6000,7 +6194,7 @@ "isExactName": false }, { - "$id": "458", + "$id": "474", "kind": "property", "name": "items", "apiVersions": [ @@ -6010,7 +6204,7 @@ "serializedName": "ItemCollection", "doc": "A wrapped array with custom wrapper name", "type": { - "$ref": "449" + "$ref": "465" }, "optional": false, "readOnly": false, @@ -6037,7 +6231,7 @@ "isExactName": false }, { - "$id": "459", + "$id": "475", "kind": "property", "name": "nestedModel", "apiVersions": [ @@ -6047,7 +6241,7 @@ "serializedName": "nestedModel", "doc": "A nested model property", "type": { - "$id": "460", + "$id": "476", "kind": "model", "name": "XmlNestedModel", "apiVersions": [ @@ -6069,7 +6263,7 @@ "isExactName": false, "properties": [ { - "$id": "461", + "$id": "477", "kind": "property", "name": "value", "apiVersions": [ @@ -6079,7 +6273,7 @@ "serializedName": "value", "doc": "The value of the nested model", "type": { - "$id": "462", + "$id": "478", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -6102,7 +6296,7 @@ "isExactName": false }, { - "$id": "463", + "$id": "479", "kind": "property", "name": "nestedId", "apiVersions": [ @@ -6112,7 +6306,7 @@ "serializedName": "nestedId", "doc": "An attribute on the nested model", "type": { - "$id": "464", + "$id": "480", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -6158,7 +6352,7 @@ "isExactName": false }, { - "$id": "465", + "$id": "481", "kind": "property", "name": "optionalNestedModel", "apiVersions": [ @@ -6168,7 +6362,7 @@ "serializedName": "optionalNestedModel", "doc": "An optional nested model", "type": { - "$ref": "460" + "$ref": "476" }, "optional": true, "readOnly": false, @@ -6187,7 +6381,7 @@ "isExactName": false }, { - "$id": "466", + "$id": "482", "kind": "property", "name": "metadata", "apiVersions": [ @@ -6197,17 +6391,17 @@ "serializedName": "metadata", "doc": "A dictionary property", "type": { - "$id": "467", + "$id": "483", "kind": "dict", "keyType": { - "$id": "468", + "$id": "484", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, "valueType": { - "$id": "469", + "$id": "485", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -6232,7 +6426,7 @@ "isExactName": false }, { - "$id": "470", + "$id": "486", "kind": "property", "name": "createdAt", "apiVersions": [ @@ -6242,12 +6436,12 @@ "serializedName": "createdAt", "doc": "A date-time property", "type": { - "$id": "471", + "$id": "487", "kind": "utcDateTime", "name": "utcDateTime", "encode": "rfc3339", "wireType": { - "$id": "472", + "$id": "488", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -6273,7 +6467,7 @@ "isExactName": false }, { - "$id": "473", + "$id": "489", "kind": "property", "name": "duration", "apiVersions": [ @@ -6283,12 +6477,12 @@ "serializedName": "duration", "doc": "A duration property", "type": { - "$id": "474", + "$id": "490", "kind": "duration", "name": "duration", "encode": "ISO8601", "wireType": { - "$id": "475", + "$id": "491", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -6314,7 +6508,7 @@ "isExactName": false }, { - "$id": "476", + "$id": "492", "kind": "property", "name": "data", "apiVersions": [ @@ -6324,7 +6518,7 @@ "serializedName": "data", "doc": "A bytes property", "type": { - "$id": "477", + "$id": "493", "kind": "bytes", "name": "bytes", "encode": "base64", @@ -6348,7 +6542,7 @@ "isExactName": false }, { - "$id": "478", + "$id": "494", "kind": "property", "name": "optionalRecordUnknown", "apiVersions": [ @@ -6358,7 +6552,7 @@ "serializedName": "optionalRecordUnknown", "doc": "optional record of unknown", "type": { - "$ref": "322" + "$ref": "338" }, "optional": true, "readOnly": false, @@ -6377,7 +6571,7 @@ "isExactName": false }, { - "$id": "479", + "$id": "495", "kind": "property", "name": "fixedEnum", "apiVersions": [ @@ -6406,7 +6600,7 @@ "isExactName": false }, { - "$id": "480", + "$id": "496", "kind": "property", "name": "extensibleEnum", "apiVersions": [ @@ -6435,7 +6629,7 @@ "isExactName": false }, { - "$id": "481", + "$id": "497", "kind": "property", "name": "optionalFixedEnum", "apiVersions": [ @@ -6464,7 +6658,7 @@ "isExactName": false }, { - "$id": "482", + "$id": "498", "kind": "property", "name": "optionalExtensibleEnum", "apiVersions": [ @@ -6493,7 +6687,7 @@ "isExactName": false }, { - "$id": "483", + "$id": "499", "kind": "property", "name": "label", "apiVersions": [ @@ -6502,7 +6696,7 @@ ], "serializedName": "label", "type": { - "$id": "484", + "$id": "500", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -6517,14 +6711,14 @@ "name": "TypeSpec.Xml.@ns", "arguments": { "ns": { - "$id": "485", + "$id": "501", "kind": "enumvalue", "decorators": [], "name": "ns1", "isExactName": false, "value": "https://example.com/ns1", "enumType": { - "$id": "486", + "$id": "502", "kind": "enum", "decorators": [ { @@ -6537,7 +6731,7 @@ "isExactName": false, "namespace": "SampleTypeSpec", "valueType": { - "$id": "487", + "$id": "503", "kind": "string", "decorators": [], "doc": "A sequence of textual characters.", @@ -6546,32 +6740,32 @@ }, "values": [ { - "$id": "488", + "$id": "504", "kind": "enumvalue", "decorators": [], "name": "ns1", "isExactName": false, "value": "https://example.com/ns1", "enumType": { - "$ref": "486" + "$ref": "502" }, "valueType": { - "$ref": "487" + "$ref": "503" }, "crossLanguageDefinitionId": "SampleTypeSpec.XmlNamespaces.ns1" }, { - "$id": "489", + "$id": "505", "kind": "enumvalue", "decorators": [], "name": "ns2", "isExactName": false, "value": "https://example.com/ns2", "enumType": { - "$ref": "486" + "$ref": "502" }, "valueType": { - "$ref": "487" + "$ref": "503" }, "crossLanguageDefinitionId": "SampleTypeSpec.XmlNamespaces.ns2" } @@ -6589,7 +6783,7 @@ "__accessSet": true }, "valueType": { - "$ref": "487" + "$ref": "503" }, "crossLanguageDefinitionId": "SampleTypeSpec.XmlNamespaces.ns1" } @@ -6616,7 +6810,7 @@ "isExactName": false }, { - "$id": "490", + "$id": "506", "kind": "property", "name": "daysUsed", "apiVersions": [ @@ -6625,7 +6819,7 @@ ], "serializedName": "daysUsed", "type": { - "$id": "491", + "$id": "507", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -6640,17 +6834,17 @@ "name": "TypeSpec.Xml.@ns", "arguments": { "ns": { - "$id": "492", + "$id": "508", "kind": "enumvalue", "decorators": [], "name": "ns2", "isExactName": false, "value": "https://example.com/ns2", "enumType": { - "$ref": "486" + "$ref": "502" }, "valueType": { - "$ref": "487" + "$ref": "503" }, "crossLanguageDefinitionId": "SampleTypeSpec.XmlNamespaces.ns2" } @@ -6673,7 +6867,7 @@ "isExactName": false }, { - "$id": "493", + "$id": "509", "kind": "property", "name": "fooItems", "apiVersions": [ @@ -6682,7 +6876,7 @@ ], "serializedName": "fooItems", "type": { - "$ref": "262" + "$ref": "278" }, "optional": false, "readOnly": false, @@ -6714,7 +6908,7 @@ "isExactName": false }, { - "$id": "494", + "$id": "510", "kind": "property", "name": "anotherModel", "apiVersions": [ @@ -6723,7 +6917,7 @@ ], "serializedName": "anotherModel", "type": { - "$ref": "460" + "$ref": "476" }, "optional": false, "readOnly": false, @@ -6754,7 +6948,7 @@ "isExactName": false }, { - "$id": "495", + "$id": "511", "kind": "property", "name": "modelsWithNamespaces", "apiVersions": [ @@ -6763,11 +6957,11 @@ ], "serializedName": "modelsWithNamespaces", "type": { - "$id": "496", + "$id": "512", "kind": "array", "name": "ArrayXmlModelWithNamespace", "valueType": { - "$id": "497", + "$id": "513", "kind": "model", "name": "XmlModelWithNamespace", "apiVersions": [ @@ -6800,7 +6994,7 @@ "isExactName": false, "properties": [ { - "$id": "498", + "$id": "514", "kind": "property", "name": "foo", "apiVersions": [ @@ -6809,7 +7003,7 @@ ], "serializedName": "foo", "type": { - "$id": "499", + "$id": "515", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -6858,7 +7052,7 @@ "isExactName": false }, { - "$id": "500", + "$id": "516", "kind": "property", "name": "unwrappedModelsWithNamespaces", "apiVersions": [ @@ -6867,7 +7061,7 @@ ], "serializedName": "unwrappedModelsWithNamespaces", "type": { - "$ref": "496" + "$ref": "512" }, "optional": false, "readOnly": false, @@ -6892,7 +7086,7 @@ "isExactName": false }, { - "$id": "501", + "$id": "517", "kind": "property", "name": "listOfListFoo", "apiVersions": [ @@ -6901,11 +7095,11 @@ ], "serializedName": "listOfListFoo", "type": { - "$id": "502", + "$id": "518", "kind": "array", "name": "ArrayArray1", "valueType": { - "$ref": "449" + "$ref": "465" }, "crossLanguageDefinitionId": "TypeSpec.Array", "decorators": [] @@ -6928,7 +7122,7 @@ "isExactName": false }, { - "$id": "503", + "$id": "519", "kind": "property", "name": "dictionaryFoo", "apiVersions": [ @@ -6937,17 +7131,17 @@ ], "serializedName": "dictionaryFoo", "type": { - "$id": "504", + "$id": "520", "kind": "dict", "keyType": { - "$id": "505", + "$id": "521", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, "valueType": { - "$ref": "450" + "$ref": "466" }, "decorators": [] }, @@ -6968,7 +7162,7 @@ "isExactName": false }, { - "$id": "506", + "$id": "522", "kind": "property", "name": "dictionaryOfDictionaryFoo", "apiVersions": [ @@ -6977,17 +7171,17 @@ ], "serializedName": "dictionaryOfDictionaryFoo", "type": { - "$id": "507", + "$id": "523", "kind": "dict", "keyType": { - "$id": "508", + "$id": "524", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, "valueType": { - "$ref": "504" + "$ref": "520" }, "decorators": [] }, @@ -7008,7 +7202,7 @@ "isExactName": false }, { - "$id": "509", + "$id": "525", "kind": "property", "name": "dictionaryListFoo", "apiVersions": [ @@ -7017,17 +7211,17 @@ ], "serializedName": "dictionaryListFoo", "type": { - "$id": "510", + "$id": "526", "kind": "dict", "keyType": { - "$id": "511", + "$id": "527", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, "valueType": { - "$ref": "449" + "$ref": "465" }, "decorators": [] }, @@ -7048,7 +7242,7 @@ "isExactName": false }, { - "$id": "512", + "$id": "528", "kind": "property", "name": "listOfDictionaryFoo", "apiVersions": [ @@ -7057,11 +7251,11 @@ ], "serializedName": "listOfDictionaryFoo", "type": { - "$id": "513", + "$id": "529", "kind": "array", "name": "ArrayRecord1", "valueType": { - "$ref": "504" + "$ref": "520" }, "crossLanguageDefinitionId": "TypeSpec.Array", "decorators": [] @@ -7086,16 +7280,16 @@ ] }, { - "$ref": "450" + "$ref": "466" }, { - "$ref": "460" + "$ref": "476" }, { - "$ref": "497" + "$ref": "513" }, { - "$id": "514", + "$id": "530", "kind": "model", "name": "Cat", "apiVersions": [ @@ -7110,7 +7304,7 @@ "isExactName": false, "properties": [ { - "$id": "515", + "$id": "531", "kind": "property", "name": "id", "apiVersions": [ @@ -7119,7 +7313,7 @@ ], "serializedName": "id", "type": { - "$id": "516", + "$id": "532", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -7146,7 +7340,7 @@ "isExactName": false }, { - "$id": "517", + "$id": "533", "kind": "property", "name": "boolPart", "apiVersions": [ @@ -7155,7 +7349,7 @@ ], "serializedName": "boolPart", "type": { - "$id": "518", + "$id": "534", "kind": "boolean", "name": "boolean", "crossLanguageDefinitionId": "TypeSpec.boolean", @@ -7182,7 +7376,7 @@ "isExactName": false }, { - "$id": "519", + "$id": "535", "kind": "property", "name": "int32Part", "apiVersions": [ @@ -7191,7 +7385,7 @@ ], "serializedName": "int32Part", "type": { - "$id": "520", + "$id": "536", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -7218,7 +7412,7 @@ "isExactName": false }, { - "$id": "521", + "$id": "537", "kind": "property", "name": "int64Part", "apiVersions": [ @@ -7227,7 +7421,7 @@ ], "serializedName": "int64Part", "type": { - "$id": "522", + "$id": "538", "kind": "int64", "name": "int64", "crossLanguageDefinitionId": "TypeSpec.int64", @@ -7254,7 +7448,7 @@ "isExactName": false }, { - "$id": "523", + "$id": "539", "kind": "property", "name": "float32Part", "apiVersions": [ @@ -7263,7 +7457,7 @@ ], "serializedName": "float32Part", "type": { - "$id": "524", + "$id": "540", "kind": "float32", "name": "float32", "crossLanguageDefinitionId": "TypeSpec.float32", @@ -7290,7 +7484,7 @@ "isExactName": false }, { - "$id": "525", + "$id": "541", "kind": "property", "name": "float64Part", "apiVersions": [ @@ -7299,7 +7493,7 @@ ], "serializedName": "float64Part", "type": { - "$id": "526", + "$id": "542", "kind": "float64", "name": "float64", "crossLanguageDefinitionId": "TypeSpec.float64", @@ -7326,7 +7520,7 @@ "isExactName": false }, { - "$id": "527", + "$id": "543", "kind": "property", "name": "decimalPart", "apiVersions": [ @@ -7335,7 +7529,7 @@ ], "serializedName": "decimalPart", "type": { - "$id": "528", + "$id": "544", "kind": "decimal128", "name": "decimal128", "crossLanguageDefinitionId": "TypeSpec.decimal128", @@ -7362,7 +7556,7 @@ "isExactName": false }, { - "$id": "529", + "$id": "545", "kind": "property", "name": "int8Part", "apiVersions": [ @@ -7371,7 +7565,7 @@ ], "serializedName": "int8Part", "type": { - "$id": "530", + "$id": "546", "kind": "int8", "name": "int8", "crossLanguageDefinitionId": "TypeSpec.int8", @@ -7398,7 +7592,7 @@ "isExactName": false }, { - "$id": "531", + "$id": "547", "kind": "property", "name": "uint8Part", "apiVersions": [ @@ -7407,7 +7601,7 @@ ], "serializedName": "uint8Part", "type": { - "$id": "532", + "$id": "548", "kind": "uint8", "name": "uint8", "crossLanguageDefinitionId": "TypeSpec.uint8", @@ -7434,7 +7628,7 @@ "isExactName": false }, { - "$id": "533", + "$id": "549", "kind": "property", "name": "dictionaryPart", "apiVersions": [ @@ -7443,7 +7637,7 @@ ], "serializedName": "dictionaryPart", "type": { - "$ref": "467" + "$ref": "483" }, "optional": false, "readOnly": false, @@ -7466,7 +7660,7 @@ "isExactName": false }, { - "$id": "534", + "$id": "550", "kind": "property", "name": "dictionaryModelPart", "apiVersions": [ @@ -7475,17 +7669,17 @@ ], "serializedName": "dictionaryModelPart", "type": { - "$id": "535", + "$id": "551", "kind": "dict", "keyType": { - "$id": "536", + "$id": "552", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, "valueType": { - "$ref": "256" + "$ref": "272" }, "decorators": [] }, @@ -7510,7 +7704,7 @@ "isExactName": false }, { - "$id": "537", + "$id": "553", "kind": "property", "name": "listPart", "apiVersions": [ @@ -7519,7 +7713,7 @@ ], "serializedName": "listPart", "type": { - "$ref": "262" + "$ref": "278" }, "optional": false, "readOnly": false, @@ -7542,7 +7736,7 @@ "isExactName": false }, { - "$id": "538", + "$id": "554", "kind": "property", "name": "listModelPart", "apiVersions": [ @@ -7551,7 +7745,7 @@ ], "serializedName": "listModelPart", "type": { - "$ref": "354" + "$ref": "370" }, "optional": false, "readOnly": false, @@ -7574,7 +7768,7 @@ "isExactName": false }, { - "$id": "539", + "$id": "555", "kind": "property", "name": "multipleListPart", "apiVersions": [ @@ -7583,11 +7777,11 @@ ], "serializedName": "multipleListPart", "type": { - "$id": "540", + "$id": "556", "kind": "array", "name": "ArrayHttpPart", "valueType": { - "$id": "541", + "$id": "557", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -7617,7 +7811,7 @@ "isExactName": false }, { - "$id": "542", + "$id": "558", "kind": "property", "name": "profileImage", "apiVersions": [ @@ -7626,7 +7820,7 @@ ], "serializedName": "profileImage", "type": { - "$id": "543", + "$id": "559", "kind": "model", "name": "File", "apiVersions": [], @@ -7641,14 +7835,14 @@ "isFileType": true, "properties": [ { - "$id": "544", + "$id": "560", "kind": "property", "name": "contentType", "apiVersions": [], "summary": "The allowed media (MIME) types of the file contents.", "doc": "The allowed media (MIME) types of the file contents.\n\nIn file bodies, this value comes from the `Content-Type` header of the request or response. In JSON bodies,\nthis value is serialized as a field in the response.\n\nNOTE: this is not _necessarily_ the same as the `Content-Type` header of the request or response, but\nit will be for file bodies. It may be different if the file is serialized as a JSON object. It always refers to the\n_contents_ of the file, and not necessarily the way the file itself is transmitted or serialized.", "type": { - "$id": "545", + "$id": "561", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -7665,14 +7859,14 @@ "isExactName": false }, { - "$id": "546", + "$id": "562", "kind": "property", "name": "filename", "apiVersions": [], "summary": "The name of the file, if any.", "doc": "The name of the file, if any.\n\nIn file bodies, this value comes from the `filename` parameter of the `Content-Disposition` header of the response\nor multipart payload. In JSON bodies, this value is serialized as a field in the response.\n\nNOTE: By default, `filename` cannot be sent in request payloads and can only be sent in responses and multipart\npayloads, as the `Content-Disposition` header is not valid in requests. If you want to send the `filename` in a request,\nyou must extend the `File` model and override the `filename` property with a different location defined by HTTP metadata\ndecorators.", "type": { - "$id": "547", + "$id": "563", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -7689,14 +7883,14 @@ "isExactName": false }, { - "$id": "548", + "$id": "564", "kind": "property", "name": "contents", "apiVersions": [], "summary": "The contents of the file.", "doc": "The contents of the file.\n\nIn file bodies, this value comes from the body of the request, response, or multipart payload. In JSON bodies,\nthis value is serialized as a field in the response.", "type": { - "$id": "549", + "$id": "565", "kind": "bytes", "name": "bytes", "encode": "base64", @@ -7726,12 +7920,12 @@ "isFilePart": true, "isMulti": false, "filename": { - "$id": "550", + "$id": "566", "doc": "The name of the file, if any.\n\nIn file bodies, this value comes from the `filename` parameter of the `Content-Disposition` header of the response\nor multipart payload. In JSON bodies, this value is serialized as a field in the response.\n\nNOTE: By default, `filename` cannot be sent in request payloads and can only be sent in responses and multipart\npayloads, as the `Content-Disposition` header is not valid in requests. If you want to send the `filename` in a request,\nyou must extend the `File` model and override the `filename` property with a different location defined by HTTP metadata\ndecorators.", "summary": "The name of the file, if any.", "apiVersions": [], "type": { - "$id": "551", + "$id": "567", "kind": "string", "decorators": [], "doc": "A sequence of textual characters.", @@ -7762,12 +7956,12 @@ "serializationOptions": {} }, "contentType": { - "$id": "552", + "$id": "568", "doc": "The allowed media (MIME) types of the file contents.\n\nIn file bodies, this value comes from the `Content-Type` header of the request or response. In JSON bodies,\nthis value is serialized as a field in the response.\n\nNOTE: this is not _necessarily_ the same as the `Content-Type` header of the request or response, but\nit will be for file bodies. It may be different if the file is serialized as a JSON object. It always refers to the\n_contents_ of the file, and not necessarily the way the file itself is transmitted or serialized.", "summary": "The allowed media (MIME) types of the file contents.", "apiVersions": [], "type": { - "$id": "553", + "$id": "569", "kind": "string", "decorators": [], "doc": "A sequence of textual characters.", @@ -7808,7 +8002,7 @@ "isExactName": false }, { - "$id": "554", + "$id": "570", "kind": "property", "name": "extensibleEnumPart", "apiVersions": [ @@ -7840,7 +8034,7 @@ "isExactName": false }, { - "$id": "555", + "$id": "571", "kind": "property", "name": "fixedEnumPart", "apiVersions": [ @@ -7872,7 +8066,7 @@ "isExactName": false }, { - "$id": "556", + "$id": "572", "kind": "property", "name": "intExtensibleEnumPart", "apiVersions": [ @@ -7904,7 +8098,7 @@ "isExactName": false }, { - "$id": "557", + "$id": "573", "kind": "property", "name": "intFixedEnumPart", "apiVersions": [ @@ -7938,7 +8132,7 @@ ] }, { - "$id": "558", + "$id": "574", "kind": "model", "name": "File", "apiVersions": [], @@ -7953,18 +8147,18 @@ "isFileType": true, "properties": [ { - "$ref": "544" + "$ref": "560" }, { - "$ref": "546" + "$ref": "562" }, { - "$ref": "548" + "$ref": "564" } ] }, { - "$id": "559", + "$id": "575", "kind": "model", "name": "StreamingItem", "apiVersions": [ @@ -7983,7 +8177,7 @@ "isExactName": false, "properties": [ { - "$id": "560", + "$id": "576", "kind": "property", "name": "message", "apiVersions": [ @@ -7992,7 +8186,7 @@ ], "serializedName": "message", "type": { - "$id": "561", + "$id": "577", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -8015,7 +8209,193 @@ ] }, { - "$id": "562", + "$id": "578", + "kind": "model", + "name": "PreviewDetails", + "apiVersions": [ + "2024-07-16-preview", + "2024-08-16-preview" + ], + "namespace": "SampleTypeSpec", + "crossLanguageDefinitionId": "SampleTypeSpec.PreviewDetails", + "usage": "Output,Json", + "decorators": [], + "serializationOptions": { + "json": { + "name": "PreviewDetails" + } + }, + "isExactName": false, + "properties": [ + { + "$id": "579", + "kind": "property", + "name": "choice", + "apiVersions": [ + "2024-07-16-preview", + "2024-08-16-preview" + ], + "serializedName": "choice", + "type": { + "$ref": "57" + }, + "optional": false, + "readOnly": false, + "discriminator": false, + "flatten": false, + "decorators": [], + "crossLanguageDefinitionId": "SampleTypeSpec.PreviewDetails.choice", + "serializationOptions": { + "json": { + "name": "choice" + } + }, + "isHttpMetadata": false, + "isExactName": false + } + ], + "experimental": { + "diagnosticId": "SAMPLE0003", + "dependsOn": [ + "SAMPLE0004" + ] + } + }, + { + "$id": "580", + "kind": "model", + "name": "LifecycleModel", + "apiVersions": [ + "2024-07-16-preview", + "2024-08-16-preview" + ], + "namespace": "SampleTypeSpec", + "crossLanguageDefinitionId": "SampleTypeSpec.LifecycleModel", + "usage": "Output,Json", + "decorators": [], + "serializationOptions": { + "json": { + "name": "LifecycleModel" + } + }, + "isExactName": false, + "properties": [ + { + "$id": "581", + "kind": "property", + "name": "preview", + "apiVersions": [ + "2024-07-16-preview", + "2024-08-16-preview" + ], + "serializedName": "preview", + "type": { + "$ref": "578" + }, + "optional": true, + "readOnly": false, + "discriminator": false, + "flatten": false, + "decorators": [], + "crossLanguageDefinitionId": "SampleTypeSpec.LifecycleModel.preview", + "serializationOptions": { + "json": { + "name": "preview" + } + }, + "isHttpMetadata": false, + "isExactName": false, + "experimental": { + "diagnosticId": "SAMPLE0008", + "dependsOn": [ + "SAMPLE0003" + ] + } + }, + { + "$id": "582", + "kind": "property", + "name": "choice", + "apiVersions": [ + "2024-07-16-preview", + "2024-08-16-preview" + ], + "serializedName": "choice", + "type": { + "$ref": "61" + }, + "optional": false, + "readOnly": false, + "discriminator": false, + "flatten": false, + "decorators": [], + "crossLanguageDefinitionId": "SampleTypeSpec.LifecycleModel.choice", + "serializationOptions": { + "json": { + "name": "choice" + } + }, + "isHttpMetadata": false, + "isExactName": false + } + ] + }, + { + "$id": "583", + "kind": "model", + "name": "PagePreviewDetails", + "apiVersions": [ + "2024-07-16-preview", + "2024-08-16-preview" + ], + "namespace": "SampleTypeSpec", + "crossLanguageDefinitionId": "SampleTypeSpec.Page", + "usage": "Output,Json", + "decorators": [], + "serializationOptions": { + "json": { + "name": "Page" + } + }, + "isExactName": false, + "properties": [ + { + "$id": "584", + "kind": "property", + "name": "items", + "apiVersions": [ + "2024-07-16-preview", + "2024-08-16-preview" + ], + "serializedName": "items", + "type": { + "$id": "585", + "kind": "array", + "name": "ArrayPreviewDetails", + "valueType": { + "$ref": "578" + }, + "crossLanguageDefinitionId": "TypeSpec.Array", + "decorators": [] + }, + "optional": false, + "readOnly": false, + "discriminator": false, + "flatten": false, + "decorators": [], + "crossLanguageDefinitionId": "SampleTypeSpec.Page.items", + "serializationOptions": { + "json": { + "name": "items" + } + }, + "isHttpMetadata": false, + "isExactName": false + } + ] + }, + { + "$id": "586", "kind": "model", "name": "Animal", "apiVersions": [ @@ -8034,7 +8414,7 @@ }, "isExactName": false, "discriminatorProperty": { - "$id": "563", + "$id": "587", "kind": "property", "name": "kind", "apiVersions": [ @@ -8044,7 +8424,7 @@ "serializedName": "kind", "doc": "The kind of animal", "type": { - "$id": "564", + "$id": "588", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -8066,10 +8446,10 @@ }, "properties": [ { - "$ref": "563" + "$ref": "587" }, { - "$id": "565", + "$id": "589", "kind": "property", "name": "name", "apiVersions": [ @@ -8079,7 +8459,7 @@ "serializedName": "name", "doc": "Name of the animal", "type": { - "$id": "566", + "$id": "590", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -8102,7 +8482,7 @@ ], "discriminatedSubtypes": { "pet": { - "$id": "567", + "$id": "591", "kind": "model", "name": "Pet", "apiVersions": [ @@ -8122,7 +8502,7 @@ }, "isExactName": false, "discriminatorProperty": { - "$id": "568", + "$id": "592", "kind": "property", "name": "kind", "apiVersions": [ @@ -8131,7 +8511,7 @@ ], "serializedName": "kind", "type": { - "$ref": "80" + "$ref": "88" }, "optional": false, "readOnly": false, @@ -8148,14 +8528,14 @@ "isExactName": false }, "baseModel": { - "$ref": "562" + "$ref": "586" }, "properties": [ { - "$ref": "568" + "$ref": "592" }, { - "$id": "569", + "$id": "593", "kind": "property", "name": "trained", "apiVersions": [ @@ -8165,7 +8545,7 @@ "serializedName": "trained", "doc": "Whether the pet is trained", "type": { - "$id": "570", + "$id": "594", "kind": "boolean", "name": "boolean", "crossLanguageDefinitionId": "TypeSpec.boolean", @@ -8188,7 +8568,7 @@ ], "discriminatedSubtypes": { "dog": { - "$id": "571", + "$id": "595", "kind": "model", "name": "Dog", "apiVersions": [ @@ -8208,11 +8588,11 @@ }, "isExactName": false, "baseModel": { - "$ref": "567" + "$ref": "591" }, "properties": [ { - "$id": "572", + "$id": "596", "kind": "property", "name": "kind", "apiVersions": [ @@ -8221,7 +8601,7 @@ ], "serializedName": "kind", "type": { - "$ref": "82" + "$ref": "90" }, "optional": false, "readOnly": false, @@ -8238,7 +8618,7 @@ "isExactName": false }, { - "$id": "573", + "$id": "597", "kind": "property", "name": "breed", "apiVersions": [ @@ -8248,7 +8628,7 @@ "serializedName": "breed", "doc": "The breed of the dog", "type": { - "$id": "574", + "$id": "598", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -8273,18 +8653,18 @@ } }, "dog": { - "$ref": "571" + "$ref": "595" } } }, { - "$ref": "567" + "$ref": "591" }, { - "$ref": "571" + "$ref": "595" }, { - "$id": "575", + "$id": "599", "kind": "model", "name": "Tree", "apiVersions": [ @@ -8309,7 +8689,7 @@ }, "isExactName": false, "baseModel": { - "$id": "576", + "$id": "600", "kind": "model", "name": "Plant", "apiVersions": [ @@ -8333,7 +8713,7 @@ }, "isExactName": false, "discriminatorProperty": { - "$id": "577", + "$id": "601", "kind": "property", "name": "species", "apiVersions": [ @@ -8343,7 +8723,7 @@ "serializedName": "species", "doc": "The species of plant", "type": { - "$id": "578", + "$id": "602", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -8370,10 +8750,10 @@ }, "properties": [ { - "$ref": "577" + "$ref": "601" }, { - "$id": "579", + "$id": "603", "kind": "property", "name": "id", "apiVersions": [ @@ -8383,7 +8763,7 @@ "serializedName": "id", "doc": "The unique identifier of the plant", "type": { - "$id": "580", + "$id": "604", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -8409,7 +8789,7 @@ "isExactName": false }, { - "$id": "581", + "$id": "605", "kind": "property", "name": "height", "apiVersions": [ @@ -8419,7 +8799,7 @@ "serializedName": "height", "doc": "The height of the plant in centimeters", "type": { - "$id": "582", + "$id": "606", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -8447,13 +8827,13 @@ ], "discriminatedSubtypes": { "tree": { - "$ref": "575" + "$ref": "599" } } }, "properties": [ { - "$id": "583", + "$id": "607", "kind": "property", "name": "species", "apiVersions": [ @@ -8462,7 +8842,7 @@ ], "serializedName": "species", "type": { - "$ref": "84" + "$ref": "92" }, "optional": false, "readOnly": false, @@ -8484,7 +8864,7 @@ "isExactName": false }, { - "$id": "584", + "$id": "608", "kind": "property", "name": "age", "apiVersions": [ @@ -8494,7 +8874,7 @@ "serializedName": "age", "doc": "The age of the tree in years", "type": { - "$id": "585", + "$id": "609", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -8522,10 +8902,10 @@ ] }, { - "$ref": "576" + "$ref": "600" }, { - "$id": "586", + "$id": "610", "kind": "model", "name": "GetWidgetMetricsResponse", "apiVersions": [ @@ -8544,7 +8924,7 @@ "isExactName": false, "properties": [ { - "$id": "587", + "$id": "611", "kind": "property", "name": "numSold", "apiVersions": [ @@ -8553,7 +8933,7 @@ ], "serializedName": "numSold", "type": { - "$id": "588", + "$id": "612", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -8574,7 +8954,7 @@ "isExactName": false }, { - "$id": "589", + "$id": "613", "kind": "property", "name": "averagePrice", "apiVersions": [ @@ -8583,7 +8963,7 @@ ], "serializedName": "averagePrice", "type": { - "$id": "590", + "$id": "614", "kind": "float32", "name": "float32", "crossLanguageDefinitionId": "TypeSpec.float32", @@ -8606,7 +8986,7 @@ ] }, { - "$id": "591", + "$id": "615", "kind": "model", "name": "GetNotebookResponse", "apiVersions": [ @@ -8625,7 +9005,7 @@ "isExactName": false, "properties": [ { - "$id": "592", + "$id": "616", "kind": "property", "name": "name", "apiVersions": [ @@ -8634,7 +9014,7 @@ ], "serializedName": "name", "type": { - "$id": "593", + "$id": "617", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -8655,7 +9035,7 @@ "isExactName": false }, { - "$id": "594", + "$id": "618", "kind": "property", "name": "content", "apiVersions": [ @@ -8664,7 +9044,7 @@ ], "serializedName": "content", "type": { - "$id": "595", + "$id": "619", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -8687,7 +9067,7 @@ ] }, { - "$id": "596", + "$id": "620", "kind": "model", "name": "NullableDynamicModel", "apiVersions": [ @@ -8712,7 +9092,7 @@ "isExactName": false, "properties": [ { - "$id": "597", + "$id": "621", "kind": "property", "name": "modelValue", "apiVersions": [ @@ -8721,10 +9101,10 @@ ], "serializedName": "modelValue", "type": { - "$id": "598", + "$id": "622", "kind": "nullable", "type": { - "$ref": "400" + "$ref": "416" }, "namespace": "SampleTypeSpec" }, @@ -8743,7 +9123,7 @@ "isExactName": false }, { - "$id": "599", + "$id": "623", "kind": "property", "name": "children", "apiVersions": [ @@ -8752,17 +9132,17 @@ ], "serializedName": "children", "type": { - "$id": "600", + "$id": "624", "kind": "nullable", "type": { - "$id": "601", + "$id": "625", "kind": "array", "name": "Array2", "valueType": { - "$id": "602", + "$id": "626", "kind": "nullable", "type": { - "$ref": "400" + "$ref": "416" }, "namespace": "SampleTypeSpec" }, @@ -8786,7 +9166,7 @@ "isExactName": false }, { - "$id": "603", + "$id": "627", "kind": "property", "name": "childDictionary", "apiVersions": [ @@ -8795,23 +9175,23 @@ ], "serializedName": "childDictionary", "type": { - "$id": "604", + "$id": "628", "kind": "nullable", "type": { - "$id": "605", + "$id": "629", "kind": "dict", "keyType": { - "$id": "606", + "$id": "630", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, "valueType": { - "$id": "607", + "$id": "631", "kind": "nullable", "type": { - "$ref": "400" + "$ref": "416" }, "namespace": "SampleTypeSpec" }, @@ -8834,7 +9214,7 @@ "isExactName": false }, { - "$id": "608", + "$id": "632", "kind": "property", "name": "nestedChildren", "apiVersions": [ @@ -8843,24 +9223,24 @@ ], "serializedName": "nestedChildren", "type": { - "$id": "609", + "$id": "633", "kind": "nullable", "type": { - "$id": "610", + "$id": "634", "kind": "array", "name": "Array3", "valueType": { - "$id": "611", + "$id": "635", "kind": "nullable", "type": { - "$id": "612", + "$id": "636", "kind": "array", "name": "Array4", "valueType": { - "$id": "613", + "$id": "637", "kind": "nullable", "type": { - "$ref": "400" + "$ref": "416" }, "namespace": "SampleTypeSpec" }, @@ -8889,7 +9269,7 @@ "isExactName": false }, { - "$id": "614", + "$id": "638", "kind": "property", "name": "nestedChildDictionary", "apiVersions": [ @@ -8898,36 +9278,36 @@ ], "serializedName": "nestedChildDictionary", "type": { - "$id": "615", + "$id": "639", "kind": "nullable", "type": { - "$id": "616", + "$id": "640", "kind": "dict", "keyType": { - "$id": "617", + "$id": "641", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, "valueType": { - "$id": "618", + "$id": "642", "kind": "nullable", "type": { - "$id": "619", + "$id": "643", "kind": "dict", "keyType": { - "$id": "620", + "$id": "644", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, "valueType": { - "$id": "621", + "$id": "645", "kind": "nullable", "type": { - "$ref": "400" + "$ref": "416" }, "namespace": "SampleTypeSpec" }, @@ -8954,7 +9334,7 @@ "isExactName": false }, { - "$id": "622", + "$id": "646", "kind": "property", "name": "dictionaryChildren", "apiVersions": [ @@ -8963,30 +9343,30 @@ ], "serializedName": "dictionaryChildren", "type": { - "$id": "623", + "$id": "647", "kind": "nullable", "type": { - "$id": "624", + "$id": "648", "kind": "dict", "keyType": { - "$id": "625", + "$id": "649", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, "valueType": { - "$id": "626", + "$id": "650", "kind": "nullable", "type": { - "$id": "627", + "$id": "651", "kind": "array", "name": "Array5", "valueType": { - "$id": "628", + "$id": "652", "kind": "nullable", "type": { - "$ref": "400" + "$ref": "416" }, "namespace": "SampleTypeSpec" }, @@ -9014,7 +9394,7 @@ "isExactName": false }, { - "$id": "629", + "$id": "653", "kind": "property", "name": "listOfDictionaries", "apiVersions": [ @@ -9023,30 +9403,30 @@ ], "serializedName": "listOfDictionaries", "type": { - "$id": "630", + "$id": "654", "kind": "nullable", "type": { - "$id": "631", + "$id": "655", "kind": "array", "name": "Array6", "valueType": { - "$id": "632", + "$id": "656", "kind": "nullable", "type": { - "$id": "633", + "$id": "657", "kind": "dict", "keyType": { - "$id": "634", + "$id": "658", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", "decorators": [] }, "valueType": { - "$id": "635", + "$id": "659", "kind": "nullable", "type": { - "$ref": "400" + "$ref": "416" }, "namespace": "SampleTypeSpec" }, @@ -9076,7 +9456,7 @@ ] }, { - "$id": "636", + "$id": "660", "kind": "model", "name": "JsonlStreamStreamingItem", "apiVersions": [], @@ -9089,7 +9469,7 @@ "isExactName": false, "properties": [ { - "$id": "637", + "$id": "661", "kind": "property", "name": "contentType", "apiVersions": [ @@ -9097,7 +9477,7 @@ "2024-08-16-preview" ], "type": { - "$ref": "196" + "$ref": "204" }, "optional": false, "readOnly": false, @@ -9110,7 +9490,7 @@ "isExactName": false }, { - "$id": "638", + "$id": "662", "kind": "property", "name": "body", "apiVersions": [ @@ -9118,7 +9498,7 @@ "2024-08-16-preview" ], "type": { - "$id": "639", + "$id": "663", "kind": "bytes", "name": "bytes", "crossLanguageDefinitionId": "", @@ -9139,7 +9519,7 @@ ], "clients": [ { - "$id": "640", + "$id": "664", "kind": "client", "name": "SampleTypeSpecClient", "isExactName": false, @@ -9147,7 +9527,7 @@ "doc": "This is a sample typespec project.", "methods": [ { - "$id": "641", + "$id": "665", "kind": "basic", "name": "sayHi", "isExactName": false, @@ -9158,7 +9538,7 @@ ], "doc": "Return hi", "operation": { - "$id": "642", + "$id": "666", "name": "sayHi", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -9166,12 +9546,12 @@ "accessibility": "public", "parameters": [ { - "$id": "643", + "$id": "667", "kind": "header", "name": "headParameter", "serializedName": "head-parameter", "type": { - "$id": "644", + "$id": "668", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -9186,12 +9566,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.sayHi.headParameter", "methodParameterSegments": [ { - "$id": "645", + "$id": "669", "kind": "method", "name": "headParameter", "serializedName": "head-parameter", "type": { - "$id": "646", + "$id": "670", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -9211,12 +9591,12 @@ "isExactName": false }, { - "$id": "647", + "$id": "671", "kind": "query", "name": "queryParameter", "serializedName": "queryParameter", "type": { - "$id": "648", + "$id": "672", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -9231,12 +9611,12 @@ "readOnly": false, "methodParameterSegments": [ { - "$id": "649", + "$id": "673", "kind": "method", "name": "queryParameter", "serializedName": "queryParameter", "type": { - "$id": "650", + "$id": "674", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -9256,12 +9636,12 @@ "isExactName": false }, { - "$id": "651", + "$id": "675", "kind": "query", "name": "optionalQuery", "serializedName": "optionalQuery", "type": { - "$id": "652", + "$id": "676", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -9276,12 +9656,12 @@ "readOnly": false, "methodParameterSegments": [ { - "$id": "653", + "$id": "677", "kind": "method", "name": "optionalQuery", "serializedName": "optionalQuery", "type": { - "$id": "654", + "$id": "678", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -9301,12 +9681,12 @@ "isExactName": false }, { - "$id": "655", + "$id": "679", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "86" + "$ref": "94" }, "isApiVersion": false, "optional": false, @@ -9317,12 +9697,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.sayHi.accept", "methodParameterSegments": [ { - "$id": "656", + "$id": "680", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "86" + "$ref": "94" }, "location": "Header", "isApiVersion": false, @@ -9344,7 +9724,7 @@ 200 ], "bodyType": { - "$ref": "256" + "$ref": "272" }, "headers": [], "isErrorResponse": false, @@ -9370,21 +9750,21 @@ }, "parameters": [ { - "$ref": "645" + "$ref": "669" }, { - "$ref": "649" + "$ref": "673" }, { - "$ref": "653" + "$ref": "677" }, { - "$ref": "656" + "$ref": "680" } ], "response": { "type": { - "$ref": "256" + "$ref": "272" } }, "isOverride": false, @@ -9393,7 +9773,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.sayHi" }, { - "$id": "657", + "$id": "681", "kind": "basic", "name": "helloAgain", "isExactName": false, @@ -9404,7 +9784,7 @@ ], "doc": "Return hi again", "operation": { - "$id": "658", + "$id": "682", "name": "helloAgain", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -9412,12 +9792,12 @@ "accessibility": "public", "parameters": [ { - "$id": "659", + "$id": "683", "kind": "header", "name": "p1", "serializedName": "p1", "type": { - "$id": "660", + "$id": "684", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -9432,12 +9812,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloAgain.p1", "methodParameterSegments": [ { - "$id": "661", + "$id": "685", "kind": "method", "name": "p1", "serializedName": "p1", "type": { - "$id": "662", + "$id": "686", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -9457,12 +9837,12 @@ "isExactName": false }, { - "$id": "663", + "$id": "687", "kind": "header", "name": "contentType", "serializedName": "Content-Type", "type": { - "$ref": "88" + "$ref": "96" }, "isApiVersion": false, "optional": false, @@ -9473,12 +9853,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloAgain.contentType", "methodParameterSegments": [ { - "$id": "664", + "$id": "688", "kind": "method", "name": "contentType", "serializedName": "Content-Type", "type": { - "$ref": "88" + "$ref": "96" }, "location": "Header", "isApiVersion": false, @@ -9494,12 +9874,12 @@ "isExactName": false }, { - "$id": "665", + "$id": "689", "kind": "path", "name": "p2", "serializedName": "p2", "type": { - "$id": "666", + "$id": "690", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -9517,12 +9897,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloAgain.p2", "methodParameterSegments": [ { - "$id": "667", + "$id": "691", "kind": "method", "name": "p2", "serializedName": "p2", "type": { - "$id": "668", + "$id": "692", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -9542,12 +9922,12 @@ "isExactName": false }, { - "$id": "669", + "$id": "693", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "92" + "$ref": "100" }, "isApiVersion": false, "optional": false, @@ -9558,12 +9938,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloAgain.accept", "methodParameterSegments": [ { - "$id": "670", + "$id": "694", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "92" + "$ref": "100" }, "location": "Header", "isApiVersion": false, @@ -9579,12 +9959,12 @@ "isExactName": false }, { - "$id": "671", + "$id": "695", "kind": "body", "name": "action", "serializedName": "action", "type": { - "$ref": "291" + "$ref": "307" }, "isApiVersion": false, "contentTypes": [ @@ -9598,12 +9978,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloAgain.action", "methodParameterSegments": [ { - "$id": "672", + "$id": "696", "kind": "method", "name": "action", "serializedName": "action", "type": { - "$ref": "291" + "$ref": "307" }, "location": "Body", "isApiVersion": false, @@ -9626,7 +10006,7 @@ 200 ], "bodyType": { - "$ref": "291" + "$ref": "307" }, "headers": [], "isErrorResponse": false, @@ -9655,24 +10035,24 @@ }, "parameters": [ { - "$ref": "661" + "$ref": "685" }, { - "$ref": "672" + "$ref": "696" }, { - "$ref": "664" + "$ref": "688" }, { - "$ref": "667" + "$ref": "691" }, { - "$ref": "670" + "$ref": "694" } ], "response": { "type": { - "$ref": "291" + "$ref": "307" } }, "isOverride": false, @@ -9681,7 +10061,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloAgain" }, { - "$id": "673", + "$id": "697", "kind": "basic", "name": "noContentType", "isExactName": false, @@ -9692,7 +10072,7 @@ ], "doc": "Return hi again", "operation": { - "$id": "674", + "$id": "698", "name": "noContentType", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -9700,12 +10080,12 @@ "accessibility": "public", "parameters": [ { - "$id": "675", + "$id": "699", "kind": "header", "name": "p1", "serializedName": "p1", "type": { - "$id": "676", + "$id": "700", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -9720,12 +10100,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.noContentType.p1", "methodParameterSegments": [ { - "$id": "677", + "$id": "701", "kind": "method", "name": "info", "serializedName": "info", "type": { - "$ref": "339" + "$ref": "355" }, "location": "", "isApiVersion": false, @@ -9738,13 +10118,13 @@ "isExactName": false }, { - "$id": "678", + "$id": "702", "kind": "method", "name": "p1", "serializedName": "p1", "doc": "header parameter", "type": { - "$ref": "341" + "$ref": "357" }, "location": "", "isApiVersion": false, @@ -9760,12 +10140,12 @@ "isExactName": false }, { - "$id": "679", + "$id": "703", "kind": "path", "name": "p2", "serializedName": "p2", "type": { - "$id": "680", + "$id": "704", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -9783,16 +10163,16 @@ "crossLanguageDefinitionId": "SampleTypeSpec.noContentType.p2", "methodParameterSegments": [ { - "$ref": "677" + "$ref": "701" }, { - "$id": "681", + "$id": "705", "kind": "method", "name": "p2", "serializedName": "p2", "doc": "path parameter", "type": { - "$ref": "344" + "$ref": "360" }, "location": "", "isApiVersion": false, @@ -9808,13 +10188,13 @@ "isExactName": false }, { - "$id": "682", + "$id": "706", "kind": "header", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "94" + "$ref": "102" }, "isApiVersion": false, "optional": false, @@ -9825,13 +10205,13 @@ "crossLanguageDefinitionId": "SampleTypeSpec.noContentType.contentType", "methodParameterSegments": [ { - "$id": "683", + "$id": "707", "kind": "method", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "94" + "$ref": "102" }, "location": "Header", "isApiVersion": false, @@ -9847,12 +10227,12 @@ "isExactName": false }, { - "$id": "684", + "$id": "708", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "96" + "$ref": "104" }, "isApiVersion": false, "optional": false, @@ -9863,12 +10243,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.noContentType.accept", "methodParameterSegments": [ { - "$id": "685", + "$id": "709", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "96" + "$ref": "104" }, "location": "Header", "isApiVersion": false, @@ -9884,12 +10264,12 @@ "isExactName": false }, { - "$id": "686", + "$id": "710", "kind": "body", "name": "action", "serializedName": "action", "type": { - "$ref": "291" + "$ref": "307" }, "isApiVersion": false, "contentTypes": [ @@ -9903,16 +10283,16 @@ "crossLanguageDefinitionId": "SampleTypeSpec.noContentType.action", "methodParameterSegments": [ { - "$ref": "677" + "$ref": "701" }, { - "$id": "687", + "$id": "711", "kind": "method", "name": "action", "serializedName": "action", "doc": "body parameter", "type": { - "$ref": "291" + "$ref": "307" }, "location": "", "isApiVersion": false, @@ -9939,7 +10319,7 @@ 200 ], "bodyType": { - "$ref": "291" + "$ref": "307" }, "headers": [], "isErrorResponse": false, @@ -9968,18 +10348,18 @@ }, "parameters": [ { - "$ref": "677" + "$ref": "701" }, { - "$ref": "683" + "$ref": "707" }, { - "$ref": "685" + "$ref": "709" } ], "response": { "type": { - "$ref": "291" + "$ref": "307" } }, "isOverride": true, @@ -9988,7 +10368,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.noContentType" }, { - "$id": "688", + "$id": "712", "kind": "basic", "name": "helloDemo2", "isExactName": false, @@ -9999,7 +10379,7 @@ ], "doc": "Return hi in demo2", "operation": { - "$id": "689", + "$id": "713", "name": "helloDemo2", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -10007,12 +10387,12 @@ "accessibility": "public", "parameters": [ { - "$id": "690", + "$id": "714", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "98" + "$ref": "106" }, "isApiVersion": false, "optional": false, @@ -10023,12 +10403,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloDemo2.accept", "methodParameterSegments": [ { - "$id": "691", + "$id": "715", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "98" + "$ref": "106" }, "location": "Header", "isApiVersion": false, @@ -10050,7 +10430,7 @@ 200 ], "bodyType": { - "$ref": "256" + "$ref": "272" }, "headers": [], "isErrorResponse": false, @@ -10080,12 +10460,12 @@ }, "parameters": [ { - "$ref": "691" + "$ref": "715" } ], "response": { "type": { - "$ref": "256" + "$ref": "272" } }, "isOverride": false, @@ -10094,7 +10474,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloDemo2" }, { - "$id": "692", + "$id": "716", "kind": "basic", "name": "createLiteral", "isExactName": false, @@ -10105,7 +10485,7 @@ ], "doc": "Create with literal value", "operation": { - "$id": "693", + "$id": "717", "name": "createLiteral", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -10113,13 +10493,13 @@ "accessibility": "public", "parameters": [ { - "$id": "694", + "$id": "718", "kind": "header", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "100" + "$ref": "108" }, "isApiVersion": false, "optional": false, @@ -10130,13 +10510,13 @@ "crossLanguageDefinitionId": "SampleTypeSpec.createLiteral.contentType", "methodParameterSegments": [ { - "$id": "695", + "$id": "719", "kind": "method", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "100" + "$ref": "108" }, "location": "Header", "isApiVersion": false, @@ -10152,12 +10532,12 @@ "isExactName": false }, { - "$id": "696", + "$id": "720", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "102" + "$ref": "110" }, "isApiVersion": false, "optional": false, @@ -10168,12 +10548,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.createLiteral.accept", "methodParameterSegments": [ { - "$id": "697", + "$id": "721", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "102" + "$ref": "110" }, "location": "Header", "isApiVersion": false, @@ -10189,12 +10569,12 @@ "isExactName": false }, { - "$id": "698", + "$id": "722", "kind": "body", "name": "body", "serializedName": "body", "type": { - "$ref": "256" + "$ref": "272" }, "isApiVersion": false, "contentTypes": [ @@ -10208,12 +10588,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.createLiteral.body", "methodParameterSegments": [ { - "$id": "699", + "$id": "723", "kind": "method", "name": "body", "serializedName": "body", "type": { - "$ref": "256" + "$ref": "272" }, "location": "Body", "isApiVersion": false, @@ -10240,7 +10620,7 @@ 200 ], "bodyType": { - "$ref": "256" + "$ref": "272" }, "headers": [], "isErrorResponse": false, @@ -10269,18 +10649,18 @@ }, "parameters": [ { - "$ref": "699" + "$ref": "723" }, { - "$ref": "695" + "$ref": "719" }, { - "$ref": "697" + "$ref": "721" } ], "response": { "type": { - "$ref": "256" + "$ref": "272" } }, "isOverride": false, @@ -10289,7 +10669,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.createLiteral" }, { - "$id": "700", + "$id": "724", "kind": "basic", "name": "helloLiteral", "isExactName": false, @@ -10300,7 +10680,7 @@ ], "doc": "Send literal parameters", "operation": { - "$id": "701", + "$id": "725", "name": "helloLiteral", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -10308,12 +10688,12 @@ "accessibility": "public", "parameters": [ { - "$id": "702", + "$id": "726", "kind": "header", "name": "p1", "serializedName": "p1", "type": { - "$ref": "104" + "$ref": "112" }, "isApiVersion": false, "optional": false, @@ -10324,12 +10704,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloLiteral.p1", "methodParameterSegments": [ { - "$id": "703", + "$id": "727", "kind": "method", "name": "p1", "serializedName": "p1", "type": { - "$ref": "106" + "$ref": "114" }, "location": "Header", "isApiVersion": false, @@ -10345,12 +10725,12 @@ "isExactName": false }, { - "$id": "704", + "$id": "728", "kind": "path", "name": "p2", "serializedName": "p2", "type": { - "$ref": "108" + "$ref": "116" }, "isApiVersion": false, "explode": false, @@ -10364,12 +10744,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloLiteral.p2", "methodParameterSegments": [ { - "$id": "705", + "$id": "729", "kind": "method", "name": "p2", "serializedName": "p2", "type": { - "$ref": "110" + "$ref": "118" }, "location": "Path", "isApiVersion": false, @@ -10385,12 +10765,12 @@ "isExactName": false }, { - "$id": "706", + "$id": "730", "kind": "query", "name": "p3", "serializedName": "p3", "type": { - "$ref": "112" + "$ref": "120" }, "isApiVersion": false, "explode": false, @@ -10401,12 +10781,12 @@ "readOnly": false, "methodParameterSegments": [ { - "$id": "707", + "$id": "731", "kind": "method", "name": "p3", "serializedName": "p3", "type": { - "$ref": "114" + "$ref": "122" }, "location": "Query", "isApiVersion": false, @@ -10422,12 +10802,12 @@ "isExactName": false }, { - "$id": "708", + "$id": "732", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "116" + "$ref": "124" }, "isApiVersion": false, "optional": false, @@ -10438,12 +10818,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloLiteral.accept", "methodParameterSegments": [ { - "$id": "709", + "$id": "733", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "116" + "$ref": "124" }, "location": "Header", "isApiVersion": false, @@ -10465,7 +10845,7 @@ 200 ], "bodyType": { - "$ref": "256" + "$ref": "272" }, "headers": [], "isErrorResponse": false, @@ -10491,21 +10871,21 @@ }, "parameters": [ { - "$ref": "703" + "$ref": "727" }, { - "$ref": "705" + "$ref": "729" }, { - "$ref": "707" + "$ref": "731" }, { - "$ref": "709" + "$ref": "733" } ], "response": { "type": { - "$ref": "256" + "$ref": "272" } }, "isOverride": false, @@ -10514,7 +10894,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloLiteral" }, { - "$id": "710", + "$id": "734", "kind": "basic", "name": "topAction", "isExactName": false, @@ -10525,7 +10905,7 @@ ], "doc": "top level method", "operation": { - "$id": "711", + "$id": "735", "name": "topAction", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -10533,17 +10913,17 @@ "accessibility": "public", "parameters": [ { - "$id": "712", + "$id": "736", "kind": "path", "name": "action", "serializedName": "action", "type": { - "$id": "713", + "$id": "737", "kind": "utcDateTime", "name": "utcDateTime", "encode": "rfc3339", "wireType": { - "$id": "714", + "$id": "738", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -10564,17 +10944,17 @@ "crossLanguageDefinitionId": "SampleTypeSpec.topAction.action", "methodParameterSegments": [ { - "$id": "715", + "$id": "739", "kind": "method", "name": "action", "serializedName": "action", "type": { - "$id": "716", + "$id": "740", "kind": "utcDateTime", "name": "utcDateTime", "encode": "rfc3339", "wireType": { - "$id": "717", + "$id": "741", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -10597,12 +10977,12 @@ "isExactName": false }, { - "$id": "718", + "$id": "742", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "118" + "$ref": "126" }, "isApiVersion": false, "optional": false, @@ -10613,12 +10993,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.topAction.accept", "methodParameterSegments": [ { - "$id": "719", + "$id": "743", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "118" + "$ref": "126" }, "location": "Header", "isApiVersion": false, @@ -10640,7 +11020,7 @@ 200 ], "bodyType": { - "$ref": "256" + "$ref": "272" }, "headers": [], "isErrorResponse": false, @@ -10666,15 +11046,15 @@ }, "parameters": [ { - "$ref": "715" + "$ref": "739" }, { - "$ref": "719" + "$ref": "743" } ], "response": { "type": { - "$ref": "256" + "$ref": "272" } }, "isOverride": false, @@ -10683,7 +11063,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.topAction" }, { - "$id": "720", + "$id": "744", "kind": "basic", "name": "topAction2", "isExactName": false, @@ -10694,7 +11074,7 @@ ], "doc": "top level method2", "operation": { - "$id": "721", + "$id": "745", "name": "topAction2", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -10702,12 +11082,12 @@ "accessibility": "public", "parameters": [ { - "$id": "722", + "$id": "746", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "120" + "$ref": "128" }, "isApiVersion": false, "optional": false, @@ -10718,12 +11098,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.topAction2.accept", "methodParameterSegments": [ { - "$id": "723", + "$id": "747", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "120" + "$ref": "128" }, "location": "Header", "isApiVersion": false, @@ -10745,7 +11125,7 @@ 200 ], "bodyType": { - "$ref": "256" + "$ref": "272" }, "headers": [], "isErrorResponse": false, @@ -10771,12 +11151,12 @@ }, "parameters": [ { - "$ref": "723" + "$ref": "747" } ], "response": { "type": { - "$ref": "256" + "$ref": "272" } }, "isOverride": false, @@ -10785,7 +11165,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.topAction2" }, { - "$id": "724", + "$id": "748", "kind": "basic", "name": "patchAction", "isExactName": false, @@ -10796,7 +11176,7 @@ ], "doc": "top level patch", "operation": { - "$id": "725", + "$id": "749", "name": "patchAction", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -10804,13 +11184,13 @@ "accessibility": "public", "parameters": [ { - "$id": "726", + "$id": "750", "kind": "header", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "122" + "$ref": "130" }, "isApiVersion": false, "optional": false, @@ -10821,13 +11201,13 @@ "crossLanguageDefinitionId": "SampleTypeSpec.patchAction.contentType", "methodParameterSegments": [ { - "$id": "727", + "$id": "751", "kind": "method", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "122" + "$ref": "130" }, "location": "Header", "isApiVersion": false, @@ -10843,12 +11223,12 @@ "isExactName": false }, { - "$id": "728", + "$id": "752", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "124" + "$ref": "132" }, "isApiVersion": false, "optional": false, @@ -10859,12 +11239,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.patchAction.accept", "methodParameterSegments": [ { - "$id": "729", + "$id": "753", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "124" + "$ref": "132" }, "location": "Header", "isApiVersion": false, @@ -10880,12 +11260,12 @@ "isExactName": false }, { - "$id": "730", + "$id": "754", "kind": "body", "name": "body", "serializedName": "body", "type": { - "$ref": "256" + "$ref": "272" }, "isApiVersion": false, "contentTypes": [ @@ -10899,12 +11279,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.patchAction.body", "methodParameterSegments": [ { - "$id": "731", + "$id": "755", "kind": "method", "name": "body", "serializedName": "body", "type": { - "$ref": "256" + "$ref": "272" }, "location": "Body", "isApiVersion": false, @@ -10931,7 +11311,7 @@ 200 ], "bodyType": { - "$ref": "256" + "$ref": "272" }, "headers": [], "isErrorResponse": false, @@ -10960,18 +11340,18 @@ }, "parameters": [ { - "$ref": "731" + "$ref": "755" }, { - "$ref": "727" + "$ref": "751" }, { - "$ref": "729" + "$ref": "753" } ], "response": { "type": { - "$ref": "256" + "$ref": "272" } }, "isOverride": false, @@ -10980,7 +11360,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.patchAction" }, { - "$id": "732", + "$id": "756", "kind": "basic", "name": "anonymousBody", "isExactName": false, @@ -10991,7 +11371,7 @@ ], "doc": "body parameter without body decorator", "operation": { - "$id": "733", + "$id": "757", "name": "anonymousBody", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -10999,12 +11379,12 @@ "accessibility": "public", "parameters": [ { - "$id": "734", + "$id": "758", "kind": "query", "name": "requiredQueryParam", "serializedName": "requiredQueryParam", "type": { - "$ref": "126" + "$ref": "134" }, "isApiVersion": false, "explode": false, @@ -11015,12 +11395,12 @@ "readOnly": false, "methodParameterSegments": [ { - "$id": "735", + "$id": "759", "kind": "method", "name": "requiredQueryParam", "serializedName": "requiredQueryParam", "type": { - "$ref": "128" + "$ref": "136" }, "location": "Query", "isApiVersion": false, @@ -11036,12 +11416,12 @@ "isExactName": false }, { - "$id": "736", + "$id": "760", "kind": "header", "name": "requiredHeader", "serializedName": "required-header", "type": { - "$ref": "130" + "$ref": "138" }, "isApiVersion": false, "optional": false, @@ -11052,12 +11432,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.anonymousBody.requiredHeader", "methodParameterSegments": [ { - "$id": "737", + "$id": "761", "kind": "method", "name": "requiredHeader", "serializedName": "required-header", "type": { - "$ref": "132" + "$ref": "140" }, "location": "Header", "isApiVersion": false, @@ -11073,13 +11453,13 @@ "isExactName": false }, { - "$id": "738", + "$id": "762", "kind": "header", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "134" + "$ref": "142" }, "isApiVersion": false, "optional": false, @@ -11090,13 +11470,13 @@ "crossLanguageDefinitionId": "SampleTypeSpec.anonymousBody.contentType", "methodParameterSegments": [ { - "$id": "739", + "$id": "763", "kind": "method", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "134" + "$ref": "142" }, "location": "Header", "isApiVersion": false, @@ -11112,12 +11492,12 @@ "isExactName": false }, { - "$id": "740", + "$id": "764", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "136" + "$ref": "144" }, "isApiVersion": false, "optional": false, @@ -11128,12 +11508,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.anonymousBody.accept", "methodParameterSegments": [ { - "$id": "741", + "$id": "765", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "136" + "$ref": "144" }, "location": "Header", "isApiVersion": false, @@ -11149,12 +11529,12 @@ "isExactName": false }, { - "$id": "742", + "$id": "766", "kind": "body", "name": "thing", "serializedName": "thing", "type": { - "$ref": "256" + "$ref": "272" }, "isApiVersion": false, "contentTypes": [ @@ -11168,13 +11548,13 @@ "crossLanguageDefinitionId": "SampleTypeSpec.anonymousBody.body", "methodParameterSegments": [ { - "$id": "743", + "$id": "767", "kind": "method", "name": "name", "serializedName": "name", "doc": "name of the Thing", "type": { - "$id": "744", + "$id": "768", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -11205,7 +11585,7 @@ 200 ], "bodyType": { - "$ref": "256" + "$ref": "272" }, "headers": [], "isErrorResponse": false, @@ -11234,16 +11614,16 @@ }, "parameters": [ { - "$ref": "743" + "$ref": "767" }, { - "$id": "745", + "$id": "769", "kind": "method", "name": "requiredUnion", "serializedName": "requiredUnion", "doc": "required Union", "type": { - "$ref": "260" + "$ref": "276" }, "location": "Body", "isApiVersion": false, @@ -11256,13 +11636,13 @@ "isExactName": false }, { - "$id": "746", + "$id": "770", "kind": "method", "name": "requiredLiteralString", "serializedName": "requiredLiteralString", "doc": "required literal string", "type": { - "$ref": "70" + "$ref": "78" }, "location": "Body", "isApiVersion": false, @@ -11275,13 +11655,13 @@ "isExactName": false }, { - "$id": "747", + "$id": "771", "kind": "method", "name": "requiredNullableString", "serializedName": "requiredNullableString", "doc": "required nullable string", "type": { - "$ref": "267" + "$ref": "283" }, "location": "Body", "isApiVersion": false, @@ -11294,13 +11674,13 @@ "isExactName": false }, { - "$id": "748", + "$id": "772", "kind": "method", "name": "optionalNullableString", "serializedName": "optionalNullableString", "doc": "required optional string", "type": { - "$ref": "270" + "$ref": "286" }, "location": "Body", "isApiVersion": false, @@ -11313,13 +11693,13 @@ "isExactName": false }, { - "$id": "749", + "$id": "773", "kind": "method", "name": "requiredLiteralInt", "serializedName": "requiredLiteralInt", "doc": "required literal int", "type": { - "$ref": "72" + "$ref": "80" }, "location": "Body", "isApiVersion": false, @@ -11332,13 +11712,13 @@ "isExactName": false }, { - "$id": "750", + "$id": "774", "kind": "method", "name": "requiredLiteralFloat", "serializedName": "requiredLiteralFloat", "doc": "required literal float", "type": { - "$ref": "74" + "$ref": "82" }, "location": "Body", "isApiVersion": false, @@ -11351,13 +11731,13 @@ "isExactName": false }, { - "$id": "751", + "$id": "775", "kind": "method", "name": "requiredLiteralBool", "serializedName": "requiredLiteralBool", "doc": "required literal bool", "type": { - "$ref": "76" + "$ref": "84" }, "location": "Body", "isApiVersion": false, @@ -11370,19 +11750,19 @@ "isExactName": false }, { - "$id": "752", + "$id": "776", "kind": "method", "name": "optionalLiteralString", "serializedName": "optionalLiteralString", "doc": "optional literal string", "type": { - "$id": "753", + "$id": "777", "kind": "enum", "name": "ThingOptionalLiteralString", "apiVersions": [], "crossLanguageDefinitionId": "", "valueType": { - "$id": "754", + "$id": "778", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -11390,12 +11770,12 @@ }, "values": [ { - "$id": "755", + "$id": "779", "kind": "enumvalue", "name": "reject", "value": "reject", "valueType": { - "$id": "756", + "$id": "780", "kind": "string", "decorators": [], "doc": "A sequence of textual characters.", @@ -11403,7 +11783,7 @@ "crossLanguageDefinitionId": "TypeSpec.string" }, "enumType": { - "$ref": "753" + "$ref": "777" }, "decorators": [], "isExactName": false @@ -11427,13 +11807,13 @@ "isExactName": false }, { - "$id": "757", + "$id": "781", "kind": "method", "name": "requiredNullableLiteralString", "serializedName": "requiredNullableLiteralString", "doc": "required nullable literal string", "type": { - "$ref": "277" + "$ref": "293" }, "location": "Body", "isApiVersion": false, @@ -11446,19 +11826,19 @@ "isExactName": false }, { - "$id": "758", + "$id": "782", "kind": "method", "name": "optionalLiteralInt", "serializedName": "optionalLiteralInt", "doc": "optional literal int", "type": { - "$id": "759", + "$id": "783", "kind": "enum", "name": "ThingOptionalLiteralInt", "apiVersions": [], "crossLanguageDefinitionId": "", "valueType": { - "$id": "760", + "$id": "784", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -11466,12 +11846,12 @@ }, "values": [ { - "$id": "761", + "$id": "785", "kind": "enumvalue", "name": "456", "value": 456, "valueType": { - "$id": "762", + "$id": "786", "kind": "int32", "decorators": [], "doc": "A 32-bit integer. (`-2,147,483,648` to `2,147,483,647`)", @@ -11479,7 +11859,7 @@ "crossLanguageDefinitionId": "TypeSpec.int32" }, "enumType": { - "$ref": "759" + "$ref": "783" }, "decorators": [], "isExactName": false @@ -11503,19 +11883,19 @@ "isExactName": false }, { - "$id": "763", + "$id": "787", "kind": "method", "name": "optionalLiteralFloat", "serializedName": "optionalLiteralFloat", "doc": "optional literal float", "type": { - "$id": "764", + "$id": "788", "kind": "enum", "name": "ThingOptionalLiteralFloat", "apiVersions": [], "crossLanguageDefinitionId": "", "valueType": { - "$id": "765", + "$id": "789", "kind": "float32", "name": "float32", "crossLanguageDefinitionId": "TypeSpec.float32", @@ -11523,12 +11903,12 @@ }, "values": [ { - "$id": "766", + "$id": "790", "kind": "enumvalue", "name": "4.56", "value": 4.56, "valueType": { - "$id": "767", + "$id": "791", "kind": "float32", "decorators": [], "doc": "A 32 bit floating point number. (`±1.5 x 10^−45` to `±3.4 x 10^38`)", @@ -11536,7 +11916,7 @@ "crossLanguageDefinitionId": "TypeSpec.float32" }, "enumType": { - "$ref": "764" + "$ref": "788" }, "decorators": [], "isExactName": false @@ -11560,13 +11940,13 @@ "isExactName": false }, { - "$id": "768", + "$id": "792", "kind": "method", "name": "optionalLiteralBool", "serializedName": "optionalLiteralBool", "doc": "optional literal bool", "type": { - "$ref": "78" + "$ref": "86" }, "location": "Body", "isApiVersion": false, @@ -11579,13 +11959,13 @@ "isExactName": false }, { - "$id": "769", + "$id": "793", "kind": "method", "name": "requiredBadDescription", "serializedName": "requiredBadDescription", "doc": "description with xml <|endoftext|>", "type": { - "$id": "770", + "$id": "794", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -11602,13 +11982,13 @@ "isExactName": false }, { - "$id": "771", + "$id": "795", "kind": "method", "name": "optionalNullableList", "serializedName": "optionalNullableList", "doc": "optional nullable collection", "type": { - "$ref": "284" + "$ref": "300" }, "location": "Body", "isApiVersion": false, @@ -11621,13 +12001,13 @@ "isExactName": false }, { - "$id": "772", + "$id": "796", "kind": "method", "name": "requiredNullableList", "serializedName": "requiredNullableList", "doc": "required nullable collection", "type": { - "$ref": "288" + "$ref": "304" }, "location": "Body", "isApiVersion": false, @@ -11640,13 +12020,13 @@ "isExactName": false }, { - "$id": "773", + "$id": "797", "kind": "method", "name": "propertyWithSpecialDocs", "serializedName": "propertyWithSpecialDocs", "doc": "This tests:\n- Simple bullet point. This bullet point is going to be very long to test how text wrapping is handled in bullet points within documentation comments. It should properly indent the wrapped lines.\n- Another bullet point with **bold text**. This bullet point is also intentionally long to see how the formatting is preserved when the text wraps onto multiple lines in the generated documentation.\n- Third bullet point with *italic text*. Similar to the previous points, this one is extended to ensure that the wrapping and formatting are correctly applied in the output.\n- Complex bullet point with **bold** and *italic* combined. This bullet point combines both bold and italic formatting and is long enough to test the wrapping behavior in such cases.\n- **Bold bullet point**: A bullet point that is entirely bolded. This point is also made lengthy to observe how the bold formatting is maintained across wrapped lines.\n- *Italic bullet point*: A bullet point that is entirely italicized. This final point is extended to verify that italic formatting is correctly applied even when the text spans multiple lines.", "type": { - "$id": "774", + "$id": "798", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -11663,21 +12043,21 @@ "isExactName": false }, { - "$ref": "735" + "$ref": "759" }, { - "$ref": "737" + "$ref": "761" }, { - "$ref": "739" + "$ref": "763" }, { - "$ref": "741" + "$ref": "765" } ], "response": { "type": { - "$ref": "256" + "$ref": "272" } }, "isOverride": false, @@ -11686,7 +12066,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.anonymousBody" }, { - "$id": "775", + "$id": "799", "kind": "basic", "name": "friendlyModel", "isExactName": false, @@ -11697,7 +12077,7 @@ ], "doc": "Model can have its friendly name", "operation": { - "$id": "776", + "$id": "800", "name": "friendlyModel", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -11705,13 +12085,13 @@ "accessibility": "public", "parameters": [ { - "$id": "777", + "$id": "801", "kind": "header", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "148" + "$ref": "156" }, "isApiVersion": false, "optional": false, @@ -11722,13 +12102,13 @@ "crossLanguageDefinitionId": "SampleTypeSpec.friendlyModel.contentType", "methodParameterSegments": [ { - "$id": "778", + "$id": "802", "kind": "method", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "148" + "$ref": "156" }, "location": "Header", "isApiVersion": false, @@ -11744,12 +12124,12 @@ "isExactName": false }, { - "$id": "779", + "$id": "803", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "150" + "$ref": "158" }, "isApiVersion": false, "optional": false, @@ -11760,12 +12140,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.friendlyModel.accept", "methodParameterSegments": [ { - "$id": "780", + "$id": "804", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "150" + "$ref": "158" }, "location": "Header", "isApiVersion": false, @@ -11781,12 +12161,12 @@ "isExactName": false }, { - "$id": "781", + "$id": "805", "kind": "body", "name": "friend", "serializedName": "friend", "type": { - "$ref": "345" + "$ref": "361" }, "isApiVersion": false, "contentTypes": [ @@ -11800,13 +12180,13 @@ "crossLanguageDefinitionId": "SampleTypeSpec.friendlyModel.body", "methodParameterSegments": [ { - "$id": "782", + "$id": "806", "kind": "method", "name": "name", "serializedName": "name", "doc": "name of the NotFriend", "type": { - "$id": "783", + "$id": "807", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -11837,7 +12217,7 @@ 200 ], "bodyType": { - "$ref": "345" + "$ref": "361" }, "headers": [], "isErrorResponse": false, @@ -11866,18 +12246,18 @@ }, "parameters": [ { - "$ref": "782" + "$ref": "806" }, { - "$ref": "778" + "$ref": "802" }, { - "$ref": "780" + "$ref": "804" } ], "response": { "type": { - "$ref": "345" + "$ref": "361" } }, "isOverride": false, @@ -11886,7 +12266,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.friendlyModel" }, { - "$id": "784", + "$id": "808", "kind": "basic", "name": "addTimeHeader", "isExactName": false, @@ -11896,24 +12276,24 @@ "2024-08-16-preview" ], "operation": { - "$id": "785", + "$id": "809", "name": "addTimeHeader", "isExactName": false, "resourceName": "SampleTypeSpec", "accessibility": "public", "parameters": [ { - "$id": "786", + "$id": "810", "kind": "header", "name": "repeatabilityFirstSent", "serializedName": "Repeatability-First-Sent", "type": { - "$id": "787", + "$id": "811", "kind": "utcDateTime", "name": "utcDateTime", "encode": "rfc7231", "wireType": { - "$id": "788", + "$id": "812", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -11931,17 +12311,17 @@ "crossLanguageDefinitionId": "SampleTypeSpec.addTimeHeader.repeatabilityFirstSent", "methodParameterSegments": [ { - "$id": "789", + "$id": "813", "kind": "method", "name": "repeatabilityFirstSent", "serializedName": "Repeatability-First-Sent", "type": { - "$id": "790", + "$id": "814", "kind": "utcDateTime", "name": "utcDateTime", "encode": "rfc7231", "wireType": { - "$id": "791", + "$id": "815", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -11986,7 +12366,7 @@ }, "parameters": [ { - "$ref": "789" + "$ref": "813" } ], "response": {}, @@ -11996,7 +12376,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.addTimeHeader" }, { - "$id": "792", + "$id": "816", "kind": "basic", "name": "projectedNameModel", "isExactName": false, @@ -12007,7 +12387,7 @@ ], "doc": "Model can have its projected name", "operation": { - "$id": "793", + "$id": "817", "name": "projectedNameModel", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -12015,13 +12395,13 @@ "accessibility": "public", "parameters": [ { - "$id": "794", + "$id": "818", "kind": "header", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "152" + "$ref": "160" }, "isApiVersion": false, "optional": false, @@ -12032,13 +12412,13 @@ "crossLanguageDefinitionId": "SampleTypeSpec.projectedNameModel.contentType", "methodParameterSegments": [ { - "$id": "795", + "$id": "819", "kind": "method", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "152" + "$ref": "160" }, "location": "Header", "isApiVersion": false, @@ -12054,12 +12434,12 @@ "isExactName": false }, { - "$id": "796", + "$id": "820", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "154" + "$ref": "162" }, "isApiVersion": false, "optional": false, @@ -12070,12 +12450,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.projectedNameModel.accept", "methodParameterSegments": [ { - "$id": "797", + "$id": "821", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "154" + "$ref": "162" }, "location": "Header", "isApiVersion": false, @@ -12091,12 +12471,12 @@ "isExactName": false }, { - "$id": "798", + "$id": "822", "kind": "body", "name": "renamedModel", "serializedName": "renamedModel", "type": { - "$ref": "348" + "$ref": "364" }, "isApiVersion": false, "contentTypes": [ @@ -12110,13 +12490,13 @@ "crossLanguageDefinitionId": "SampleTypeSpec.projectedNameModel.body", "methodParameterSegments": [ { - "$id": "799", + "$id": "823", "kind": "method", "name": "otherName", "serializedName": "otherName", "doc": "name of the ModelWithClientName", "type": { - "$id": "800", + "$id": "824", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -12147,7 +12527,7 @@ 200 ], "bodyType": { - "$ref": "348" + "$ref": "364" }, "headers": [], "isErrorResponse": false, @@ -12176,18 +12556,18 @@ }, "parameters": [ { - "$ref": "799" + "$ref": "823" }, { - "$ref": "795" + "$ref": "819" }, { - "$ref": "797" + "$ref": "821" } ], "response": { "type": { - "$ref": "348" + "$ref": "364" } }, "isOverride": false, @@ -12196,7 +12576,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.projectedNameModel" }, { - "$id": "801", + "$id": "825", "kind": "basic", "name": "returnsAnonymousModel", "isExactName": false, @@ -12207,7 +12587,7 @@ ], "doc": "return anonymous model", "operation": { - "$id": "802", + "$id": "826", "name": "returnsAnonymousModel", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -12215,12 +12595,12 @@ "accessibility": "public", "parameters": [ { - "$id": "803", + "$id": "827", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "156" + "$ref": "164" }, "isApiVersion": false, "optional": false, @@ -12231,12 +12611,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.returnsAnonymousModel.accept", "methodParameterSegments": [ { - "$id": "804", + "$id": "828", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "156" + "$ref": "164" }, "location": "Header", "isApiVersion": false, @@ -12258,7 +12638,7 @@ 200 ], "bodyType": { - "$ref": "351" + "$ref": "367" }, "headers": [], "isErrorResponse": false, @@ -12284,12 +12664,12 @@ }, "parameters": [ { - "$ref": "804" + "$ref": "828" } ], "response": { "type": { - "$ref": "351" + "$ref": "367" } }, "isOverride": false, @@ -12298,7 +12678,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.returnsAnonymousModel" }, { - "$id": "805", + "$id": "829", "kind": "basic", "name": "getUnknownValue", "isExactName": false, @@ -12309,7 +12689,7 @@ ], "doc": "get extensible enum", "operation": { - "$id": "806", + "$id": "830", "name": "getUnknownValue", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -12317,12 +12697,12 @@ "accessibility": "public", "parameters": [ { - "$id": "807", + "$id": "831", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "158" + "$ref": "166" }, "isApiVersion": false, "optional": false, @@ -12333,12 +12713,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.getUnknownValue.accept", "methodParameterSegments": [ { - "$id": "808", + "$id": "832", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "158" + "$ref": "166" }, "location": "Header", "isApiVersion": false, @@ -12360,7 +12740,7 @@ 200 ], "bodyType": { - "$ref": "57" + "$ref": "65" }, "headers": [], "isErrorResponse": false, @@ -12382,12 +12762,12 @@ }, "parameters": [ { - "$ref": "808" + "$ref": "832" } ], "response": { "type": { - "$ref": "57" + "$ref": "65" } }, "isOverride": false, @@ -12396,7 +12776,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.getUnknownValue" }, { - "$id": "809", + "$id": "833", "kind": "basic", "name": "internalProtocol", "isExactName": false, @@ -12407,7 +12787,7 @@ ], "doc": "When set protocol false and convenient true, then the protocol method should be internal", "operation": { - "$id": "810", + "$id": "834", "name": "internalProtocol", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -12415,13 +12795,13 @@ "accessibility": "public", "parameters": [ { - "$id": "811", + "$id": "835", "kind": "header", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "160" + "$ref": "168" }, "isApiVersion": false, "optional": false, @@ -12432,13 +12812,13 @@ "crossLanguageDefinitionId": "SampleTypeSpec.internalProtocol.contentType", "methodParameterSegments": [ { - "$id": "812", + "$id": "836", "kind": "method", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "160" + "$ref": "168" }, "location": "Header", "isApiVersion": false, @@ -12454,12 +12834,12 @@ "isExactName": false }, { - "$id": "813", + "$id": "837", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "162" + "$ref": "170" }, "isApiVersion": false, "optional": false, @@ -12470,12 +12850,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.internalProtocol.accept", "methodParameterSegments": [ { - "$id": "814", + "$id": "838", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "162" + "$ref": "170" }, "location": "Header", "isApiVersion": false, @@ -12491,12 +12871,12 @@ "isExactName": false }, { - "$id": "815", + "$id": "839", "kind": "body", "name": "body", "serializedName": "body", "type": { - "$ref": "256" + "$ref": "272" }, "isApiVersion": false, "contentTypes": [ @@ -12510,12 +12890,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.internalProtocol.body", "methodParameterSegments": [ { - "$id": "816", + "$id": "840", "kind": "method", "name": "body", "serializedName": "body", "type": { - "$ref": "256" + "$ref": "272" }, "location": "Body", "isApiVersion": false, @@ -12542,7 +12922,7 @@ 200 ], "bodyType": { - "$ref": "256" + "$ref": "272" }, "headers": [], "isErrorResponse": false, @@ -12571,18 +12951,18 @@ }, "parameters": [ { - "$ref": "816" + "$ref": "840" }, { - "$ref": "812" + "$ref": "836" }, { - "$ref": "814" + "$ref": "838" } ], "response": { "type": { - "$ref": "256" + "$ref": "272" } }, "isOverride": false, @@ -12591,7 +12971,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.internalProtocol" }, { - "$id": "817", + "$id": "841", "kind": "basic", "name": "stillConvenient", "isExactName": false, @@ -12602,7 +12982,7 @@ ], "doc": "When set protocol false and convenient true, the convenient method should be generated even it has the same signature as protocol one", "operation": { - "$id": "818", + "$id": "842", "name": "stillConvenient", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -12637,7 +13017,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.stillConvenient" }, { - "$id": "819", + "$id": "843", "kind": "basic", "name": "headAsBoolean", "isExactName": false, @@ -12648,7 +13028,7 @@ ], "doc": "head as boolean.", "operation": { - "$id": "820", + "$id": "844", "name": "headAsBoolean", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -12656,12 +13036,12 @@ "accessibility": "public", "parameters": [ { - "$id": "821", + "$id": "845", "kind": "path", "name": "id", "serializedName": "id", "type": { - "$id": "822", + "$id": "846", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -12679,12 +13059,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.headAsBoolean.id", "methodParameterSegments": [ { - "$id": "823", + "$id": "847", "kind": "method", "name": "id", "serializedName": "id", "type": { - "$id": "824", + "$id": "848", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -12726,7 +13106,7 @@ }, "parameters": [ { - "$ref": "823" + "$ref": "847" } ], "response": {}, @@ -12736,7 +13116,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.headAsBoolean" }, { - "$id": "825", + "$id": "849", "kind": "basic", "name": "WithApiVersion", "isExactName": false, @@ -12747,7 +13127,7 @@ ], "doc": "Return hi again", "operation": { - "$id": "826", + "$id": "850", "name": "WithApiVersion", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -12755,12 +13135,12 @@ "accessibility": "public", "parameters": [ { - "$id": "827", + "$id": "851", "kind": "header", "name": "p1", "serializedName": "p1", "type": { - "$id": "828", + "$id": "852", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -12775,12 +13155,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.WithApiVersion.p1", "methodParameterSegments": [ { - "$id": "829", + "$id": "853", "kind": "method", "name": "p1", "serializedName": "p1", "type": { - "$id": "830", + "$id": "854", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -12800,12 +13180,12 @@ "isExactName": false }, { - "$id": "831", + "$id": "855", "kind": "query", "name": "apiVersion", "serializedName": "apiVersion", "type": { - "$id": "832", + "$id": "856", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -12815,7 +13195,7 @@ "explode": false, "defaultValue": { "type": { - "$id": "833", + "$id": "857", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string" @@ -12829,12 +13209,12 @@ "readOnly": false, "methodParameterSegments": [ { - "$id": "834", + "$id": "858", "kind": "method", "name": "apiVersion", "serializedName": "apiVersion", "type": { - "$id": "835", + "$id": "859", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -12844,7 +13224,7 @@ "isApiVersion": true, "defaultValue": { "type": { - "$id": "836", + "$id": "860", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string" @@ -12885,7 +13265,7 @@ }, "parameters": [ { - "$ref": "829" + "$ref": "853" } ], "response": {}, @@ -12895,7 +13275,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.WithApiVersion" }, { - "$id": "837", + "$id": "861", "kind": "paging", "name": "ListWithNextLink", "isExactName": false, @@ -12906,7 +13286,7 @@ ], "doc": "List things with nextlink", "operation": { - "$id": "838", + "$id": "862", "name": "ListWithNextLink", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -12914,12 +13294,12 @@ "accessibility": "public", "parameters": [ { - "$id": "839", + "$id": "863", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "164" + "$ref": "172" }, "isApiVersion": false, "optional": false, @@ -12930,12 +13310,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.ListWithNextLink.accept", "methodParameterSegments": [ { - "$id": "840", + "$id": "864", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "164" + "$ref": "172" }, "location": "Header", "isApiVersion": false, @@ -12957,7 +13337,7 @@ 200 ], "bodyType": { - "$ref": "352" + "$ref": "368" }, "headers": [], "isErrorResponse": false, @@ -12983,12 +13363,12 @@ }, "parameters": [ { - "$ref": "840" + "$ref": "864" } ], "response": { "type": { - "$ref": "354" + "$ref": "370" }, "resultSegments": [ "things" @@ -13012,7 +13392,7 @@ } }, { - "$id": "841", + "$id": "865", "kind": "paging", "name": "ListWithStringNextLink", "isExactName": false, @@ -13023,7 +13403,7 @@ ], "doc": "List things with nextlink", "operation": { - "$id": "842", + "$id": "866", "name": "ListWithStringNextLink", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -13031,12 +13411,12 @@ "accessibility": "public", "parameters": [ { - "$id": "843", + "$id": "867", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "166" + "$ref": "174" }, "isApiVersion": false, "optional": false, @@ -13047,12 +13427,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.ListWithStringNextLink.accept", "methodParameterSegments": [ { - "$id": "844", + "$id": "868", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "166" + "$ref": "174" }, "location": "Header", "isApiVersion": false, @@ -13074,7 +13454,7 @@ 200 ], "bodyType": { - "$ref": "357" + "$ref": "373" }, "headers": [], "isErrorResponse": false, @@ -13100,12 +13480,12 @@ }, "parameters": [ { - "$ref": "844" + "$ref": "868" } ], "response": { "type": { - "$ref": "354" + "$ref": "370" }, "resultSegments": [ "things" @@ -13129,7 +13509,7 @@ } }, { - "$id": "845", + "$id": "869", "kind": "paging", "name": "ListWithContinuationToken", "isExactName": false, @@ -13140,7 +13520,7 @@ ], "doc": "List things with continuation token", "operation": { - "$id": "846", + "$id": "870", "name": "ListWithContinuationToken", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -13148,12 +13528,12 @@ "accessibility": "public", "parameters": [ { - "$id": "847", + "$id": "871", "kind": "query", "name": "token", "serializedName": "token", "type": { - "$id": "848", + "$id": "872", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -13168,12 +13548,12 @@ "readOnly": false, "methodParameterSegments": [ { - "$id": "849", + "$id": "873", "kind": "method", "name": "token", "serializedName": "token", "type": { - "$id": "850", + "$id": "874", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -13193,12 +13573,12 @@ "isExactName": false }, { - "$id": "851", + "$id": "875", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "168" + "$ref": "176" }, "isApiVersion": false, "optional": false, @@ -13209,12 +13589,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.ListWithContinuationToken.accept", "methodParameterSegments": [ { - "$id": "852", + "$id": "876", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "168" + "$ref": "176" }, "location": "Header", "isApiVersion": false, @@ -13236,7 +13616,7 @@ 200 ], "bodyType": { - "$ref": "361" + "$ref": "377" }, "headers": [], "isErrorResponse": false, @@ -13262,15 +13642,15 @@ }, "parameters": [ { - "$ref": "849" + "$ref": "873" }, { - "$ref": "852" + "$ref": "876" } ], "response": { "type": { - "$ref": "354" + "$ref": "370" }, "resultSegments": [ "things" @@ -13286,7 +13666,7 @@ ], "continuationToken": { "parameter": { - "$ref": "847" + "$ref": "871" }, "responseSegments": [ "nextToken" @@ -13297,7 +13677,7 @@ } }, { - "$id": "853", + "$id": "877", "kind": "paging", "name": "ListWithContinuationTokenHeaderResponse", "isExactName": false, @@ -13308,7 +13688,7 @@ ], "doc": "List things with continuation token header response", "operation": { - "$id": "854", + "$id": "878", "name": "ListWithContinuationTokenHeaderResponse", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -13316,12 +13696,12 @@ "accessibility": "public", "parameters": [ { - "$id": "855", + "$id": "879", "kind": "query", "name": "token", "serializedName": "token", "type": { - "$id": "856", + "$id": "880", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -13336,12 +13716,12 @@ "readOnly": false, "methodParameterSegments": [ { - "$id": "857", + "$id": "881", "kind": "method", "name": "token", "serializedName": "token", "type": { - "$id": "858", + "$id": "882", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -13361,12 +13741,12 @@ "isExactName": false }, { - "$id": "859", + "$id": "883", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "170" + "$ref": "178" }, "isApiVersion": false, "optional": false, @@ -13377,12 +13757,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.ListWithContinuationTokenHeaderResponse.accept", "methodParameterSegments": [ { - "$id": "860", + "$id": "884", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "170" + "$ref": "178" }, "location": "Header", "isApiVersion": false, @@ -13404,14 +13784,14 @@ 200 ], "bodyType": { - "$ref": "365" + "$ref": "381" }, "headers": [ { "name": "nextToken", "nameInResponse": "next-token", "type": { - "$id": "861", + "$id": "885", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -13442,15 +13822,15 @@ }, "parameters": [ { - "$ref": "857" + "$ref": "881" }, { - "$ref": "860" + "$ref": "884" } ], "response": { "type": { - "$ref": "354" + "$ref": "370" }, "resultSegments": [ "things" @@ -13466,7 +13846,7 @@ ], "continuationToken": { "parameter": { - "$ref": "855" + "$ref": "879" }, "responseSegments": [ "next-token" @@ -13477,7 +13857,7 @@ } }, { - "$id": "862", + "$id": "886", "kind": "paging", "name": "ListWithPaging", "isExactName": false, @@ -13488,7 +13868,7 @@ ], "doc": "List things with paging", "operation": { - "$id": "863", + "$id": "887", "name": "ListWithPaging", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -13496,12 +13876,12 @@ "accessibility": "public", "parameters": [ { - "$id": "864", + "$id": "888", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "172" + "$ref": "180" }, "isApiVersion": false, "optional": false, @@ -13512,12 +13892,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.ListWithPaging.accept", "methodParameterSegments": [ { - "$id": "865", + "$id": "889", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "172" + "$ref": "180" }, "location": "Header", "isApiVersion": false, @@ -13539,7 +13919,7 @@ 200 ], "bodyType": { - "$ref": "367" + "$ref": "383" }, "headers": [], "isErrorResponse": false, @@ -13565,12 +13945,12 @@ }, "parameters": [ { - "$ref": "865" + "$ref": "889" } ], "response": { "type": { - "$ref": "354" + "$ref": "370" }, "resultSegments": [ "items" @@ -13588,7 +13968,7 @@ } }, { - "$id": "866", + "$id": "890", "kind": "basic", "name": "EmbeddedParameters", "isExactName": false, @@ -13599,7 +13979,7 @@ ], "doc": "An operation with embedded parameters within the body", "operation": { - "$id": "867", + "$id": "891", "name": "EmbeddedParameters", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -13607,13 +13987,13 @@ "accessibility": "public", "parameters": [ { - "$id": "868", + "$id": "892", "kind": "header", "name": "requiredHeader", "serializedName": "required-header", "doc": "required header parameter", "type": { - "$id": "869", + "$id": "893", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -13628,12 +14008,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.ModelWithEmbeddedNonBodyParameters.requiredHeader", "methodParameterSegments": [ { - "$id": "870", + "$id": "894", "kind": "method", "name": "body", "serializedName": "body", "type": { - "$ref": "369" + "$ref": "385" }, "location": "Body", "isApiVersion": false, @@ -13646,13 +14026,13 @@ "isExactName": false }, { - "$id": "871", + "$id": "895", "kind": "method", "name": "requiredHeader", "serializedName": "requiredHeader", "doc": "required header parameter", "type": { - "$ref": "373" + "$ref": "389" }, "location": "", "isApiVersion": false, @@ -13668,13 +14048,13 @@ "isExactName": false }, { - "$id": "872", + "$id": "896", "kind": "header", "name": "optionalHeader", "serializedName": "optional-header", "doc": "optional header parameter", "type": { - "$id": "873", + "$id": "897", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -13689,16 +14069,16 @@ "crossLanguageDefinitionId": "SampleTypeSpec.ModelWithEmbeddedNonBodyParameters.optionalHeader", "methodParameterSegments": [ { - "$ref": "870" + "$ref": "894" }, { - "$id": "874", + "$id": "898", "kind": "method", "name": "optionalHeader", "serializedName": "optionalHeader", "doc": "optional header parameter", "type": { - "$ref": "375" + "$ref": "391" }, "location": "", "isApiVersion": false, @@ -13714,13 +14094,13 @@ "isExactName": false }, { - "$id": "875", + "$id": "899", "kind": "query", "name": "requiredQuery", "serializedName": "requiredQuery", "doc": "required query parameter", "type": { - "$id": "876", + "$id": "900", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -13735,16 +14115,16 @@ "readOnly": false, "methodParameterSegments": [ { - "$ref": "870" + "$ref": "894" }, { - "$id": "877", + "$id": "901", "kind": "method", "name": "requiredQuery", "serializedName": "requiredQuery", "doc": "required query parameter", "type": { - "$ref": "377" + "$ref": "393" }, "location": "", "isApiVersion": false, @@ -13760,13 +14140,13 @@ "isExactName": false }, { - "$id": "878", + "$id": "902", "kind": "query", "name": "optionalQuery", "serializedName": "optionalQuery", "doc": "optional query parameter", "type": { - "$id": "879", + "$id": "903", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -13781,16 +14161,16 @@ "readOnly": false, "methodParameterSegments": [ { - "$ref": "870" + "$ref": "894" }, { - "$id": "880", + "$id": "904", "kind": "method", "name": "optionalQuery", "serializedName": "optionalQuery", "doc": "optional query parameter", "type": { - "$ref": "379" + "$ref": "395" }, "location": "", "isApiVersion": false, @@ -13806,13 +14186,13 @@ "isExactName": false }, { - "$id": "881", + "$id": "905", "kind": "header", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "174" + "$ref": "182" }, "isApiVersion": false, "optional": false, @@ -13823,13 +14203,13 @@ "crossLanguageDefinitionId": "SampleTypeSpec.EmbeddedParameters.contentType", "methodParameterSegments": [ { - "$id": "882", + "$id": "906", "kind": "method", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "174" + "$ref": "182" }, "location": "Header", "isApiVersion": false, @@ -13845,12 +14225,12 @@ "isExactName": false }, { - "$id": "883", + "$id": "907", "kind": "body", "name": "body", "serializedName": "body", "type": { - "$ref": "369" + "$ref": "385" }, "isApiVersion": false, "contentTypes": [ @@ -13864,7 +14244,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.EmbeddedParameters.body", "methodParameterSegments": [ { - "$ref": "870" + "$ref": "894" } ], "isExactName": false, @@ -13900,10 +14280,10 @@ }, "parameters": [ { - "$ref": "870" + "$ref": "894" }, { - "$ref": "882" + "$ref": "906" } ], "response": {}, @@ -13913,7 +14293,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.EmbeddedParameters" }, { - "$id": "884", + "$id": "908", "kind": "basic", "name": "DynamicModelOperation", "isExactName": false, @@ -13924,7 +14304,7 @@ ], "doc": "An operation with a dynamic model", "operation": { - "$id": "885", + "$id": "909", "name": "DynamicModelOperation", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -13932,13 +14312,13 @@ "accessibility": "public", "parameters": [ { - "$id": "886", + "$id": "910", "kind": "header", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "176" + "$ref": "184" }, "isApiVersion": false, "optional": false, @@ -13949,13 +14329,13 @@ "crossLanguageDefinitionId": "SampleTypeSpec.DynamicModelOperation.contentType", "methodParameterSegments": [ { - "$id": "887", + "$id": "911", "kind": "method", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "176" + "$ref": "184" }, "location": "Header", "isApiVersion": false, @@ -13971,12 +14351,12 @@ "isExactName": false }, { - "$id": "888", + "$id": "912", "kind": "body", "name": "body", "serializedName": "body", "type": { - "$ref": "380" + "$ref": "396" }, "isApiVersion": false, "contentTypes": [ @@ -13990,12 +14370,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.DynamicModelOperation.body", "methodParameterSegments": [ { - "$id": "889", + "$id": "913", "kind": "method", "name": "body", "serializedName": "body", "type": { - "$ref": "380" + "$ref": "396" }, "location": "Body", "isApiVersion": false, @@ -14047,10 +14427,10 @@ }, "parameters": [ { - "$ref": "889" + "$ref": "913" }, { - "$ref": "887" + "$ref": "911" } ], "response": {}, @@ -14060,7 +14440,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.DynamicModelOperation" }, { - "$id": "890", + "$id": "914", "kind": "basic", "name": "GetXmlAdvancedModel", "isExactName": false, @@ -14071,7 +14451,7 @@ ], "doc": "Get an advanced XML model with various property types", "operation": { - "$id": "891", + "$id": "915", "name": "GetXmlAdvancedModel", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -14079,12 +14459,12 @@ "accessibility": "public", "parameters": [ { - "$id": "892", + "$id": "916", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "178" + "$ref": "186" }, "isApiVersion": false, "optional": false, @@ -14095,12 +14475,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.GetXmlAdvancedModel.accept", "methodParameterSegments": [ { - "$id": "893", + "$id": "917", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "178" + "$ref": "186" }, "location": "Header", "isApiVersion": false, @@ -14122,14 +14502,14 @@ 200 ], "bodyType": { - "$ref": "418" + "$ref": "434" }, "headers": [ { "name": "contentType", "nameInResponse": "content-type", "type": { - "$ref": "180" + "$ref": "188" } } ], @@ -14156,12 +14536,12 @@ }, "parameters": [ { - "$ref": "893" + "$ref": "917" } ], "response": { "type": { - "$ref": "418" + "$ref": "434" } }, "isOverride": false, @@ -14170,7 +14550,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.GetXmlAdvancedModel" }, { - "$id": "894", + "$id": "918", "kind": "basic", "name": "UpdateXmlAdvancedModel", "isExactName": false, @@ -14181,7 +14561,7 @@ ], "doc": "Update an advanced XML model with various property types", "operation": { - "$id": "895", + "$id": "919", "name": "UpdateXmlAdvancedModel", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -14189,12 +14569,12 @@ "accessibility": "public", "parameters": [ { - "$id": "896", + "$id": "920", "kind": "header", "name": "contentType", "serializedName": "Content-Type", "type": { - "$ref": "182" + "$ref": "190" }, "isApiVersion": false, "optional": false, @@ -14205,12 +14585,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.UpdateXmlAdvancedModel.contentType", "methodParameterSegments": [ { - "$id": "897", + "$id": "921", "kind": "method", "name": "contentType", "serializedName": "Content-Type", "type": { - "$ref": "182" + "$ref": "190" }, "location": "Header", "isApiVersion": false, @@ -14226,12 +14606,12 @@ "isExactName": false }, { - "$id": "898", + "$id": "922", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "186" + "$ref": "194" }, "isApiVersion": false, "optional": false, @@ -14242,12 +14622,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.UpdateXmlAdvancedModel.accept", "methodParameterSegments": [ { - "$id": "899", + "$id": "923", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "186" + "$ref": "194" }, "location": "Header", "isApiVersion": false, @@ -14263,12 +14643,12 @@ "isExactName": false }, { - "$id": "900", + "$id": "924", "kind": "body", "name": "body", "serializedName": "body", "type": { - "$ref": "418" + "$ref": "434" }, "isApiVersion": false, "contentTypes": [ @@ -14282,12 +14662,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.UpdateXmlAdvancedModel.body", "methodParameterSegments": [ { - "$id": "901", + "$id": "925", "kind": "method", "name": "body", "serializedName": "body", "type": { - "$ref": "418" + "$ref": "434" }, "location": "Body", "isApiVersion": false, @@ -14314,14 +14694,14 @@ 200 ], "bodyType": { - "$ref": "418" + "$ref": "434" }, "headers": [ { "name": "contentType", "nameInResponse": "content-type", "type": { - "$ref": "188" + "$ref": "196" } } ], @@ -14351,18 +14731,18 @@ }, "parameters": [ { - "$ref": "901" + "$ref": "925" }, { - "$ref": "897" + "$ref": "921" }, { - "$ref": "899" + "$ref": "923" } ], "response": { "type": { - "$ref": "418" + "$ref": "434" } }, "isOverride": false, @@ -14371,7 +14751,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.UpdateXmlAdvancedModel" }, { - "$id": "902", + "$id": "926", "kind": "basic", "name": "uploadCat", "isExactName": false, @@ -14381,19 +14761,19 @@ "2024-08-16-preview" ], "operation": { - "$id": "903", + "$id": "927", "name": "uploadCat", "isExactName": false, "resourceName": "SampleTypeSpec", "accessibility": "public", "parameters": [ { - "$id": "904", + "$id": "928", "kind": "header", "name": "contentType", "serializedName": "Content-Type", "type": { - "$ref": "190" + "$ref": "198" }, "isApiVersion": false, "optional": false, @@ -14404,12 +14784,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.uploadCat.contentType", "methodParameterSegments": [ { - "$id": "905", + "$id": "929", "kind": "method", "name": "contentType", "serializedName": "Content-Type", "type": { - "$ref": "190" + "$ref": "198" }, "location": "Header", "isApiVersion": false, @@ -14425,12 +14805,12 @@ "isExactName": false }, { - "$id": "906", + "$id": "930", "kind": "body", "name": "body", "serializedName": "body", "type": { - "$ref": "514" + "$ref": "530" }, "isApiVersion": false, "contentTypes": [ @@ -14444,12 +14824,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.uploadCat.body", "methodParameterSegments": [ { - "$id": "907", + "$id": "931", "kind": "method", "name": "body", "serializedName": "body", "type": { - "$ref": "514" + "$ref": "530" }, "location": "Body", "isApiVersion": false, @@ -14491,10 +14871,10 @@ }, "parameters": [ { - "$ref": "905" + "$ref": "929" }, { - "$ref": "907" + "$ref": "931" } ], "response": {}, @@ -14504,7 +14884,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.uploadCat" }, { - "$id": "908", + "$id": "932", "kind": "basic", "name": "sendJsonLines", "isExactName": false, @@ -14514,19 +14894,19 @@ "2024-08-16-preview" ], "operation": { - "$id": "909", + "$id": "933", "name": "sendJsonLines", "isExactName": false, "resourceName": "SampleTypeSpec", "accessibility": "public", "parameters": [ { - "$id": "910", + "$id": "934", "kind": "header", "name": "contentType", "serializedName": "Content-Type", "type": { - "$ref": "194" + "$ref": "202" }, "isApiVersion": false, "optional": false, @@ -14537,16 +14917,16 @@ "crossLanguageDefinitionId": "TypeSpec.Http.Streams.JsonlStream.contentType", "methodParameterSegments": [ { - "$id": "911", + "$id": "935", "kind": "method", "name": "stream", "serializedName": "stream", "type": { - "$id": "912", + "$id": "936", "kind": "streaming", "name": "JsonlStreamStreamingItem", "valueType": { - "$ref": "559" + "$ref": "575" }, "streamKind": "jsonl", "contentTypes": [ @@ -14565,12 +14945,12 @@ "isExactName": false }, { - "$id": "913", + "$id": "937", "kind": "method", "name": "contentType", "serializedName": "contentType", "type": { - "$ref": "196" + "$ref": "204" }, "location": "", "isApiVersion": false, @@ -14586,16 +14966,16 @@ "isExactName": false }, { - "$id": "914", + "$id": "938", "kind": "body", "name": "body", "serializedName": "body", "type": { - "$id": "915", + "$id": "939", "kind": "streaming", "name": "JsonlStreamStreamingItem", "valueType": { - "$ref": "559" + "$ref": "575" }, "streamKind": "jsonl", "contentTypes": [ @@ -14615,15 +14995,15 @@ "crossLanguageDefinitionId": "TypeSpec.Http.Streams.JsonlStream.body", "methodParameterSegments": [ { - "$ref": "911" + "$ref": "935" }, { - "$id": "916", + "$id": "940", "kind": "method", "name": "body", "serializedName": "body", "type": { - "$ref": "639" + "$ref": "663" }, "location": "", "isApiVersion": false, @@ -14669,7 +15049,7 @@ }, "parameters": [ { - "$ref": "911" + "$ref": "935" } ], "response": {}, @@ -14679,7 +15059,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.sendJsonLines" }, { - "$id": "917", + "$id": "941", "kind": "basic", "name": "receiveJsonLines", "isExactName": false, @@ -14689,19 +15069,19 @@ "2024-08-16-preview" ], "operation": { - "$id": "918", + "$id": "942", "name": "receiveJsonLines", "isExactName": false, "resourceName": "SampleTypeSpec", "accessibility": "public", "parameters": [ { - "$id": "919", + "$id": "943", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "198" + "$ref": "206" }, "isApiVersion": false, "optional": false, @@ -14712,12 +15092,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.receiveJsonLines.accept", "methodParameterSegments": [ { - "$id": "920", + "$id": "944", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "198" + "$ref": "206" }, "location": "Header", "isApiVersion": false, @@ -14739,11 +15119,11 @@ 200 ], "bodyType": { - "$id": "921", + "$id": "945", "kind": "streaming", "name": "JsonlStreamStreamingItem", "valueType": { - "$ref": "559" + "$ref": "575" }, "streamKind": "jsonl", "contentTypes": [ @@ -14756,7 +15136,7 @@ "name": "contentType", "nameInResponse": "content-type", "type": { - "$ref": "200" + "$ref": "208" } } ], @@ -14783,16 +15163,16 @@ }, "parameters": [ { - "$ref": "920" + "$ref": "944" } ], "response": { "type": { - "$id": "922", + "$id": "946", "kind": "streaming", "name": "JsonlStreamStreamingItem", "valueType": { - "$ref": "559" + "$ref": "575" }, "streamKind": "jsonl", "contentTypes": [ @@ -14807,7 +15187,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.receiveJsonLines" }, { - "$id": "923", + "$id": "947", "kind": "basic", "name": "receiveSse", "isExactName": false, @@ -14817,19 +15197,19 @@ "2024-08-16-preview" ], "operation": { - "$id": "924", + "$id": "948", "name": "receiveSse", "isExactName": false, "resourceName": "SampleTypeSpec", "accessibility": "public", "parameters": [ { - "$id": "925", + "$id": "949", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "202" + "$ref": "210" }, "isApiVersion": false, "optional": false, @@ -14840,12 +15220,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.receiveSse.accept", "methodParameterSegments": [ { - "$id": "926", + "$id": "950", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "202" + "$ref": "210" }, "location": "Header", "isApiVersion": false, @@ -14867,128 +15247,512 @@ 200 ], "bodyType": { - "$id": "927", + "$id": "951", "kind": "streaming", "name": "SSEStreamSampleEvents", "valueType": { - "$id": "928", + "$id": "952", "kind": "union", "name": "SampleEvents", "variantTypes": [ { - "$ref": "559" + "$ref": "575" }, { - "$ref": "204" + "$ref": "212" + } + ], + "namespace": "SampleTypeSpec", + "decorators": [], + "isExactName": false + }, + "streamKind": "sse", + "contentTypes": [ + "text/event-stream" + ], + "terminalEventValue": "[DONE]", + "crossLanguageDefinitionId": "TypeSpec.SSE.SSEStream" + }, + "headers": [ + { + "name": "contentType", + "nameInResponse": "content-type", + "type": { + "$ref": "214" + } + } + ], + "isErrorResponse": false, + "contentTypes": [ + "text/event-stream" + ], + "serializationOptions": {} + } + ], + "httpMethod": "GET", + "uri": "{sampleTypeSpecUrl}", + "path": "/streaming/sse/receive", + "bufferResponse": false, + "generateProtocolMethod": true, + "generateConvenienceMethod": true, + "crossLanguageDefinitionId": "SampleTypeSpec.receiveSse", + "decorators": [], + "namespace": "SampleTypeSpec" + }, + "parameters": [ + { + "$ref": "950" + } + ], + "response": { + "type": { + "$id": "953", + "kind": "streaming", + "name": "SSEStreamSampleEvents", + "valueType": { + "$ref": "952" + }, + "streamKind": "sse", + "contentTypes": [ + "text/event-stream" + ], + "terminalEventValue": "[DONE]", + "crossLanguageDefinitionId": "TypeSpec.SSE.SSEStream" + } + }, + "isOverride": false, + "generateConvenient": true, + "generateProtocol": true, + "crossLanguageDefinitionId": "SampleTypeSpec.receiveSse" + }, + { + "$id": "954", + "kind": "basic", + "name": "receiveExperimentalJsonLines", + "isExactName": false, + "accessibility": "public", + "apiVersions": [ + "2024-07-16-preview", + "2024-08-16-preview" + ], + "operation": { + "$id": "955", + "name": "receiveExperimentalJsonLines", + "isExactName": false, + "resourceName": "SampleTypeSpec", + "accessibility": "public", + "parameters": [ + { + "$id": "956", + "kind": "header", + "name": "accept", + "serializedName": "Accept", + "type": { + "$ref": "216" + }, + "isApiVersion": false, + "optional": false, + "isContentType": false, + "scope": "Constant", + "readOnly": false, + "decorators": [], + "crossLanguageDefinitionId": "SampleTypeSpec.receiveExperimentalJsonLines.accept", + "methodParameterSegments": [ + { + "$id": "957", + "kind": "method", + "name": "accept", + "serializedName": "Accept", + "type": { + "$ref": "216" + }, + "location": "Header", + "isApiVersion": false, + "optional": false, + "scope": "Constant", + "crossLanguageDefinitionId": "SampleTypeSpec.receiveExperimentalJsonLines.accept", + "readOnly": false, + "access": "public", + "decorators": [], + "isExactName": false + } + ], + "isExactName": false + } + ], + "responses": [ + { + "statusCodes": [ + 200 + ], + "bodyType": { + "$id": "958", + "kind": "streaming", + "name": "JsonlStreamPreviewDetails", + "valueType": { + "$ref": "578" + }, + "streamKind": "jsonl", + "contentTypes": [ + "application/jsonl" + ], + "crossLanguageDefinitionId": "TypeSpec.Http.Streams.JsonlStream" + }, + "headers": [ + { + "name": "contentType", + "nameInResponse": "content-type", + "type": { + "$ref": "218" + } + } + ], + "isErrorResponse": false, + "contentTypes": [ + "application/jsonl" + ], + "serializationOptions": { + "json": { + "name": "" + } + } + } + ], + "httpMethod": "GET", + "uri": "{sampleTypeSpecUrl}", + "path": "/experimental/jsonl", + "bufferResponse": false, + "generateProtocolMethod": true, + "generateConvenienceMethod": true, + "crossLanguageDefinitionId": "SampleTypeSpec.receiveExperimentalJsonLines", + "decorators": [], + "namespace": "SampleTypeSpec" + }, + "parameters": [ + { + "$ref": "957" + } + ], + "response": { + "type": { + "$id": "959", + "kind": "streaming", + "name": "JsonlStreamPreviewDetails", + "valueType": { + "$ref": "578" + }, + "streamKind": "jsonl", + "contentTypes": [ + "application/jsonl" + ], + "crossLanguageDefinitionId": "TypeSpec.Http.Streams.JsonlStream" + } + }, + "isOverride": false, + "generateConvenient": true, + "generateProtocol": true, + "crossLanguageDefinitionId": "SampleTypeSpec.receiveExperimentalJsonLines" + } + ], + "parameters": [ + { + "$id": "960", + "kind": "endpoint", + "name": "sampleTypeSpecUrl", + "serializedName": "sampleTypeSpecUrl", + "type": { + "$id": "961", + "kind": "url", + "name": "endpoint", + "crossLanguageDefinitionId": "TypeSpec.url" + }, + "isApiVersion": false, + "optional": false, + "scope": "Client", + "isEndpoint": true, + "serverUrlTemplate": "{sampleTypeSpecUrl}", + "skipUrlEncoding": false, + "readOnly": false, + "crossLanguageDefinitionId": "SampleTypeSpec.sampleTypeSpecUrl", + "isExactName": false + }, + { + "$ref": "858" + } + ], + "initializedBy": 1, + "decorators": [], + "crossLanguageDefinitionId": "SampleTypeSpec", + "apiVersions": [ + "2024-07-16-preview", + "2024-08-16-preview" + ], + "children": [ + { + "$id": "962", + "kind": "client", + "name": "ExperimentalSamples", + "isExactName": false, + "namespace": "SampleTypeSpec", + "methods": [ + { + "$id": "963", + "kind": "basic", + "name": "read", + "isExactName": false, + "accessibility": "public", + "apiVersions": [ + "2024-07-16-preview", + "2024-08-16-preview" + ], + "operation": { + "$id": "964", + "name": "read", + "isExactName": false, + "resourceName": "ExperimentalSamples", + "accessibility": "public", + "parameters": [ + { + "$id": "965", + "kind": "header", + "name": "accept", + "serializedName": "Accept", + "type": { + "$ref": "220" + }, + "isApiVersion": false, + "optional": false, + "isContentType": false, + "scope": "Constant", + "readOnly": false, + "decorators": [], + "crossLanguageDefinitionId": "SampleTypeSpec.ExperimentalSamples.read.accept", + "methodParameterSegments": [ + { + "$id": "966", + "kind": "method", + "name": "accept", + "serializedName": "Accept", + "type": { + "$ref": "220" + }, + "location": "Header", + "isApiVersion": false, + "optional": false, + "scope": "Constant", + "crossLanguageDefinitionId": "SampleTypeSpec.ExperimentalSamples.read.accept", + "readOnly": false, + "access": "public", + "decorators": [], + "isExactName": false + } + ], + "isExactName": false + } + ], + "responses": [ + { + "statusCodes": [ + 200 + ], + "bodyType": { + "$ref": "580" + }, + "headers": [], + "isErrorResponse": false, + "contentTypes": [ + "application/json" + ], + "serializationOptions": { + "json": { + "name": "" + } + } + } + ], + "httpMethod": "GET", + "uri": "{sampleTypeSpecUrl}", + "path": "/experimental", + "bufferResponse": true, + "generateProtocolMethod": true, + "generateConvenienceMethod": true, + "crossLanguageDefinitionId": "SampleTypeSpec.ExperimentalSamples.read", + "decorators": [], + "namespace": "SampleTypeSpec" + }, + "parameters": [ + { + "$ref": "966" + } + ], + "response": { + "type": { + "$ref": "580" + } + }, + "isOverride": false, + "generateConvenient": true, + "generateProtocol": true, + "crossLanguageDefinitionId": "SampleTypeSpec.ExperimentalSamples.read" + }, + { + "$id": "967", + "kind": "paging", + "name": "list", + "isExactName": false, + "accessibility": "public", + "apiVersions": [ + "2024-07-16-preview", + "2024-08-16-preview" + ], + "operation": { + "$id": "968", + "name": "list", + "isExactName": false, + "resourceName": "ExperimentalSamples", + "accessibility": "public", + "parameters": [ + { + "$id": "969", + "kind": "header", + "name": "accept", + "serializedName": "Accept", + "type": { + "$ref": "222" + }, + "isApiVersion": false, + "optional": false, + "isContentType": false, + "scope": "Constant", + "readOnly": false, + "decorators": [], + "crossLanguageDefinitionId": "SampleTypeSpec.ExperimentalSamples.list.accept", + "methodParameterSegments": [ + { + "$id": "970", + "kind": "method", + "name": "accept", + "serializedName": "Accept", + "type": { + "$ref": "222" + }, + "location": "Header", + "isApiVersion": false, + "optional": false, + "scope": "Constant", + "crossLanguageDefinitionId": "SampleTypeSpec.ExperimentalSamples.list.accept", + "readOnly": false, + "access": "public", + "decorators": [], + "isExactName": false } ], - "namespace": "SampleTypeSpec", - "decorators": [], "isExactName": false - }, - "streamKind": "sse", - "contentTypes": [ - "text/event-stream" - ], - "terminalEventValue": "[DONE]", - "crossLanguageDefinitionId": "TypeSpec.SSE.SSEStream" - }, - "headers": [ + } + ], + "responses": [ { - "name": "contentType", - "nameInResponse": "content-type", - "type": { - "$ref": "206" + "statusCodes": [ + 200 + ], + "bodyType": { + "$ref": "583" + }, + "headers": [], + "isErrorResponse": false, + "contentTypes": [ + "application/json" + ], + "serializationOptions": { + "json": { + "name": "" + } } } ], - "isErrorResponse": false, - "contentTypes": [ - "text/event-stream" + "httpMethod": "GET", + "uri": "{sampleTypeSpecUrl}", + "path": "/experimental/pages", + "bufferResponse": true, + "generateProtocolMethod": true, + "generateConvenienceMethod": true, + "crossLanguageDefinitionId": "SampleTypeSpec.ExperimentalSamples.list", + "decorators": [], + "namespace": "SampleTypeSpec" + }, + "parameters": [ + { + "$ref": "970" + } + ], + "response": { + "type": { + "$ref": "585" + }, + "resultSegments": [ + "items" + ] + }, + "isOverride": false, + "generateConvenient": true, + "generateProtocol": true, + "crossLanguageDefinitionId": "SampleTypeSpec.ExperimentalSamples.list", + "pagingMetadata": { + "itemPropertySegments": [ + "items" ], - "serializationOptions": {} + "pageSizeParameterSegments": [] } - ], - "httpMethod": "GET", - "uri": "{sampleTypeSpecUrl}", - "path": "/streaming/sse/receive", - "bufferResponse": false, - "generateProtocolMethod": true, - "generateConvenienceMethod": true, - "crossLanguageDefinitionId": "SampleTypeSpec.receiveSse", - "decorators": [], - "namespace": "SampleTypeSpec" - }, - "parameters": [ - { - "$ref": "926" } ], - "response": { - "type": { - "$id": "929", - "kind": "streaming", - "name": "SSEStreamSampleEvents", - "valueType": { - "$ref": "928" + "parameters": [ + { + "$id": "971", + "kind": "endpoint", + "name": "sampleTypeSpecUrl", + "serializedName": "sampleTypeSpecUrl", + "type": { + "$id": "972", + "kind": "url", + "name": "endpoint", + "crossLanguageDefinitionId": "TypeSpec.url" }, - "streamKind": "sse", - "contentTypes": [ - "text/event-stream" - ], - "terminalEventValue": "[DONE]", - "crossLanguageDefinitionId": "TypeSpec.SSE.SSEStream" + "isApiVersion": false, + "optional": false, + "scope": "Client", + "isEndpoint": true, + "serverUrlTemplate": "{sampleTypeSpecUrl}", + "skipUrlEncoding": false, + "readOnly": false, + "crossLanguageDefinitionId": "SampleTypeSpec.ExperimentalSamples.sampleTypeSpecUrl", + "isExactName": false } + ], + "initializedBy": 0, + "decorators": [], + "crossLanguageDefinitionId": "SampleTypeSpec.ExperimentalSamples", + "apiVersions": [ + "2024-07-16-preview", + "2024-08-16-preview" + ], + "parent": { + "$ref": "664" }, - "isOverride": false, - "generateConvenient": true, - "generateProtocol": true, - "crossLanguageDefinitionId": "SampleTypeSpec.receiveSse" - } - ], - "parameters": [ - { - "$id": "930", - "kind": "endpoint", - "name": "sampleTypeSpecUrl", - "serializedName": "sampleTypeSpecUrl", - "type": { - "$id": "931", - "kind": "url", - "name": "endpoint", - "crossLanguageDefinitionId": "TypeSpec.url" - }, - "isApiVersion": false, - "optional": false, - "scope": "Client", - "isEndpoint": true, - "serverUrlTemplate": "{sampleTypeSpecUrl}", - "skipUrlEncoding": false, - "readOnly": false, - "crossLanguageDefinitionId": "SampleTypeSpec.sampleTypeSpecUrl", - "isExactName": false + "isMultiServiceClient": false, + "experimental": { + "diagnosticId": "SAMPLE0009", + "dependsOn": [] + } }, { - "$ref": "834" - } - ], - "initializedBy": 1, - "decorators": [], - "crossLanguageDefinitionId": "SampleTypeSpec", - "apiVersions": [ - "2024-07-16-preview", - "2024-08-16-preview" - ], - "children": [ - { - "$id": "932", + "$id": "973", "kind": "client", "name": "AnimalOperations", "isExactName": false, "namespace": "SampleTypeSpec", "methods": [ { - "$id": "933", + "$id": "974", "kind": "basic", "name": "updatePetAsAnimal", "isExactName": false, @@ -14999,7 +15763,7 @@ ], "doc": "Update a pet as an animal", "operation": { - "$id": "934", + "$id": "975", "name": "updatePetAsAnimal", "isExactName": false, "resourceName": "AnimalOperations", @@ -15007,13 +15771,13 @@ "accessibility": "public", "parameters": [ { - "$id": "935", + "$id": "976", "kind": "header", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "208" + "$ref": "224" }, "isApiVersion": false, "optional": false, @@ -15024,13 +15788,13 @@ "crossLanguageDefinitionId": "SampleTypeSpec.AnimalOperations.updatePetAsAnimal.contentType", "methodParameterSegments": [ { - "$id": "936", + "$id": "977", "kind": "method", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "208" + "$ref": "224" }, "location": "Header", "isApiVersion": false, @@ -15046,12 +15810,12 @@ "isExactName": false }, { - "$id": "937", + "$id": "978", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "210" + "$ref": "226" }, "isApiVersion": false, "optional": false, @@ -15062,12 +15826,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.AnimalOperations.updatePetAsAnimal.accept", "methodParameterSegments": [ { - "$id": "938", + "$id": "979", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "210" + "$ref": "226" }, "location": "Header", "isApiVersion": false, @@ -15083,12 +15847,12 @@ "isExactName": false }, { - "$id": "939", + "$id": "980", "kind": "body", "name": "animal", "serializedName": "animal", "type": { - "$ref": "562" + "$ref": "586" }, "isApiVersion": false, "contentTypes": [ @@ -15102,12 +15866,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.AnimalOperations.updatePetAsAnimal.animal", "methodParameterSegments": [ { - "$id": "940", + "$id": "981", "kind": "method", "name": "animal", "serializedName": "animal", "type": { - "$ref": "562" + "$ref": "586" }, "location": "Body", "isApiVersion": false, @@ -15134,7 +15898,7 @@ 200 ], "bodyType": { - "$ref": "562" + "$ref": "586" }, "headers": [], "isErrorResponse": false, @@ -15163,18 +15927,18 @@ }, "parameters": [ { - "$ref": "940" + "$ref": "981" }, { - "$ref": "936" + "$ref": "977" }, { - "$ref": "938" + "$ref": "979" } ], "response": { "type": { - "$ref": "562" + "$ref": "586" } }, "isOverride": false, @@ -15183,7 +15947,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.AnimalOperations.updatePetAsAnimal" }, { - "$id": "941", + "$id": "982", "kind": "basic", "name": "updateDogAsAnimal", "isExactName": false, @@ -15194,7 +15958,7 @@ ], "doc": "Update a dog as an animal", "operation": { - "$id": "942", + "$id": "983", "name": "updateDogAsAnimal", "isExactName": false, "resourceName": "AnimalOperations", @@ -15202,13 +15966,13 @@ "accessibility": "public", "parameters": [ { - "$id": "943", + "$id": "984", "kind": "header", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "212" + "$ref": "228" }, "isApiVersion": false, "optional": false, @@ -15219,13 +15983,13 @@ "crossLanguageDefinitionId": "SampleTypeSpec.AnimalOperations.updateDogAsAnimal.contentType", "methodParameterSegments": [ { - "$id": "944", + "$id": "985", "kind": "method", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "212" + "$ref": "228" }, "location": "Header", "isApiVersion": false, @@ -15241,12 +16005,12 @@ "isExactName": false }, { - "$id": "945", + "$id": "986", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "214" + "$ref": "230" }, "isApiVersion": false, "optional": false, @@ -15257,12 +16021,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.AnimalOperations.updateDogAsAnimal.accept", "methodParameterSegments": [ { - "$id": "946", + "$id": "987", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "214" + "$ref": "230" }, "location": "Header", "isApiVersion": false, @@ -15278,12 +16042,12 @@ "isExactName": false }, { - "$id": "947", + "$id": "988", "kind": "body", "name": "animal", "serializedName": "animal", "type": { - "$ref": "562" + "$ref": "586" }, "isApiVersion": false, "contentTypes": [ @@ -15297,12 +16061,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.AnimalOperations.updateDogAsAnimal.animal", "methodParameterSegments": [ { - "$id": "948", + "$id": "989", "kind": "method", "name": "animal", "serializedName": "animal", "type": { - "$ref": "562" + "$ref": "586" }, "location": "Body", "isApiVersion": false, @@ -15329,7 +16093,7 @@ 200 ], "bodyType": { - "$ref": "562" + "$ref": "586" }, "headers": [], "isErrorResponse": false, @@ -15358,18 +16122,18 @@ }, "parameters": [ { - "$ref": "948" + "$ref": "989" }, { - "$ref": "944" + "$ref": "985" }, { - "$ref": "946" + "$ref": "987" } ], "response": { "type": { - "$ref": "562" + "$ref": "586" } }, "isOverride": false, @@ -15380,12 +16144,12 @@ ], "parameters": [ { - "$id": "949", + "$id": "990", "kind": "endpoint", "name": "sampleTypeSpecUrl", "serializedName": "sampleTypeSpecUrl", "type": { - "$id": "950", + "$id": "991", "kind": "url", "name": "endpoint", "crossLanguageDefinitionId": "TypeSpec.url" @@ -15409,19 +16173,19 @@ "2024-08-16-preview" ], "parent": { - "$ref": "640" + "$ref": "664" }, "isMultiServiceClient": false }, { - "$id": "951", + "$id": "992", "kind": "client", "name": "PetOperations", "isExactName": false, "namespace": "SampleTypeSpec", "methods": [ { - "$id": "952", + "$id": "993", "kind": "basic", "name": "updatePetAsPet", "isExactName": false, @@ -15432,7 +16196,7 @@ ], "doc": "Update a pet as a pet", "operation": { - "$id": "953", + "$id": "994", "name": "updatePetAsPet", "isExactName": false, "resourceName": "PetOperations", @@ -15440,13 +16204,13 @@ "accessibility": "public", "parameters": [ { - "$id": "954", + "$id": "995", "kind": "header", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "216" + "$ref": "232" }, "isApiVersion": false, "optional": false, @@ -15457,13 +16221,13 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PetOperations.updatePetAsPet.contentType", "methodParameterSegments": [ { - "$id": "955", + "$id": "996", "kind": "method", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "216" + "$ref": "232" }, "location": "Header", "isApiVersion": false, @@ -15479,12 +16243,12 @@ "isExactName": false }, { - "$id": "956", + "$id": "997", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "218" + "$ref": "234" }, "isApiVersion": false, "optional": false, @@ -15495,12 +16259,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PetOperations.updatePetAsPet.accept", "methodParameterSegments": [ { - "$id": "957", + "$id": "998", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "218" + "$ref": "234" }, "location": "Header", "isApiVersion": false, @@ -15516,12 +16280,12 @@ "isExactName": false }, { - "$id": "958", + "$id": "999", "kind": "body", "name": "pet", "serializedName": "pet", "type": { - "$ref": "567" + "$ref": "591" }, "isApiVersion": false, "contentTypes": [ @@ -15535,12 +16299,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PetOperations.updatePetAsPet.pet", "methodParameterSegments": [ { - "$id": "959", + "$id": "1000", "kind": "method", "name": "pet", "serializedName": "pet", "type": { - "$ref": "567" + "$ref": "591" }, "location": "Body", "isApiVersion": false, @@ -15567,7 +16331,7 @@ 200 ], "bodyType": { - "$ref": "567" + "$ref": "591" }, "headers": [], "isErrorResponse": false, @@ -15596,18 +16360,18 @@ }, "parameters": [ { - "$ref": "959" + "$ref": "1000" }, { - "$ref": "955" + "$ref": "996" }, { - "$ref": "957" + "$ref": "998" } ], "response": { "type": { - "$ref": "567" + "$ref": "591" } }, "isOverride": false, @@ -15616,7 +16380,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PetOperations.updatePetAsPet" }, { - "$id": "960", + "$id": "1001", "kind": "basic", "name": "updateDogAsPet", "isExactName": false, @@ -15627,7 +16391,7 @@ ], "doc": "Update a dog as a pet", "operation": { - "$id": "961", + "$id": "1002", "name": "updateDogAsPet", "isExactName": false, "resourceName": "PetOperations", @@ -15635,13 +16399,13 @@ "accessibility": "public", "parameters": [ { - "$id": "962", + "$id": "1003", "kind": "header", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "220" + "$ref": "236" }, "isApiVersion": false, "optional": false, @@ -15652,13 +16416,13 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PetOperations.updateDogAsPet.contentType", "methodParameterSegments": [ { - "$id": "963", + "$id": "1004", "kind": "method", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "220" + "$ref": "236" }, "location": "Header", "isApiVersion": false, @@ -15674,12 +16438,12 @@ "isExactName": false }, { - "$id": "964", + "$id": "1005", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "222" + "$ref": "238" }, "isApiVersion": false, "optional": false, @@ -15690,12 +16454,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PetOperations.updateDogAsPet.accept", "methodParameterSegments": [ { - "$id": "965", + "$id": "1006", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "222" + "$ref": "238" }, "location": "Header", "isApiVersion": false, @@ -15711,12 +16475,12 @@ "isExactName": false }, { - "$id": "966", + "$id": "1007", "kind": "body", "name": "pet", "serializedName": "pet", "type": { - "$ref": "567" + "$ref": "591" }, "isApiVersion": false, "contentTypes": [ @@ -15730,12 +16494,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PetOperations.updateDogAsPet.pet", "methodParameterSegments": [ { - "$id": "967", + "$id": "1008", "kind": "method", "name": "pet", "serializedName": "pet", "type": { - "$ref": "567" + "$ref": "591" }, "location": "Body", "isApiVersion": false, @@ -15762,7 +16526,7 @@ 200 ], "bodyType": { - "$ref": "567" + "$ref": "591" }, "headers": [], "isErrorResponse": false, @@ -15791,18 +16555,18 @@ }, "parameters": [ { - "$ref": "967" + "$ref": "1008" }, { - "$ref": "963" + "$ref": "1004" }, { - "$ref": "965" + "$ref": "1006" } ], "response": { "type": { - "$ref": "567" + "$ref": "591" } }, "isOverride": false, @@ -15813,12 +16577,12 @@ ], "parameters": [ { - "$id": "968", + "$id": "1009", "kind": "endpoint", "name": "sampleTypeSpecUrl", "serializedName": "sampleTypeSpecUrl", "type": { - "$id": "969", + "$id": "1010", "kind": "url", "name": "endpoint", "crossLanguageDefinitionId": "TypeSpec.url" @@ -15842,19 +16606,19 @@ "2024-08-16-preview" ], "parent": { - "$ref": "640" + "$ref": "664" }, "isMultiServiceClient": false }, { - "$id": "970", + "$id": "1011", "kind": "client", "name": "DogOperations", "isExactName": false, "namespace": "SampleTypeSpec", "methods": [ { - "$id": "971", + "$id": "1012", "kind": "basic", "name": "updateDogAsDog", "isExactName": false, @@ -15865,7 +16629,7 @@ ], "doc": "Update a dog as a dog", "operation": { - "$id": "972", + "$id": "1013", "name": "updateDogAsDog", "isExactName": false, "resourceName": "DogOperations", @@ -15873,13 +16637,13 @@ "accessibility": "public", "parameters": [ { - "$id": "973", + "$id": "1014", "kind": "header", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "224" + "$ref": "240" }, "isApiVersion": false, "optional": false, @@ -15890,13 +16654,13 @@ "crossLanguageDefinitionId": "SampleTypeSpec.DogOperations.updateDogAsDog.contentType", "methodParameterSegments": [ { - "$id": "974", + "$id": "1015", "kind": "method", "name": "contentType", "serializedName": "Content-Type", "doc": "Body parameter's content type. Known values are application/json", "type": { - "$ref": "224" + "$ref": "240" }, "location": "Header", "isApiVersion": false, @@ -15912,12 +16676,12 @@ "isExactName": false }, { - "$id": "975", + "$id": "1016", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "226" + "$ref": "242" }, "isApiVersion": false, "optional": false, @@ -15928,12 +16692,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.DogOperations.updateDogAsDog.accept", "methodParameterSegments": [ { - "$id": "976", + "$id": "1017", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "226" + "$ref": "242" }, "location": "Header", "isApiVersion": false, @@ -15949,12 +16713,12 @@ "isExactName": false }, { - "$id": "977", + "$id": "1018", "kind": "body", "name": "dog", "serializedName": "dog", "type": { - "$ref": "571" + "$ref": "595" }, "isApiVersion": false, "contentTypes": [ @@ -15968,12 +16732,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.DogOperations.updateDogAsDog.dog", "methodParameterSegments": [ { - "$id": "978", + "$id": "1019", "kind": "method", "name": "dog", "serializedName": "dog", "type": { - "$ref": "571" + "$ref": "595" }, "location": "Body", "isApiVersion": false, @@ -16000,7 +16764,7 @@ 200 ], "bodyType": { - "$ref": "571" + "$ref": "595" }, "headers": [], "isErrorResponse": false, @@ -16029,18 +16793,18 @@ }, "parameters": [ { - "$ref": "978" + "$ref": "1019" }, { - "$ref": "974" + "$ref": "1015" }, { - "$ref": "976" + "$ref": "1017" } ], "response": { "type": { - "$ref": "571" + "$ref": "595" } }, "isOverride": false, @@ -16051,12 +16815,12 @@ ], "parameters": [ { - "$id": "979", + "$id": "1020", "kind": "endpoint", "name": "sampleTypeSpecUrl", "serializedName": "sampleTypeSpecUrl", "type": { - "$id": "980", + "$id": "1021", "kind": "url", "name": "endpoint", "crossLanguageDefinitionId": "TypeSpec.url" @@ -16080,19 +16844,19 @@ "2024-08-16-preview" ], "parent": { - "$ref": "640" + "$ref": "664" }, "isMultiServiceClient": false }, { - "$id": "981", + "$id": "1022", "kind": "client", "name": "PlantOperations", "isExactName": false, "namespace": "SampleTypeSpec", "methods": [ { - "$id": "982", + "$id": "1023", "kind": "basic", "name": "getTree", "isExactName": false, @@ -16103,7 +16867,7 @@ ], "doc": "Get a tree as a plant", "operation": { - "$id": "983", + "$id": "1024", "name": "getTree", "isExactName": false, "resourceName": "PlantOperations", @@ -16111,12 +16875,12 @@ "accessibility": "public", "parameters": [ { - "$id": "984", + "$id": "1025", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "228" + "$ref": "244" }, "isApiVersion": false, "optional": false, @@ -16127,12 +16891,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PlantOperations.getTree.accept", "methodParameterSegments": [ { - "$id": "985", + "$id": "1026", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "228" + "$ref": "244" }, "location": "Header", "isApiVersion": false, @@ -16154,14 +16918,14 @@ 200 ], "bodyType": { - "$ref": "575" + "$ref": "599" }, "headers": [ { "name": "contentType", "nameInResponse": "content-type", "type": { - "$ref": "230" + "$ref": "246" } } ], @@ -16188,12 +16952,12 @@ }, "parameters": [ { - "$ref": "985" + "$ref": "1026" } ], "response": { "type": { - "$ref": "575" + "$ref": "599" } }, "isOverride": false, @@ -16202,7 +16966,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PlantOperations.getTree" }, { - "$id": "986", + "$id": "1027", "kind": "basic", "name": "getTreeAsJson", "isExactName": false, @@ -16213,7 +16977,7 @@ ], "doc": "Get a tree as a plant", "operation": { - "$id": "987", + "$id": "1028", "name": "getTreeAsJson", "isExactName": false, "resourceName": "PlantOperations", @@ -16221,12 +16985,12 @@ "accessibility": "public", "parameters": [ { - "$id": "988", + "$id": "1029", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "232" + "$ref": "248" }, "isApiVersion": false, "optional": false, @@ -16237,12 +17001,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PlantOperations.getTreeAsJson.accept", "methodParameterSegments": [ { - "$id": "989", + "$id": "1030", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "232" + "$ref": "248" }, "location": "Header", "isApiVersion": false, @@ -16264,14 +17028,14 @@ 200 ], "bodyType": { - "$ref": "575" + "$ref": "599" }, "headers": [ { "name": "contentType", "nameInResponse": "content-type", "type": { - "$ref": "234" + "$ref": "250" } } ], @@ -16298,12 +17062,12 @@ }, "parameters": [ { - "$ref": "989" + "$ref": "1030" } ], "response": { "type": { - "$ref": "575" + "$ref": "599" } }, "isOverride": false, @@ -16312,7 +17076,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PlantOperations.getTreeAsJson" }, { - "$id": "990", + "$id": "1031", "kind": "basic", "name": "updateTree", "isExactName": false, @@ -16323,7 +17087,7 @@ ], "doc": "Update a tree as a plant", "operation": { - "$id": "991", + "$id": "1032", "name": "updateTree", "isExactName": false, "resourceName": "PlantOperations", @@ -16331,12 +17095,12 @@ "accessibility": "public", "parameters": [ { - "$id": "992", + "$id": "1033", "kind": "header", "name": "contentType", "serializedName": "Content-Type", "type": { - "$ref": "236" + "$ref": "252" }, "isApiVersion": false, "optional": false, @@ -16347,12 +17111,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PlantOperations.updateTree.contentType", "methodParameterSegments": [ { - "$id": "993", + "$id": "1034", "kind": "method", "name": "contentType", "serializedName": "Content-Type", "type": { - "$ref": "236" + "$ref": "252" }, "location": "Header", "isApiVersion": false, @@ -16368,12 +17132,12 @@ "isExactName": false }, { - "$id": "994", + "$id": "1035", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "240" + "$ref": "256" }, "isApiVersion": false, "optional": false, @@ -16384,12 +17148,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PlantOperations.updateTree.accept", "methodParameterSegments": [ { - "$id": "995", + "$id": "1036", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "240" + "$ref": "256" }, "location": "Header", "isApiVersion": false, @@ -16405,12 +17169,12 @@ "isExactName": false }, { - "$id": "996", + "$id": "1037", "kind": "body", "name": "tree", "serializedName": "tree", "type": { - "$ref": "575" + "$ref": "599" }, "isApiVersion": false, "contentTypes": [ @@ -16424,12 +17188,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PlantOperations.updateTree.tree", "methodParameterSegments": [ { - "$id": "997", + "$id": "1038", "kind": "method", "name": "tree", "serializedName": "tree", "type": { - "$ref": "575" + "$ref": "599" }, "location": "Body", "isApiVersion": false, @@ -16456,14 +17220,14 @@ 200 ], "bodyType": { - "$ref": "575" + "$ref": "599" }, "headers": [ { "name": "contentType", "nameInResponse": "content-type", "type": { - "$ref": "242" + "$ref": "258" } } ], @@ -16493,18 +17257,18 @@ }, "parameters": [ { - "$ref": "997" + "$ref": "1038" }, { - "$ref": "993" + "$ref": "1034" }, { - "$ref": "995" + "$ref": "1036" } ], "response": { "type": { - "$ref": "575" + "$ref": "599" } }, "isOverride": false, @@ -16513,7 +17277,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PlantOperations.updateTree" }, { - "$id": "998", + "$id": "1039", "kind": "basic", "name": "updateTreeAsJson", "isExactName": false, @@ -16524,7 +17288,7 @@ ], "doc": "Update a tree as a plant", "operation": { - "$id": "999", + "$id": "1040", "name": "updateTreeAsJson", "isExactName": false, "resourceName": "PlantOperations", @@ -16532,12 +17296,12 @@ "accessibility": "public", "parameters": [ { - "$id": "1000", + "$id": "1041", "kind": "header", "name": "contentType", "serializedName": "Content-Type", "type": { - "$ref": "244" + "$ref": "260" }, "isApiVersion": false, "optional": false, @@ -16548,12 +17312,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PlantOperations.updateTreeAsJson.contentType", "methodParameterSegments": [ { - "$id": "1001", + "$id": "1042", "kind": "method", "name": "contentType", "serializedName": "Content-Type", "type": { - "$ref": "244" + "$ref": "260" }, "location": "Header", "isApiVersion": false, @@ -16569,12 +17333,12 @@ "isExactName": false }, { - "$id": "1002", + "$id": "1043", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "248" + "$ref": "264" }, "isApiVersion": false, "optional": false, @@ -16585,12 +17349,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PlantOperations.updateTreeAsJson.accept", "methodParameterSegments": [ { - "$id": "1003", + "$id": "1044", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "248" + "$ref": "264" }, "location": "Header", "isApiVersion": false, @@ -16606,12 +17370,12 @@ "isExactName": false }, { - "$id": "1004", + "$id": "1045", "kind": "body", "name": "tree", "serializedName": "tree", "type": { - "$ref": "575" + "$ref": "599" }, "isApiVersion": false, "contentTypes": [ @@ -16625,12 +17389,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PlantOperations.updateTreeAsJson.tree", "methodParameterSegments": [ { - "$id": "1005", + "$id": "1046", "kind": "method", "name": "tree", "serializedName": "tree", "type": { - "$ref": "575" + "$ref": "599" }, "location": "Body", "isApiVersion": false, @@ -16657,14 +17421,14 @@ 200 ], "bodyType": { - "$ref": "575" + "$ref": "599" }, "headers": [ { "name": "contentType", "nameInResponse": "content-type", "type": { - "$ref": "250" + "$ref": "266" } } ], @@ -16694,18 +17458,18 @@ }, "parameters": [ { - "$ref": "1005" + "$ref": "1046" }, { - "$ref": "1001" + "$ref": "1042" }, { - "$ref": "1003" + "$ref": "1044" } ], "response": { "type": { - "$ref": "575" + "$ref": "599" } }, "isOverride": false, @@ -16716,12 +17480,12 @@ ], "parameters": [ { - "$id": "1006", + "$id": "1047", "kind": "endpoint", "name": "sampleTypeSpecUrl", "serializedName": "sampleTypeSpecUrl", "type": { - "$id": "1007", + "$id": "1048", "kind": "url", "name": "endpoint", "crossLanguageDefinitionId": "TypeSpec.url" @@ -16745,19 +17509,19 @@ "2024-08-16-preview" ], "parent": { - "$ref": "640" + "$ref": "664" }, "isMultiServiceClient": false }, { - "$id": "1008", + "$id": "1049", "kind": "client", "name": "Metrics", "isExactName": false, "namespace": "SampleTypeSpec", "methods": [ { - "$id": "1009", + "$id": "1050", "kind": "basic", "name": "getWidgetMetrics", "isExactName": false, @@ -16768,7 +17532,7 @@ ], "doc": "Get Widget metrics for given day of week", "operation": { - "$id": "1010", + "$id": "1051", "name": "getWidgetMetrics", "isExactName": false, "resourceName": "Metrics", @@ -16776,12 +17540,12 @@ "accessibility": "public", "parameters": [ { - "$id": "1011", + "$id": "1052", "kind": "path", "name": "metricsNamespace", "serializedName": "metricsNamespace", "type": { - "$id": "1012", + "$id": "1053", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -16799,12 +17563,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.Metrics.getWidgetMetrics.metricsNamespace", "methodParameterSegments": [ { - "$id": "1013", + "$id": "1054", "kind": "method", "name": "metricsNamespace", "serializedName": "metricsNamespace", "type": { - "$id": "1014", + "$id": "1055", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -16824,12 +17588,12 @@ "isExactName": false }, { - "$id": "1015", + "$id": "1056", "kind": "path", "name": "day", "serializedName": "day", "type": { - "$ref": "57" + "$ref": "65" }, "isApiVersion": false, "explode": false, @@ -16843,12 +17607,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.Metrics.getWidgetMetrics.day", "methodParameterSegments": [ { - "$id": "1016", + "$id": "1057", "kind": "method", "name": "day", "serializedName": "day", "type": { - "$ref": "57" + "$ref": "65" }, "location": "Path", "isApiVersion": false, @@ -16864,12 +17628,12 @@ "isExactName": false }, { - "$id": "1017", + "$id": "1058", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "252" + "$ref": "268" }, "isApiVersion": false, "optional": false, @@ -16880,12 +17644,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.Metrics.getWidgetMetrics.accept", "methodParameterSegments": [ { - "$id": "1018", + "$id": "1059", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "252" + "$ref": "268" }, "location": "Header", "isApiVersion": false, @@ -16907,7 +17671,7 @@ 200 ], "bodyType": { - "$ref": "586" + "$ref": "610" }, "headers": [], "isErrorResponse": false, @@ -16933,15 +17697,15 @@ }, "parameters": [ { - "$ref": "1016" + "$ref": "1057" }, { - "$ref": "1018" + "$ref": "1059" } ], "response": { "type": { - "$ref": "586" + "$ref": "610" } }, "isOverride": false, @@ -16952,12 +17716,12 @@ ], "parameters": [ { - "$id": "1019", + "$id": "1060", "kind": "endpoint", "name": "sampleTypeSpecUrl", "serializedName": "sampleTypeSpecUrl", "type": { - "$id": "1020", + "$id": "1061", "kind": "url", "name": "endpoint", "crossLanguageDefinitionId": "TypeSpec.url" @@ -16973,7 +17737,7 @@ "isExactName": false }, { - "$ref": "1013" + "$ref": "1054" } ], "initializedBy": 3, @@ -16984,19 +17748,19 @@ "2024-08-16-preview" ], "parent": { - "$ref": "640" + "$ref": "664" }, "isMultiServiceClient": false }, { - "$id": "1021", + "$id": "1062", "kind": "client", "name": "Notebooks", "isExactName": false, "namespace": "SampleTypeSpec", "methods": [ { - "$id": "1022", + "$id": "1063", "kind": "basic", "name": "getNotebook", "isExactName": false, @@ -17007,7 +17771,7 @@ ], "doc": "Get a notebook by name", "operation": { - "$id": "1023", + "$id": "1064", "name": "getNotebook", "isExactName": false, "resourceName": "Notebooks", @@ -17015,12 +17779,12 @@ "accessibility": "public", "parameters": [ { - "$id": "1024", + "$id": "1065", "kind": "path", "name": "notebookName", "serializedName": "notebookName", "type": { - "$id": "1025", + "$id": "1066", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -17038,12 +17802,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.Notebooks.getNotebook.notebookName", "methodParameterSegments": [ { - "$id": "1026", + "$id": "1067", "kind": "method", "name": "notebook", "serializedName": "notebook", "type": { - "$id": "1027", + "$id": "1068", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -17064,12 +17828,12 @@ "isExactName": false }, { - "$id": "1028", + "$id": "1069", "kind": "header", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "254" + "$ref": "270" }, "isApiVersion": false, "optional": false, @@ -17080,12 +17844,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.Notebooks.getNotebook.accept", "methodParameterSegments": [ { - "$id": "1029", + "$id": "1070", "kind": "method", "name": "accept", "serializedName": "Accept", "type": { - "$ref": "254" + "$ref": "270" }, "location": "Header", "isApiVersion": false, @@ -17107,7 +17871,7 @@ 200 ], "bodyType": { - "$ref": "591" + "$ref": "615" }, "headers": [], "isErrorResponse": false, @@ -17133,12 +17897,12 @@ }, "parameters": [ { - "$ref": "1029" + "$ref": "1070" } ], "response": { "type": { - "$ref": "591" + "$ref": "615" } }, "isOverride": false, @@ -17149,12 +17913,12 @@ ], "parameters": [ { - "$id": "1030", + "$id": "1071", "kind": "endpoint", "name": "sampleTypeSpecUrl", "serializedName": "sampleTypeSpecUrl", "type": { - "$id": "1031", + "$id": "1072", "kind": "url", "name": "endpoint", "crossLanguageDefinitionId": "TypeSpec.url" @@ -17170,7 +17934,7 @@ "isExactName": false }, { - "$ref": "1026" + "$ref": "1067" } ], "initializedBy": 3, @@ -17181,7 +17945,7 @@ "2024-08-16-preview" ], "parent": { - "$ref": "640" + "$ref": "664" }, "isMultiServiceClient": false } diff --git a/packages/http-client-csharp/readme.md b/packages/http-client-csharp/readme.md index 80db5263884..614ac522578 100644 --- a/packages/http-client-csharp/readme.md +++ b/packages/http-client-csharp/readme.md @@ -19,10 +19,10 @@ npm install @typespec/http-client-csharp For detailed instructions on how to customize the generated C# code, see the [Customization Guide](https://github.com/microsoft/typespec/blob/main/packages/http-client-csharp/.tspd/docs/customization.md). -### Experimental operations +### Experimental types and members -Use `TypeSpec.HttpClient.@experimental` to assign a public diagnostic ID to an operation and -identify experiments used by its implementation: +Use `TypeSpec.HttpClient.@experimental` to assign a public diagnostic ID to a generated +type or member and identify experiments used by its implementation: ```typespec import "@typespec/http-client"; @@ -35,23 +35,54 @@ import "@typespec/http-client"; op bar(): void; ``` -The C# emitter adds `[Experimental("C")]` to the generated synchronous and asynchronous -protocol and convenience methods. It disables dependency diagnostics `A` and `B` before -each generated method or request-helper declaration and restores them after that method. -This covers experimental types in parameter and return types, including generic arguments, -as well as references in the implementation. These suppressions do not affect neighboring -members or callers. +The C# emitter adds `[Experimental("C")]` to the corresponding generated declaration: + +| TypeSpec target | Generated C# target | +| ----------------------------------------------------- | ------------------------------------------------------------- | +| Model | Model class or struct | +| Model property | Property | +| Enum or union emitted as an enum | Enum or extensible-enum struct | +| Enum member or union variant emitted as an enum value | Enum field or extensible-enum property | +| Operation | Synchronous and asynchronous protocol and convenience methods | +| Namespace or interface emitted as a client | Client class | + +For example, models and their properties can be separate experiments: + +```typespec +@TypeSpec.HttpClient.experimental(#{ diagnosticId: "MODEL001" }) +model Preview { + @TypeSpec.HttpClient.experimental(#{ diagnosticId: "PROPERTY001" }) + value?: string; +} +``` + +Model factory methods expose the model's diagnostic. Partial serialization declarations +do not repeat the model attribute. Generated code suppresses diagnostics when referring +to source-annotated experimental types and members, so serialization, factories, and client +implementations can compile without changing the experimental status of other public APIs. + +For operation dependencies, suppressions surround each method declaration and body. +For type/member dependencies and generated references to experimental declarations, +suppressions are scoped to the generated file. Both include parameter and return types, +generic arguments, and implementation references; neither suppresses diagnostics in +consumer code. Dependencies identify diagnostics rather than individual types: one diagnostic can apply -to multiple types or members, including those defined in external libraries. +to multiple types or members, including those defined in external libraries. External +experiments still require explicit `dependsOn` entries; they are not discovered by reflection. Both metadata fields are optional. Without `diagnosticId`, no public experimental attribute -is added; without `dependsOn`, no dependency suppressions are added. Emitter scopes apply to -both fields. C# currently consumes this metadata on operations, not on models or properties. +is added. Without `dependsOn`, no additional dependency diagnostics are requested; generated +references to source-annotated experiments are still handled. Emitter scopes apply to both fields. + +Some TypeSpec declarations have no corresponding C# declaration, such as scalars or unions +mapped to built-in C# types. C# also does not allow `ExperimentalAttribute` on parameters. +The emitter reports `experimental-target-not-supported` for these annotations instead of +silently dropping them or assigning their diagnostic to an unrelated API. Graduation is an explicit source change: dependencies becoming generally available, or removing entries from `dependsOn`, does not remove `[Experimental("C")]`. Remove the -operation's `@experimental` decorator when the public API is ready to graduate. +declaration's `@experimental` decorator when the public API is ready to graduate. ## Emitter usage From 489130e4d1234e75549610a1fff0729f34748892 Mon Sep 17 00:00:00 2001 From: jolov Date: Tue, 22 Sep 2026 19:02:24 -0700 Subject: [PATCH 5/8] fix(http-client-csharp): address experimental metadata review feedback Preserve custom experimental attributes, validate pragma-safe diagnostic IDs, cover client options/settings references, and add compiler-backed production and multipart regressions. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../http-client-csharp/.tspd/docs/usage.md | 6 + .../emitter/src/lib/experimental.ts | 21 +++ .../http-client-csharp/emitter/src/lib/lib.ts | 6 + .../emitter/test/Unit/decorator-list.test.ts | 49 +++++++ .../src/Providers/ClientOptionsProvider.cs | 3 + .../src/Providers/ClientProvider.cs | 4 +- .../src/Providers/ClientSettingsProvider.cs | 4 + .../Providers/ScmMethodProviderCollection.cs | 27 ++-- .../ClientProviders/ClientProviderTests.cs | 14 ++ .../TestClient.cs | 12 ++ .../Providers/ClientSettingsProviderTests.cs | 35 +++++ .../ScmModelFactoryProviderTests.cs | 41 ++++++ .../ScmMethodProviderCollectionTests.cs | 137 +++++++++++++++++- .../TestClient.cs | 23 +++ .../Choice.cs | 26 ++++ .../src/Providers/NamedTypeSymbolProvider.cs | 4 + .../src/Utilities/ExperimentalApiHelpers.cs | 16 +- packages/http-client-csharp/readme.md | 6 + 18 files changed, 420 insertions(+), 14 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/ExperimentalCustomClientKeepsExistingAttribute/TestClient.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ExperimentalCustomizedPartialMethodsKeepExistingAttributes/TestClient.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ExperimentalProductionMethodsCompile/Choice.cs diff --git a/packages/http-client-csharp/.tspd/docs/usage.md b/packages/http-client-csharp/.tspd/docs/usage.md index 5a598048e1e..14315f54cfa 100644 --- a/packages/http-client-csharp/.tspd/docs/usage.md +++ b/packages/http-client-csharp/.tspd/docs/usage.md @@ -63,6 +63,12 @@ Both metadata fields are optional. Without `diagnosticId`, no public experimenta is added. Without `dependsOn`, no additional dependency diagnostics are requested; generated references to source-annotated experiments are still handled. Emitter scopes apply to both fields. +Diagnostic IDs must be single C# warning identifiers (ASCII letters, digits, and underscores, +not starting with a digit) or decimal warning numbers. Whitespace, punctuation, comments, and +line breaks are rejected with `invalid-experimental-diagnostic-id` before generating C#. +An `ExperimentalAttribute` on a customized partial client or method takes precedence over +the generated attribute. + Some TypeSpec declarations have no corresponding C# declaration, such as scalars or unions mapped to built-in C# types. C# also does not allow `ExperimentalAttribute` on parameters. The emitter reports `experimental-target-not-supported` for these annotations instead of diff --git a/packages/http-client-csharp/emitter/src/lib/experimental.ts b/packages/http-client-csharp/emitter/src/lib/experimental.ts index 5bf6d60d572..3db88c6c1cb 100644 --- a/packages/http-client-csharp/emitter/src/lib/experimental.ts +++ b/packages/http-client-csharp/emitter/src/lib/experimental.ts @@ -29,6 +29,27 @@ export function getExperimentalDetails( return diagnostics.wrap(undefined); } + if (lifecycle) { + const ids = [ + ...(lifecycle.diagnosticId === undefined ? [] : [lifecycle.diagnosticId]), + ...lifecycle.dependsOn, + ]; + for (const id of new Set(ids)) { + if (id.match(/^(?:[A-Za-z_][A-Za-z0-9_]*|[0-9]+)/u)?.[0] !== id) { + diagnostics.add( + createDiagnostic({ + code: "invalid-experimental-diagnostic-id", + target, + format: { diagnosticId: JSON.stringify(id) }, + }), + ); + } + } + if (diagnostics.diagnostics.length > 0) { + return diagnostics.wrap(undefined); + } + } + return diagnostics.wrap( lifecycle ? { diagnosticId: lifecycle.diagnosticId, dependsOn: [...lifecycle.dependsOn] } diff --git a/packages/http-client-csharp/emitter/src/lib/lib.ts b/packages/http-client-csharp/emitter/src/lib/lib.ts index 38161428c87..1f6a052ce64 100644 --- a/packages/http-client-csharp/emitter/src/lib/lib.ts +++ b/packages/http-client-csharp/emitter/src/lib/lib.ts @@ -10,6 +10,12 @@ export type DiagnosticMessagesMap = { }; const diags: { [code: string]: DiagnosticDefinition } = { + "invalid-experimental-diagnostic-id": { + severity: "error", + messages: { + default: paramMessage`Experimental diagnostic ID ${"diagnosticId"} must be a single C# warning identifier (ASCII letters, digits, or underscores, not starting with a digit) or a decimal warning number.`, + }, + }, "experimental-target-not-supported": { severity: "error", messages: { diff --git a/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts b/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts index f37e3edd037..a7ea5fd75dc 100644 --- a/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts @@ -29,6 +29,55 @@ describe("Test emitting decorator list", () => { await runner.addTypeSpecLibrary(HttpClientTestLibrary); }); + describe.each(["diagnosticId", "dependsOn"])("validating %s", (field) => { + it.each([ + "", + " ", + "A B", + "A,B", + "A-B", + "A.B", + "1A", + "@A", + "A\n", + "A\r", + "A\tB", + "A//B", + "A/*B*/", + "A\n#pragma warning disable", + ])("rejects invalid ID %j", async (id) => { + const value = field === "dependsOn" ? `#[${JSON.stringify(id)}]` : JSON.stringify(id); + const program = await typeSpecCompile( + `@TypeSpec.HttpClient.experimental(#{ ${field}: ${value} }) op read(): void;`, + runner, + { IsHttpClientNeeded: true }, + ); + const sdkContext = await createCSharpSdkContext(createEmitterContext(program)); + const [, diagnostics] = createModel(sdkContext); + expectDiagnostics(diagnostics, { + code: "@typespec/http-client-csharp/invalid-experimental-diagnostic-id", + }); + }); + }); + + it.each(["A", "DEP001", "_DEP001", "CS0618", "0618", "class"])( + "accepts pragma identifier %s", + async (id) => { + const program = await typeSpecCompile( + `@TypeSpec.HttpClient.experimental(#{ diagnosticId: "${id}", dependsOn: #["${id}"] }) op read(): void;`, + runner, + { IsHttpClientNeeded: true }, + ); + const sdkContext = await createCSharpSdkContext(createEmitterContext(program)); + const [root, diagnostics] = createModel(sdkContext); + expectDiagnosticEmpty(diagnostics); + deepStrictEqual(root.clients[0].methods[0].operation.experimental, { + diagnosticId: id, + dependsOn: [id], + }); + }, + ); + const declarations = [ { name: "model", diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientOptionsProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientOptionsProvider.cs index c62e74461fd..7ef2036ae6d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientOptionsProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientOptionsProvider.cs @@ -26,6 +26,9 @@ public class ClientOptionsProvider : TypeProvider private const string LatestVersionFieldName = $"{LatestPrefix}{VersionSuffix}"; private readonly InputClient _inputClient; + protected override SuppressionStatement[] BuildDisabledFileWarnings() + => ExperimentalApiHelpers.GetParameterSuppressions(_inputClient.Parameters); + private readonly ClientProvider _clientProvider; private readonly Lazy?> _serviceVersionsEnums; private static ClientOptionsProvider? _singletonInstance; 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 57fe8fb2cd0..04b7dfa40c7 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 @@ -47,7 +47,9 @@ private record ApiVersionFields(FieldProvider Field, PropertyProvider? Correspon private readonly InputClient _inputClient; internal InputClient InputClient => _inputClient; protected override IReadOnlyList BuildAttributes() - => ExperimentalApiHelpers.BuildAttributes(_inputClient.Experimental); + => CustomCodeView?.Attributes.Any(a => a.Type.Equals(typeof(ExperimentalAttribute))) == true + ? [] + : ExperimentalApiHelpers.BuildAttributes(_inputClient.Experimental); protected override SuppressionStatement[] BuildDisabledFileWarnings() => ExperimentalApiHelpers.GetSuppressions(_inputClient); 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 ed69046abb1..faa4a3615c3 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 @@ -16,6 +16,7 @@ using Microsoft.TypeSpec.Generator.Snippets; using Microsoft.TypeSpec.Generator.Statements; using Microsoft.TypeSpec.Generator.ClientModel.Utilities; +using Microsoft.TypeSpec.Generator.Utilities; using static Microsoft.TypeSpec.Generator.Snippets.Snippet; namespace Microsoft.TypeSpec.Generator.ClientModel.Providers @@ -25,6 +26,9 @@ public class ClientSettingsProvider : TypeProvider internal const string ClientSettingsDiagnosticId = "SCME0002"; private readonly ClientProvider _clientProvider; + protected override SuppressionStatement[] BuildDisabledFileWarnings() + => ExperimentalApiHelpers.GetParameterSuppressions(_clientProvider.InputClient.Parameters); + private readonly HashSet _reportedUnsupportedCustomParameters = []; #pragma warning disable SCME0002 // ClientSettings is for evaluation purposes only diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs index 89df02d287c..fb875eb85e0 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs @@ -218,11 +218,7 @@ private ScmMethodProvider BuildConvenienceMethod(MethodProvider protocolMethod, signatureParameters); } - var convenienceAttributes = BuildConvenienceMethodAttributes(); - if (convenienceAttributes is not null) - { - methodSignature.Update(attributes: [.. methodSignature.Attributes, .. convenienceAttributes]); - } + ApplyExperimentalAttributes(methodSignature, customSignature, BuildConvenienceMethodAttributes() ?? []); // Recompute the response body type so we can branch the body accordingly. GetResponseType(ServiceMethod.Operation.Responses, true, isAsync, out var responseBodyType); @@ -1142,6 +1138,22 @@ private static bool IsConvertibleFromBinaryData(CSharpType type) return null; } + private static void ApplyExperimentalAttributes( + MethodSignature signature, + MethodSignature? customSignature, + IReadOnlyList generatedAttributes) + { + if (customSignature?.Attributes.Any(a => a.Type.Equals(typeof(ExperimentalAttribute))) == true) + { + // The defining partial declaration already carries the custom experiment. + signature.Update(attributes: [.. signature.Attributes.Where(a => !a.Type.Equals(typeof(ExperimentalAttribute)))]); + } + else if (generatedAttributes.Count > 0) + { + signature.Update(attributes: [.. signature.Attributes, .. generatedAttributes]); + } + } + private IReadOnlyList GetProtocolMethodArguments(Dictionary declarations) { List conversions = new List(); @@ -1452,10 +1464,7 @@ private ScmMethodProvider BuildProtocolMethod(MethodProvider createRequestMethod bodyParameters = parameters; } - if (ExperimentalApiHelpers.BuildAttribute(ServiceMethod.Operation) is { } experimentalAttribute) - { - methodSignature.Update(attributes: [.. methodSignature.Attributes, experimentalAttribute]); - } + ApplyExperimentalAttributes(methodSignature, customSignature, ExperimentalApiHelpers.BuildAttributes(ServiceMethod.Operation.Experimental)); TypeProvider? collection = null; MethodBodyStatement[] methodBody; 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 c30a12aa1cc..6cc7ead0b38 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 @@ -26,6 +26,20 @@ namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.ClientProvide { public class ClientProviderTests { + [Test] + public async Task ExperimentalCustomClientKeepsExistingAttribute() + { + var input = InputFactory.Experimental(InputFactory.Client("TestClient"), "GENERATED001"); + await MockHelpers.LoadMockGeneratorAsync( + clients: () => [input], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(input)!; + + Assert.AreEqual(0, client.Attributes.Count(a => a.Type.Equals(typeof(ExperimentalAttribute)))); + Assert.AreEqual(Literal("CUSTOM001").ToDisplayString(), + client.CanonicalView.Attributes.Single(a => a.Type.Equals(typeof(ExperimentalAttribute))).Arguments[0].ToDisplayString()); + } + [Test] public void ExperimentalClientAndModelReferences() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/ExperimentalCustomClientKeepsExistingAttribute/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/ExperimentalCustomClientKeepsExistingAttribute/TestClient.cs new file mode 100644 index 00000000000..967d62aa4a5 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/ExperimentalCustomClientKeepsExistingAttribute/TestClient.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; + +namespace Sample +{ + [Experimental("CUSTOM001")] + public partial class 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 b9655612614..5d639611527 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 @@ -7,11 +7,14 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.TypeSpec.Generator.EmitterRpc; using Microsoft.TypeSpec.Generator.ClientModel.Providers; using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Primitives; using Microsoft.TypeSpec.Generator.Providers; +using Microsoft.TypeSpec.Generator.Snippets; using Microsoft.TypeSpec.Generator.Statements; using Microsoft.TypeSpec.Generator.Tests.Common; using NUnit.Framework; @@ -20,6 +23,38 @@ namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers { public class ClientSettingsProviderTests { + [Test] + public void ExperimentalClientParametersCompileInOptionsAndSettings() + { + var mode = InputFactory.Experimental(InputFactory.StringEnum("Mode", [("One", "one")], isExtensible: true), "MODE001"); + var input = InputFactory.Client("TestClient", parameters: + [ + InputFactory.MethodParameter("requiredMode", mode, isRequired: true, scope: InputParameterScope.Client), + InputFactory.MethodParameter("optionalMode", mode, defaultValue: new InputConstant("one", mode), scope: InputParameterScope.Client) + ]); + MockHelpers.LoadMockGenerator(inputEnums: () => [mode], clients: () => [input]); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(input)!; + var enumProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateEnum(mode)!; + var options = client.ClientOptions!; + var settings = client.ClientSettings!; + var references = AppDomain.CurrentDomain.GetAssemblies() + .Where(a => !a.IsDynamic && !string.IsNullOrEmpty(a.Location)) + .Select(a => MetadataReference.CreateFromFile(a.Location)) + .Append(MetadataReference.CreateFromFile(typeof(Microsoft.Extensions.Configuration.IConfigurationSection).Assembly.Location)); + var compilation = CSharpCompilation.Create( + "ExperimentalClientParameters", + new TypeProvider[] { enumProvider, options, settings, new ArgumentDefinition() } + .Select(p => CSharpSyntaxTree.ParseText(new TypeProviderWriter(p).Write().Content)), + references, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, generalDiagnosticOption: ReportDiagnostic.Error)); + var errors = compilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).ToArray(); + Assert.IsEmpty(errors.Select(d => d.ToString())); + foreach (var provider in new TypeProvider[] { options, settings }) + { + Assert.IsTrue(provider.DisabledFileWarnings.Any(s => s.Code.ToDisplayString() == Snippet.Literal("MODE001").ToDisplayString())); + } + } + [SetUp] public void SetUp() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ModelFactoryProviders/ScmModelFactoryProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ModelFactoryProviders/ScmModelFactoryProviderTests.cs index cac43a50c53..439b3ca5bc4 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ModelFactoryProviders/ScmModelFactoryProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ModelFactoryProviders/ScmModelFactoryProviderTests.cs @@ -3,12 +3,16 @@ #pragma warning disable SCME0004 // FileBinaryContent is evaluation-only. +using System; using System.ClientModel; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Primitives; +using Microsoft.TypeSpec.Generator.Providers; using Microsoft.TypeSpec.Generator.Snippets; using Microsoft.TypeSpec.Generator.Tests.Common; using NUnit.Framework; @@ -17,6 +21,43 @@ namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.ModelFactoryP { public class ScmModelFactoryProviderTests { + [TestCase(true, false)] + [TestCase(false, true)] + [TestCase(true, true)] + public void SourceExperimentalMultipartDeclarationsCompile(bool experimentalModel, bool experimentalProperty) + { + var property = FilePartProperty("file"); + if (experimentalProperty) + { + InputFactory.Experimental(property, "PROPERTY001"); + } + var input = MultipartModel("Payload", [property]); + if (experimentalModel) + { + InputFactory.Experimental(input, "MODEL001"); + } + MockHelpers.LoadMockGenerator(inputModels: () => [input]); + var model = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(input)!; + var factory = ScmCodeModelGenerator.Instance.TypeFactory.CreateModelFactory([input]); + var references = AppDomain.CurrentDomain.GetAssemblies() + .Where(a => !a.IsDynamic && !string.IsNullOrEmpty(a.Location)) + .Select(a => MetadataReference.CreateFromFile(a.Location)); + var compilation = CSharpCompilation.Create( + "ExperimentalMultipart", + [CSharpSyntaxTree.ParseText(new TypeProviderWriter(model).Write().Content), + CSharpSyntaxTree.ParseText(new TypeProviderWriter(factory).Write().Content), + CSharpSyntaxTree.ParseText(new TypeProviderWriter(new ArgumentDefinition()).Write().Content)], + references, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, generalDiagnosticOption: ReportDiagnostic.Error)); + + var errors = compilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).ToArray(); + Assert.IsEmpty(errors.Select(d => d.ToString())); + Assert.AreEqual(Snippet.Literal(experimentalModel ? "MODEL001" : "SCME0004").ToDisplayString(), + factory.Methods.Single().Signature.Attributes.Single(a => a.Type.Equals(typeof(ExperimentalAttribute))).Arguments[0].ToDisplayString()); + Assert.AreEqual(Snippet.Literal(experimentalProperty ? "PROPERTY001" : "SCME0004").ToDisplayString(), + model.Properties.Single(p => p.Name == "File").Attributes.Single(a => a.Type.Equals(typeof(ExperimentalAttribute))).Arguments[0].ToDisplayString()); + } + [Test] public void ExperimentalModelFactoryUsesThePublicModelDiagnostic() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs index 9932efedb05..a9540a0f394 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs @@ -93,7 +93,18 @@ public void ExperimentalOperationGeneratesScopedDiagnostics(string? diagnosticId [TestCase("")] [TestCase(" \t")] - public void ExperimentalOperationRejectsEmptyDependencyDiagnostic(string diagnosticId) + [TestCase("A B")] + [TestCase("A,B")] + [TestCase("A-B")] + [TestCase("A.B")] + [TestCase("1A")] + [TestCase("@A")] + [TestCase("A\n")] + [TestCase("A\r")] + [TestCase("A//B")] + [TestCase("A/*B*/")] + [TestCase("A\n#pragma warning disable")] + public void ExperimentalOperationRejectsInvalidDependencyDiagnostic(string diagnosticId) { var operation = InputFactory.Operation("Bar"); operation.Update(experimental: new InputExperimentalDetails("C", [diagnosticId])); @@ -105,6 +116,29 @@ public void ExperimentalOperationRejectsEmptyDependencyDiagnostic(string diagnos Assert.Throws(() => new ScmMethodProviderCollection(serviceMethod, client)); } + [TestCase("A")] + [TestCase("DEP001")] + [TestCase("_DEP001")] + [TestCase("CS0618")] + [TestCase("0618")] + [TestCase("class")] + public void ExperimentalDiagnosticIdProducesOnePragmaToken(string diagnosticId) + { + var operation = InputFactory.Operation("Read"); + operation.Update(experimental: new InputExperimentalDetails("API001", [diagnosticId])); + var method = InputFactory.BasicServiceMethod("Read", operation); + var inputClient = InputFactory.Client("TestClient", methods: [method]); + MockHelpers.LoadMockGenerator(clients: () => [inputClient]); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + var generatedMethod = new ScmMethodProviderCollection(method, client)[0]; + var tree = CSharpSyntaxTree.ParseText(generatedMethod.Suppressions.Single().DisableStatement.ToDisplayString()); + + Assert.AreEqual(0, tree.GetDiagnostics().Count()); + var pragma = tree.GetRoot().DescendantTrivia(descendIntoTrivia: true).Select(t => t.GetStructure()) + .OfType().Single(); + Assert.AreEqual(diagnosticId, pragma.ErrorCodes.Single().ToString()); + } + [Test] public void ExperimentalDependenciesAlreadySuppressedForClientAreNotRestoredByMethods() { @@ -246,6 +280,107 @@ public void ExperimentalDependenciesCoverSignaturesAndBodies(string shape, bool outsideDiagnostics.Select(d => d.Id).Distinct()); } + [TestCase(null)] + [TestCase("GENERATED001")] + public async Task ExperimentalCustomizedPartialMethodsKeepExistingAttributes(string? diagnosticId) + { + var operation = InputFactory.Operation("Bar"); + operation.Update(experimental: new InputExperimentalDetails(diagnosticId, [])); + var serviceMethod = InputFactory.BasicServiceMethod("Bar", operation); + var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]); + MockHelpers.LoadMockGenerator(); + var customCompilation = await Helpers.GetCompilationFromDirectoryAsync(); + await MockHelpers.LoadMockGeneratorAsync(clients: () => [inputClient], compilation: () => Task.FromResult(customCompilation)); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + var methods = new ScmMethodProviderCollection(serviceMethod, client); + var implementations = new List(); + + Assert.AreEqual(4, methods.Count); + foreach (var method in methods) + { + Assert.IsTrue(method.IsPartialMethod); + using var writer = new CodeWriter(); + writer.WriteMethod(method); + var declaration = (MethodDeclarationSyntax)SyntaxFactory.ParseMemberDeclaration(writer.ToString(false))!; + implementations.Add(declaration + .WithModifiers(SyntaxFactory.TokenList(declaration.Modifiers.Where(m => !m.IsKind(SyntaxKind.AsyncKeyword)))) + .WithBody(SyntaxFactory.Block(SyntaxFactory.ThrowStatement(SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression))))); + } + + var customTree = customCompilation.SyntaxTrees.Single(t => t.GetRoot().DescendantNodes().OfType() + .Any(c => c.Identifier.ValueText == "TestClient")); + var root = customTree.GetRoot(); + var customClass = root.DescendantNodes().OfType().Single(c => c.Identifier.ValueText == "TestClient"); + var implementationRoot = root.ReplaceNode(customClass, customClass.WithMembers(SyntaxFactory.List(implementations))); + var compilation = customCompilation.RemoveAllSyntaxTrees().AddSyntaxTrees(customTree, CSharpSyntaxTree.Create((CSharpSyntaxNode)implementationRoot)); + var errors = compilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).ToArray(); + Assert.IsEmpty(errors.Select(d => d.ToString()), "Customized attributes must occur only on the defining partial declaration."); + var symbol = compilation.GetTypeByMetadataName("Sample.TestClient")!; + foreach (var method in symbol.GetMembers().OfType().Where(m => m.Name is "Bar" or "BarAsync")) + { + Assert.AreEqual("CUSTOM001", method.GetAttributes().Single().ConstructorArguments[0].Value); + } + } + + [TestCase("plain", true)] + [TestCase("array", true)] + [TestCase("nested", true)] + [TestCase("plain", false)] + public async Task ExperimentalProductionMethodsCompile(string shape, bool suppressType) + { + var choice = InputFactory.StringEnum("Choice", [("One", "one")], isExtensible: true); + var payload = InputFactory.Model("Payload", usage: InputModelTypeUsage.Output | InputModelTypeUsage.Json); + InputType type = shape switch + { + "plain" => payload, + "array" => InputFactory.Array(payload), + "nested" => InputFactory.Array(InputFactory.Array(payload)), + _ => throw new ArgumentOutOfRangeException(nameof(shape)) + }; + var query = InputFactory.QueryParameter("value", choice, isRequired: true); + var operation = InputFactory.Operation("Read", parameters: [query], responses: [InputFactory.OperationResponse(bodytype: type)]); + operation.Update(experimental: new InputExperimentalDetails(null, suppressType ? ["A", "B"] : ["B"])); + var serviceMethod = InputFactory.BasicServiceMethod("Read", operation, parameters: + [InputFactory.MethodParameter("value", choice, isRequired: true, location: InputRequestLocation.Query)]); + var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]); + MockHelpers.LoadMockGenerator(); + var customCompilation = await Helpers.GetCompilationFromDirectoryAsync(); + await MockHelpers.LoadMockGeneratorAsync( + inputEnums: () => [choice], inputModels: () => [payload], clients: () => [inputClient], + compilation: () => Task.FromResult(customCompilation)); + var generator = ScmCodeModelGenerator.Instance; + var client = generator.TypeFactory.CreateClient(inputClient)!; + Assert.AreEqual(4, client.Methods.OfType().Count()); + foreach (var method in client.Methods.OfType().Append((ScmMethodProvider)client.RestClient.GetCreateRequestMethod(operation))) + { + CollectionAssert.AreEqual( + (suppressType ? new[] { "A", "B" } : ["B"]).Select(id => Snippet.Literal(id).ToDisplayString()), + method.Suppressions.Select(s => s.Code.ToDisplayString())); + } + + var customTree = customCompilation.SyntaxTrees.Single(t => t.GetRoot().DescendantNodes().OfType() + .Any(s => s.Identifier.ValueText == "Choice")); + // Isolate the client and serialization paths; this GET does not use request-content or model-factory APIs. + var providers = generator.OutputLibrary.TypeProviders + .Where(p => p is not Utf8JsonBinaryContentDefinition and not BinaryContentHelperDefinition and not ModelFactoryProvider).ToArray(); + var generatedTrees = providers.Concat(providers.SelectMany(p => p.SerializationProviders)) + .Select(p => new TypeProviderWriter(p).Write()) + .Select(file => CSharpSyntaxTree.ParseText(file.Content, path: file.Name)); + var compilation = customCompilation.RemoveAllSyntaxTrees() + .AddSyntaxTrees(customTree) + .AddSyntaxTrees(generatedTrees); + var errors = compilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).ToArray(); + if (suppressType) + { + Assert.IsEmpty(errors.Select(d => d.ToString())); + } + else + { + Assert.IsTrue(errors.Length > 0); + Assert.IsTrue(errors.All(d => d.Id == "A"), string.Join(Environment.NewLine, errors.Select(d => d.ToString()))); + } + } + [Test] public void JsonLinesRequestGeneratesAsyncStreamingConvenienceMethod() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ExperimentalCustomizedPartialMethodsKeepExistingAttributes/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ExperimentalCustomizedPartialMethodsKeepExistingAttributes/TestClient.cs new file mode 100644 index 00000000000..01b817b6f71 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ExperimentalCustomizedPartialMethodsKeepExistingAttributes/TestClient.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + [Experimental("CUSTOM001")] + public partial ClientResult Bar(RequestOptions options); + [Experimental("CUSTOM001")] + public partial Task BarAsync(RequestOptions options); + [Experimental("CUSTOM001")] + public partial ClientResult Bar(CancellationToken cancellationToken); + [Experimental("CUSTOM001")] + public partial Task BarAsync(CancellationToken cancellationToken); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ExperimentalProductionMethodsCompile/Choice.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ExperimentalProductionMethodsCompile/Choice.cs new file mode 100644 index 00000000000..55f06778c60 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ExperimentalProductionMethodsCompile/Choice.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; + +namespace Sample.Models +{ + public readonly partial struct Choice + { + [Experimental("B")] + public override string ToString() => _value; + } + + [Experimental("A")] + public partial class Payload + { + } +} + +namespace Sample +{ + public partial class SampleContext + { + public static SampleContext Default { get; } = new SampleContext(); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/NamedTypeSymbolProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/NamedTypeSymbolProvider.cs index 613c9e34108..88cc9a5ec79 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/NamedTypeSymbolProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/NamedTypeSymbolProvider.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Xml; @@ -345,6 +346,9 @@ protected internal override MethodProvider[] BuildMethods() GetNullableCSharpType(methodSymbol.ReturnType), GetSymbolXmlDoc(methodSymbol, "returns"), [.. methodSymbol.Parameters.Select(p => ConvertToParameterProvider(methodSymbol, p))], + Attributes: [.. methodSymbol.GetAttributes() + .Where(a => a.AttributeClass?.ToDisplayString() == typeof(ExperimentalAttribute).FullName) + .Select(a => new AttributeStatement(a))], GenericArguments: methodSymbol.TypeParameters.IsEmpty ? null : [.. methodSymbol.TypeParameters.Select(parameter => parameter.GetCSharpType())], diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/ExperimentalApiHelpers.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/ExperimentalApiHelpers.cs index 48428196bf0..3f410728ff4 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/ExperimentalApiHelpers.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/ExperimentalApiHelpers.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Text.RegularExpressions; using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Providers; using Microsoft.TypeSpec.Generator.Statements; @@ -85,6 +86,16 @@ public static SuppressionStatement[] GetSuppressions(InputClient client) return CreateSuppressions(collector.Ids.Order(StringComparer.Ordinal)); } + public static SuppressionStatement[] GetParameterSuppressions(IEnumerable parameters) + { + var collector = new DiagnosticCollector(); + foreach (var parameter in parameters) + { + collector.AddProperty(parameter); + } + return CreateSuppressions(collector.Ids.Order(StringComparer.Ordinal)); + } + public static SuppressionStatement[] GetSuppressions(InputOperation operation, InputClient? client = null) { var collector = new DiagnosticCollector(); @@ -113,10 +124,9 @@ private static SuppressionStatement[] CreateSuppressions(IEnumerable dia private static void ValidateDiagnosticId(string diagnosticId) { - // An empty #pragma warning disable would suppress every diagnostic. - if (string.IsNullOrWhiteSpace(diagnosticId)) + if (!Regex.IsMatch(diagnosticId, @"\A(?:[A-Za-z_][A-Za-z0-9_]*|[0-9]+)\z", RegexOptions.CultureInvariant)) { - throw new ArgumentException("Experimental diagnostic identifiers cannot be empty or whitespace.", nameof(diagnosticId)); + throw new ArgumentException("Experimental diagnostic IDs must be single C# warning identifiers or decimal warning numbers.", nameof(diagnosticId)); } } diff --git a/packages/http-client-csharp/readme.md b/packages/http-client-csharp/readme.md index 614ac522578..3c42389e2cb 100644 --- a/packages/http-client-csharp/readme.md +++ b/packages/http-client-csharp/readme.md @@ -75,6 +75,12 @@ Both metadata fields are optional. Without `diagnosticId`, no public experimenta is added. Without `dependsOn`, no additional dependency diagnostics are requested; generated references to source-annotated experiments are still handled. Emitter scopes apply to both fields. +Diagnostic IDs must be single C# warning identifiers (ASCII letters, digits, and underscores, +not starting with a digit) or decimal warning numbers. Whitespace, punctuation, comments, and +line breaks are rejected with `invalid-experimental-diagnostic-id` before generating C#. +An `ExperimentalAttribute` on a customized partial client or method takes precedence over +the generated attribute. + Some TypeSpec declarations have no corresponding C# declaration, such as scalars or unions mapped to built-in C# types. C# also does not allow `ExperimentalAttribute` on parameters. The emitter reports `experimental-target-not-supported` for these annotations instead of From 14be684aa8e7e89472a08cc98c9aaca2219d8cee Mon Sep 17 00:00:00 2001 From: jolov Date: Wed, 23 Sep 2026 09:35:58 -0700 Subject: [PATCH 6/8] fix(http-client-csharp): preserve experimental compatibility metadata Keep current experimental attributes and suppressions on compatibility overloads, include child accessor parameter dependencies, and clarify validation-only handling for unsupported direct parameter annotations. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../emitter/src/lib/operation-converter.ts | 3 +- .../emitter/test/Unit/decorator-list.test.ts | 15 +++++ .../ClientProviders/ClientProviderTests.cs | 31 +++++++++ .../src/Utilities/BackCompatHelper.cs | 23 +++++++ .../src/Utilities/ExperimentalApiHelpers.cs | 4 ++ .../ExperimentalCompatibility.cs | 19 ++++++ .../test/Providers/TypeProviderTests.cs | 66 +++++++++++++++++++ 7 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/ExperimentalBackCompatOverloadsFollowCurrentMetadata/ExperimentalCompatibility.cs diff --git a/packages/http-client-csharp/emitter/src/lib/operation-converter.ts b/packages/http-client-csharp/emitter/src/lib/operation-converter.ts index 3a92236db3a..5b3f554ac39 100644 --- a/packages/http-client-csharp/emitter/src/lib/operation-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/operation-converter.ts @@ -690,6 +690,8 @@ export function fromMethodParameter( return diagnostics.wrap(retVar as InputMethodParameter); } + // C# cannot apply ExperimentalAttribute directly to a parameter. + diagnostics.pipe(getExperimentalDetails(sdkContext, p.__raw, false)); const parameterType = diagnostics.pipe(fromSdkType(sdkContext, p.type, p, namespace)); const paramAlias = p.__raw ? getParamAlias(sdkContext, p.__raw) : undefined; @@ -714,7 +716,6 @@ export function fromMethodParameter( decorators: p.decorators, paramAlias, isExactName: p.isExactName, - experimental: diagnostics.pipe(getExperimentalDetails(sdkContext, p.__raw, false)), }; sdkContext.__typeCache.updateSdkMethodParameterReferences(p, retVar); diff --git a/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts b/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts index a7ea5fd75dc..4fae7562c1e 100644 --- a/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts @@ -29,6 +29,21 @@ describe("Test emitting decorator list", () => { await runner.addTypeSpecLibrary(HttpClientTestLibrary); }); + it.each([ + `@TypeSpec.HttpClient.experimental(#{ diagnosticId: "TYPE001" }) + model Payload { value: string; } op read(@body value: Payload): void;`, + `@TypeSpec.HttpClient.experimental(#{ diagnosticId: "TYPE001" }) + enum Choice { One, Two } op read(@query value: Choice): void;`, + ])("keeps parameter-type metadata without annotating the parameter", async (code) => { + const program = await typeSpecCompile(code, runner, { IsHttpClientNeeded: true }); + const sdkContext = await createCSharpSdkContext(createEmitterContext(program)); + const [root, diagnostics] = createModel(sdkContext); + expectDiagnosticEmpty(diagnostics); + const parameter = root.clients[0].methods[0].parameters.find((p) => p.name === "value")!; + strictEqual(parameter.experimental, undefined); + deepStrictEqual(parameter.type.experimental, { diagnosticId: "TYPE001", dependsOn: [] }); + }); + describe.each(["diagnosticId", "dependsOn"])("validating %s", (field) => { it.each([ "", 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 6cc7ead0b38..84dc616fd71 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 @@ -12,6 +12,8 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xml.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.TypeSpec.Generator.ClientModel.Providers; using Microsoft.TypeSpec.Generator.Expressions; using Microsoft.TypeSpec.Generator.Input; @@ -26,6 +28,35 @@ namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.ClientProvide { public class ClientProviderTests { + [Test] + public void ExperimentalChildParametersAreSuppressedOnParentAccessors() + { + var mode = InputFactory.Experimental(InputFactory.StringEnum("Mode", [("One", "one")], isExtensible: true), "MODE001"); + var parent = InputFactory.Client("ParentClient"); + var child = InputFactory.Client("ChildClient", parent: parent, + parameters: [InputFactory.PathParameter("mode", mode, isRequired: true, scope: InputParameterScope.Client)], + initializedBy: InputClientInitializedBy.Parent); + MockHelpers.LoadMockGenerator(inputEnums: () => [mode], clients: () => [parent, child]); + var generator = ScmCodeModelGenerator.Instance; + var client = generator.TypeFactory.CreateClient(parent)!; + var accessor = client.Methods.Single(m => m.Signature.Name == "GetChildClient"); + Assert.AreEqual("Mode", accessor.Signature.Parameters.Single().Type.Name); + Assert.IsTrue(client.DisabledFileWarnings.Any(s => s.Code.ToDisplayString() == Literal("MODE001").ToDisplayString())); + + var references = AppDomain.CurrentDomain.GetAssemblies() + .Where(a => !a.IsDynamic && !string.IsNullOrEmpty(a.Location)) + .Select(a => MetadataReference.CreateFromFile(a.Location)) + .Append(MetadataReference.CreateFromFile(typeof(Microsoft.Extensions.Configuration.IConfigurationSection).Assembly.Location)); + var providers = generator.OutputLibrary.TypeProviders + .Where(p => p is not Utf8JsonBinaryContentDefinition and not BinaryContentHelperDefinition); + var compilation = CSharpCompilation.Create( + "ExperimentalChildParameters", + providers.Select(p => CSharpSyntaxTree.ParseText(new TypeProviderWriter(p).Write().Content)), + references, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, generalDiagnosticOption: ReportDiagnostic.Error)); + Assert.IsEmpty(compilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).Select(d => d.ToString())); + } + [Test] public async Task ExperimentalCustomClientKeepsExistingAttribute() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs index 5123a920e7b..585d589e64a 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.TypeSpec.Generator.EmitterRpc; using Microsoft.TypeSpec.Generator.Expressions; @@ -487,6 +488,10 @@ public static void AddBackCompatOverloads(TypeProvider enclosingType, List MethodSignatureBase.SignatureComparer.Equals(method.Signature, currentMethod.Signature)) + : null; + var attribute = customMethod?.Signature.Attributes.FirstOrDefault(a => a.Type.Equals(typeof(ExperimentalAttribute))) + ?? currentMethod.Signature.Attributes.FirstOrDefault(a => a.Type.Equals(typeof(ExperimentalAttribute))); + + // Graduation and custom partial attributes follow the current API, not the last contract. + overload.Signature.Update(attributes: + [ + .. overload.Signature.Attributes.Where(a => !a.Type.Equals(typeof(ExperimentalAttribute))), + .. attribute is null ? Array.Empty() : [attribute] + ]); + overload.Update(suppressions: ExperimentalApiHelpers.MergeSuppressions(overload.Suppressions, currentMethod.Suppressions)); + } + // Returns true when both signatures have the same return type (matched by name); a null return type // matches only another null return type. Compares in both directions so that a return type whose // (possibly generic) parts are unresolved in the customization compilation still matches by name. diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/ExperimentalApiHelpers.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/ExperimentalApiHelpers.cs index 3f410728ff4..32a6beda738 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/ExperimentalApiHelpers.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/ExperimentalApiHelpers.cs @@ -66,6 +66,10 @@ public static SuppressionStatement[] GetSuppressions(InputClient client) foreach (var child in client.Children) { collector.AddId(child.Experimental?.DiagnosticId); + foreach (var parameter in child.Parameters) + { + collector.AddProperty(parameter); + } } foreach (var parameter in client.Parameters) { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/ExperimentalBackCompatOverloadsFollowCurrentMetadata/ExperimentalCompatibility.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/ExperimentalBackCompatOverloadsFollowCurrentMetadata/ExperimentalCompatibility.cs new file mode 100644 index 00000000000..6d390bc98f8 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/ExperimentalBackCompatOverloadsFollowCurrentMetadata/ExperimentalCompatibility.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; + +namespace Test +{ + public class ExperimentalCompatibility + { + [Experimental("PREVIOUS001")] + public void AddOptional(int value) { } + + [Experimental("PREVIOUS001")] + public void RemoveNullability(int? value) { } + + [Experimental("PREVIOUS001")] + public void RequireOptional(int? value = null) { } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs index 133203c1774..a50557bab5e 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs @@ -3,9 +3,13 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Primitives; using Microsoft.TypeSpec.Generator.Providers; @@ -19,6 +23,68 @@ namespace Microsoft.TypeSpec.Generator.Tests.Providers { public class TypeProviderTests { + [TestCase("CURRENT001", false)] + [TestCase("CURRENT001", true)] + [TestCase(null, true)] + public async Task ExperimentalBackCompatOverloadsFollowCurrentMetadata(string? diagnosticId, bool previouslyExperimental) + { + await MockHelpers.LoadMockGeneratorAsync(lastContractCompilation: async () => + { + var compilation = await Helpers.GetCompilationFromDirectoryAsync(); + if (previouslyExperimental) + { + return compilation; + } + var tree = compilation.SyntaxTrees.Single(t => t.FilePath.EndsWith("ExperimentalCompatibility.cs", StringComparison.Ordinal)); + var root = tree.GetRoot(); + var updatedRoot = root.ReplaceNodes(root.DescendantNodes().OfType(), + (_, method) => method.WithAttributeLists(default)); + return compilation.ReplaceSyntaxTree(tree, tree.WithRootAndOptions(updatedRoot, tree.Options)); + }); + var owner = new TestTypeProvider(name: "ExperimentalCompatibility", ns: "Test"); + var attributes = ExperimentalApiHelpers.BuildAttributes(new InputExperimentalDetails(diagnosticId)); + var suppressions = new[] + { + new SuppressionStatement(null, Snippet.Literal("DEP001"), "Dependency one."), + new SuppressionStatement(null, Snippet.Literal("DEP002"), "Dependency two.") + }; + MethodProvider CreateMethod(string name, params ParameterProvider[] parameters) => new( + new MethodSignature(name, null, MethodSignatureModifiers.Public, null, null, parameters, Attributes: attributes), + Snippet.Throw(Snippet.Null), owner, suppressions: suppressions); + owner.Update(methods: + [ + CreateMethod("AddOptional", + new ParameterProvider("value", $"", typeof(int)), + new ParameterProvider("added", $"", typeof(bool), defaultValue: Snippet.Default, location: ParameterLocation.Query)), + CreateMethod("RemoveNullability", new ParameterProvider("value", $"", typeof(int))), + CreateMethod("RequireOptional", new ParameterProvider("value", $"", typeof(int?))) + ]); + + owner.ProcessTypeForBackCompatibility(); + var shims = owner.Methods.Where(m => m.Signature.Attributes.Any(a => a.Type.Equals(typeof(EditorBrowsableAttribute)))).ToArray(); + Assert.AreEqual(3, shims.Length); + foreach (var shim in shims) + { + var experiment = shim.Signature.Attributes.SingleOrDefault(a => a.Type.Equals(typeof(ExperimentalAttribute))); + Assert.AreEqual(diagnosticId is null ? null : Snippet.Literal(diagnosticId).ToDisplayString(), + experiment?.Arguments[0].ToDisplayString()); + CollectionAssert.AreEqual( + suppressions.Select(s => s.Code.ToDisplayString()), + shim.Suppressions.Select(s => s.Code.ToDisplayString())); + } + + var references = AppDomain.CurrentDomain.GetAssemblies() + .Where(a => !a.IsDynamic && !string.IsNullOrEmpty(a.Location)) + .Select(a => MetadataReference.CreateFromFile(a.Location)); + var compilation = CSharpCompilation.Create( + "ExperimentalCompatibility", + [CSharpSyntaxTree.ParseText(new TypeProviderWriter(owner).Write().Content), + CSharpSyntaxTree.ParseText(new TypeProviderWriter(new ArgumentDefinition()).Write().Content)], + references, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, generalDiagnosticOption: ReportDiagnostic.Error)); + Assert.IsEmpty(compilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).Select(d => d.ToString())); + } + [TestCase(false)] [TestCase(true)] public void ExperimentalModelsAndProperties(bool modelAsStruct) From f3634506c1def0987ce6b32a5821395c8232b45e Mon Sep 17 00:00:00 2001 From: jolov Date: Wed, 23 Sep 2026 11:55:39 -0700 Subject: [PATCH 7/8] fix(http-client-csharp): retain experimental metadata for external types Preserve source lifecycle details on explicitly mapped external types through input serialization without emitting their declarations, and add compiler regressions for external references and literal enum members. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../http-client-csharp/.tspd/docs/usage.md | 8 +- .../emitter/src/lib/experimental.ts | 4 +- .../emitter/src/lib/type-converter.ts | 9 +- .../emitter/test/Unit/type-converter.test.ts | 57 ++++++++++++- .../ScmModelFactoryProviderTests.cs | 82 +++++++++++++++++++ .../CustomTypes.cs | 22 +++++ .../Serialization/InputArrayTypeConverter.cs | 7 +- .../InputDateTimeTypeConverter.cs | 6 +- .../InputDictionaryTypeConverter.cs | 7 +- .../InputDurationTypeConverter.cs | 6 +- .../InputPrimitiveTypeConverter.cs | 3 + .../Serialization/InputUnionTypeConverter.cs | 3 + .../TypeSpecInputNullableTypeConverter.cs | 7 +- .../test/TypeSpecInputConverterTests.cs | 35 ++++++++ packages/http-client-csharp/readme.md | 8 +- 15 files changed, 240 insertions(+), 24 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ModelFactoryProviders/TestData/ScmModelFactoryProviderTests/ExperimentalLiteralEnumMembersCompileInModelsAndFactories/CustomTypes.cs diff --git a/packages/http-client-csharp/.tspd/docs/usage.md b/packages/http-client-csharp/.tspd/docs/usage.md index 14315f54cfa..e245185dda3 100644 --- a/packages/http-client-csharp/.tspd/docs/usage.md +++ b/packages/http-client-csharp/.tspd/docs/usage.md @@ -56,8 +56,10 @@ generic arguments, and implementation references; neither suppresses diagnostics consumer code. Dependencies identify diagnostics rather than individual types: one diagnostic can apply -to multiple types or members, including those defined in external libraries. External -experiments still require explicit `dependsOn` entries; they are not discovered by reflection. +to multiple types or members, including those defined in external libraries. For externally +mapped types, source `@experimental` metadata is retained for generated-reference suppressions; +the emitter does not generate the external declaration or add attributes to its library. +Other external experiments require explicit `dependsOn` entries. They are not discovered by reflection. Both metadata fields are optional. Without `diagnosticId`, no public experimental attribute is added. Without `dependsOn`, no additional dependency diagnostics are requested; generated @@ -70,7 +72,7 @@ An `ExperimentalAttribute` on a customized partial client or method takes preced the generated attribute. Some TypeSpec declarations have no corresponding C# declaration, such as scalars or unions -mapped to built-in C# types. C# also does not allow `ExperimentalAttribute` on parameters. +erased to built-in C# types without an explicit external mapping. C# also does not allow `ExperimentalAttribute` on parameters. The emitter reports `experimental-target-not-supported` for these annotations instead of silently dropping them or assigning their diagnostic to an unrelated API. diff --git a/packages/http-client-csharp/emitter/src/lib/experimental.ts b/packages/http-client-csharp/emitter/src/lib/experimental.ts index 3db88c6c1cb..d7bc23db00e 100644 --- a/packages/http-client-csharp/emitter/src/lib/experimental.ts +++ b/packages/http-client-csharp/emitter/src/lib/experimental.ts @@ -12,7 +12,7 @@ import { createDiagnostic } from "./lib.js"; export function getExperimentalDetails( context: SdkContext, target: Type | undefined, - hasGeneratedDeclaration = true, + supportsExperimentalMetadata = true, ): [InputExperimentalDetails | undefined, readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); if (!target) { @@ -24,7 +24,7 @@ export function getExperimentalDetails( emitterName: "@typespec/http-client-csharp", }), ); - if (lifecycle && !hasGeneratedDeclaration) { + if (lifecycle && !supportsExperimentalMetadata) { diagnostics.add(createDiagnostic({ code: "experimental-target-not-supported", target })); return diagnostics.wrap(undefined); } diff --git a/packages/http-client-csharp/emitter/src/lib/type-converter.ts b/packages/http-client-csharp/emitter/src/lib/type-converter.ts index f304689d93d..a0fcaf7ff10 100644 --- a/packages/http-client-csharp/emitter/src/lib/type-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/type-converter.ts @@ -190,14 +190,15 @@ export function fromSdkType( break; } + // External declarations are not emitted, but their known diagnostics apply at generated reference sites. retVar.experimental = diagnostics.pipe( getExperimentalDetails( sdkContext, sdkType.__raw, - !retVar.external && - ((retVar.kind === "model" && !retVar.isFileType) || - retVar.kind === "enum" || - retVar.kind === "enumvalue"), + retVar.external !== undefined || + (retVar.kind === "model" && !retVar.isFileType) || + retVar.kind === "enum" || + retVar.kind === "enumvalue", ), ); if (sdkType.__raw?.kind === "Union" && retVar.kind !== "enum") { diff --git a/packages/http-client-csharp/emitter/test/Unit/type-converter.test.ts b/packages/http-client-csharp/emitter/test/Unit/type-converter.test.ts index 7c399bdd299..75305bc59e8 100644 --- a/packages/http-client-csharp/emitter/test/Unit/type-converter.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/type-converter.test.ts @@ -1,8 +1,13 @@ vi.resetModules(); import type { DecoratorInfo } from "@azure-tools/typespec-client-generator-core"; -import type { TestHost } from "@typespec/compiler/testing"; -import { ok, strictEqual } from "assert"; +import { + expectDiagnosticEmpty, + expectDiagnostics, + type TestHost, +} from "@typespec/compiler/testing"; +import { HttpClientTestLibrary } from "@typespec/http-client/testing"; +import { deepStrictEqual, ok, strictEqual } from "assert"; import { beforeEach, describe, it, vi } from "vitest"; import { createModel } from "../../src/lib/client-model-builder.js"; import { getAllModelDecorators } from "../../src/lib/type-converter.js"; @@ -93,6 +98,54 @@ describe("External types", () => { beforeEach(async () => { runner = await createEmitterTestHost(); + await runner.addTypeSpecLibrary(HttpClientTestLibrary); + }); + + describe.each([ + { kind: "model", declaration: "model ExternalValue { value: string; }" }, + { kind: "enum", declaration: 'enum ExternalValue { One: "one", Two: "two" }' }, + { kind: "union", declaration: "union ExternalValue { text: string, count: int32 }" }, + { kind: "scalar", declaration: "scalar ExternalValue extends string;" }, + { kind: "nullable", declaration: "union ExternalValue { string, null }" }, + { kind: "array", declaration: "model ExternalValue is Array;" }, + { kind: "dict", declaration: "model ExternalValue is Record;" }, + { kind: "utcDateTime", declaration: "scalar ExternalValue extends utcDateTime;" }, + { kind: "duration", declaration: "scalar ExternalValue extends duration;" }, + ])("experimental external $kind", ({ declaration, kind }) => { + it.each([ + { scope: "@typespec/http-client-csharp", applies: true }, + { scope: "!other-emitter", applies: true }, + { scope: "other-emitter", applies: false }, + ])("preserves metadata scoped to $scope", async ({ scope, applies }) => { + const program = await typeSpecCompile( + ` + @TypeSpec.HttpClient.experimental(#{ + emitterScope: "${scope}", diagnosticId: "EXTERNAL001", dependsOn: #["DEP001"] + }) + @alternateType({ identity: "External.Value" }, "csharp") + ${declaration} + model Wrapper { value: ExternalValue; } + op read(): Wrapper; + `, + runner, + { IsTCGCNeeded: true, IsHttpClientNeeded: true }, + ); + const sdkContext = await createCSharpSdkContext(createEmitterContext(program)); + expectDiagnostics( + sdkContext.diagnostics, + kind === "union" + ? [{ code: "@azure-tools/typespec-azure-core/union-enums-multiple-kind" }] + : [], + ); + const [root, diagnostics] = createModel(sdkContext); + expectDiagnosticEmpty(diagnostics); + const type = root.models.find((model) => model.name === "Wrapper")!.properties[0].type; + strictEqual(type.external?.identity, "External.Value"); + deepStrictEqual( + type.experimental, + applies ? { diagnosticId: "EXTERNAL001", dependsOn: ["DEP001"] } : undefined, + ); + }); }); it("should convert external type from @alternateType decorator", async () => { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ModelFactoryProviders/ScmModelFactoryProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ModelFactoryProviders/ScmModelFactoryProviderTests.cs index 439b3ca5bc4..14e7e47cf26 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ModelFactoryProviders/ScmModelFactoryProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ModelFactoryProviders/ScmModelFactoryProviderTests.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.TypeSpec.Generator.Input; @@ -21,6 +22,87 @@ namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.ModelFactoryP { public class ScmModelFactoryProviderTests { + [Test] + public void ExperimentalExternalTypesOnlyContributeReferenceSuppressions() + { + var externalModel = InputFactory.Experimental( + InputFactory.Model("ForeignPatch", properties: [], + external: new InputExternalTypeMetadata("System.ClientModel.Primitives.JsonPatch", null, null)), + "SCME0001"); + var externalEnum = InputFactory.Experimental( + InputFactory.StringEnum("ForeignStatus", [("OK", "OK")], + external: new InputExternalTypeMetadata("System.Net.HttpStatusCode", null, null)), + "EXTERNAL_ENUM"); + var input = InputFactory.Model("Payload", properties: [InputFactory.Property("patch", externalModel)]); + MockHelpers.LoadMockGenerator(inputModels: () => [externalModel, input], inputEnums: () => [externalEnum]); + var generator = ScmCodeModelGenerator.Instance; + var model = generator.TypeFactory.CreateModel(input)!; + var factory = generator.TypeFactory.CreateModelFactory([externalModel, input]); + + Assert.IsInstanceOf(generator.TypeFactory.CreateModel(externalModel)); + Assert.IsNull(generator.TypeFactory.CreateEnum(externalEnum)); + Assert.IsFalse(generator.OutputLibrary.TypeProviders.Any(p => p is SystemObjectModelProvider || p.Name == "ForeignStatus")); + Assert.AreEqual("Payload", factory.Methods.Single().Signature.Name); + Assert.IsFalse(model.Attributes.Any(a => a.Type.Equals(typeof(ExperimentalAttribute)))); + Assert.IsFalse(factory.Methods.Single().Signature.Attributes.Any(a => a.Type.Equals(typeof(ExperimentalAttribute)))); + Assert.IsTrue(model.DisabledFileWarnings.Any(s => s.Code.ToDisplayString() == Snippet.Literal("SCME0001").ToDisplayString())); + + var references = AppDomain.CurrentDomain.GetAssemblies() + .Where(a => !a.IsDynamic && !string.IsNullOrEmpty(a.Location)) + .Select(a => MetadataReference.CreateFromFile(a.Location)); + var compilation = CSharpCompilation.Create( + "ExperimentalExternalReferences", + new TypeProvider[] { model, factory, new ArgumentDefinition() } + .Select(p => CSharpSyntaxTree.ParseText(new TypeProviderWriter(p).Write().Content)), + references, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, generalDiagnosticOption: ReportDiagnostic.Error)); + Assert.IsEmpty(compilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).Select(d => d.ToString())); + } + + [TestCase(false)] + [TestCase(true)] + public async Task ExperimentalLiteralEnumMembersCompileInModelsAndFactories(bool extensible) + { + var choice = InputFactory.StringEnum("Choice", [("One", "one"), ("Two", "two")], isExtensible: extensible); + InputFactory.Experimental(choice.Values[0], "SAMPLE_MEMBER"); + InputFactory.Experimental(choice.Values[1], "OTHER_MEMBER"); + var input = InputFactory.Model("Payload", properties: + [InputFactory.Property("kind", choice.Values[0], isRequired: true)]); + Compilation? customCompilation = null; + await MockHelpers.LoadMockGeneratorAsync( + inputEnums: () => [choice], inputModels: () => [input], + compilation: extensible ? null : async () => customCompilation = await Helpers.GetCompilationFromDirectoryAsync()); + var generator = ScmCodeModelGenerator.Instance; + var model = generator.TypeFactory.CreateModel(input)!; + var factory = generator.TypeFactory.CreateModelFactory([input]); + var enumProvider = generator.TypeFactory.CreateEnum(choice)!; + + Assert.IsFalse(enumProvider.Attributes.Any(a => a.Type.Equals(typeof(ExperimentalAttribute)))); + foreach (var provider in new TypeProvider[] { model, factory }) + { + CollectionAssert.AreEqual( + new[] { Snippet.Literal("SAMPLE_MEMBER").ToDisplayString() }, + provider.DisabledFileWarnings.Select(s => s.Code.ToDisplayString())); + } + var references = AppDomain.CurrentDomain.GetAssemblies() + .Where(a => !a.IsDynamic && !string.IsNullOrEmpty(a.Location)) + .Select(a => MetadataReference.CreateFromFile(a.Location)); + var compilation = CSharpCompilation.Create( + "ExperimentalLiteralEnum", + new TypeProvider[] { model, factory, new ArgumentDefinition() } + .Select(p => CSharpSyntaxTree.ParseText(new TypeProviderWriter(p).Write().Content)) + .Concat(extensible + ? new[] { CSharpSyntaxTree.ParseText(new TypeProviderWriter(enumProvider).Write().Content) } + : customCompilation!.SyntaxTrees.Where(t => t.FilePath.EndsWith("CustomTypes.cs", StringComparison.Ordinal))), + references, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, generalDiagnosticOption: ReportDiagnostic.Error)); + Assert.IsEmpty(compilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).Select(d => d.ToString())); + if (!extensible) + { + StringAssert.Contains("Choice.One", factory.Methods.Single().BodyStatements!.ToDisplayString()); + } + } + [TestCase(true, false)] [TestCase(false, true)] [TestCase(true, true)] diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ModelFactoryProviders/TestData/ScmModelFactoryProviderTests/ExperimentalLiteralEnumMembersCompileInModelsAndFactories/CustomTypes.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ModelFactoryProviders/TestData/ScmModelFactoryProviderTests/ExperimentalLiteralEnumMembersCompileInModelsAndFactories/CustomTypes.cs new file mode 100644 index 00000000000..6a761b32d82 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ModelFactoryProviders/TestData/ScmModelFactoryProviderTests/ExperimentalLiteralEnumMembersCompileInModelsAndFactories/CustomTypes.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; + +namespace Sample.Models +{ + public partial class Payload + { +#pragma warning disable SAMPLE_MEMBER + public Choice Kind { get; } = Choice.One; +#pragma warning restore SAMPLE_MEMBER + } + + public enum Choice + { + [Experimental("SAMPLE_MEMBER")] + One, + [Experimental("OTHER_MEMBER")] + Two + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputArrayTypeConverter.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputArrayTypeConverter.cs index c7b2eaf884f..6591e88575c 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputArrayTypeConverter.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputArrayTypeConverter.cs @@ -29,6 +29,7 @@ public static InputArrayType CreateListType(ref Utf8JsonReader reader, string? i InputType? valueType = null; IReadOnlyList? decorators = null; InputExternalTypeMetadata? external = null; + InputExperimentalDetails? experimental = null; while (reader.TokenType != JsonTokenType.EndObject) { var isKnownProperty = reader.TryReadReferenceId(ref id) @@ -36,7 +37,8 @@ public static InputArrayType CreateListType(ref Utf8JsonReader reader, string? i || reader.TryReadString("crossLanguageDefinitionId", ref crossLanguageDefinitionId) || reader.TryReadComplexType("valueType", options, ref valueType) || reader.TryReadComplexType("decorators", options, ref decorators) - || reader.TryReadComplexType("external", options, ref external); + || reader.TryReadComplexType("external", options, ref external) + || reader.TryReadComplexType("experimental", options, ref experimental); if (!isKnownProperty) { @@ -48,7 +50,8 @@ public static InputArrayType CreateListType(ref Utf8JsonReader reader, string? i var listType = new InputArrayType(name ?? "Array", crossLanguageDefinitionId ?? string.Empty, valueType) { Decorators = decorators ?? [], - External = external + External = external, + Experimental = experimental }; if (id != null) { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputDateTimeTypeConverter.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputDateTimeTypeConverter.cs index 60641e7b697..d6537d5b98d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputDateTimeTypeConverter.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputDateTimeTypeConverter.cs @@ -30,6 +30,7 @@ public static InputDateTimeType CreateDateTimeType(ref Utf8JsonReader reader, st IReadOnlyList? decorators = null; InputDateTimeType? baseType = null; InputExternalTypeMetadata? external = null; + InputExperimentalDetails? experimental = null; while (reader.TokenType != JsonTokenType.EndObject) { @@ -40,7 +41,8 @@ public static InputDateTimeType CreateDateTimeType(ref Utf8JsonReader reader, st || reader.TryReadComplexType("wireType", options, ref wireType) || reader.TryReadComplexType("baseType", options, ref baseType) || reader.TryReadComplexType("decorators", options, ref decorators) - || reader.TryReadComplexType("external", options, ref external); + || reader.TryReadComplexType("external", options, ref external) + || reader.TryReadComplexType("experimental", options, ref experimental); if (!isKnownProperty) { @@ -53,7 +55,7 @@ public static InputDateTimeType CreateDateTimeType(ref Utf8JsonReader reader, st encode = encode ?? throw new JsonException("DateTime type must have encoding"); wireType = wireType ?? throw new JsonException("DateTime type must have wireType"); - var dateTimeType = new InputDateTimeType(new DateTimeKnownEncoding(encode), name, crossLanguageDefinitionId, wireType, baseType) { Decorators = decorators ?? [], External = external }; + var dateTimeType = new InputDateTimeType(new DateTimeKnownEncoding(encode), name, crossLanguageDefinitionId, wireType, baseType) { Decorators = decorators ?? [], External = external, Experimental = experimental }; if (id != null) { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputDictionaryTypeConverter.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputDictionaryTypeConverter.cs index 429cb5f396f..0cb0dc35109 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputDictionaryTypeConverter.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputDictionaryTypeConverter.cs @@ -29,13 +29,15 @@ public static InputDictionaryType CreateDictionaryType(ref Utf8JsonReader reader InputType? valueType = null; IReadOnlyList? decorators = null; InputExternalTypeMetadata? external = null; + InputExperimentalDetails? experimental = null; while (reader.TokenType != JsonTokenType.EndObject) { var isKnownProperty = reader.TryReadReferenceId(ref id) || reader.TryReadComplexType("keyType", options, ref keyType) || reader.TryReadComplexType("valueType", options, ref valueType) || reader.TryReadComplexType("decorators", options, ref decorators) - || reader.TryReadComplexType("external", options, ref external); + || reader.TryReadComplexType("external", options, ref external) + || reader.TryReadComplexType("experimental", options, ref experimental); if (!isKnownProperty) { @@ -49,7 +51,8 @@ public static InputDictionaryType CreateDictionaryType(ref Utf8JsonReader reader var dictType = new InputDictionaryType("Dictionary", keyType, valueType) { Decorators = decorators ?? [], - External = external + External = external, + Experimental = experimental }; if (id != null) { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputDurationTypeConverter.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputDurationTypeConverter.cs index ee61696e9d2..4c9baebaa6a 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputDurationTypeConverter.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputDurationTypeConverter.cs @@ -31,6 +31,7 @@ public static InputDurationType CreateDurationType(ref Utf8JsonReader reader, st IReadOnlyList? decorators = null; InputDurationType? baseType = null; InputExternalTypeMetadata? external = null; + InputExperimentalDetails? experimental = null; while (reader.TokenType != JsonTokenType.EndObject) { @@ -41,7 +42,8 @@ public static InputDurationType CreateDurationType(ref Utf8JsonReader reader, st || reader.TryReadComplexType("wireType", options, ref wireType) || reader.TryReadComplexType("baseType", options, ref baseType) || reader.TryReadComplexType("decorators", options, ref decorators) - || reader.TryReadComplexType("external", options, ref external); + || reader.TryReadComplexType("external", options, ref external) + || reader.TryReadComplexType("experimental", options, ref experimental); if (!isKnownProperty) { @@ -59,7 +61,7 @@ public static InputDurationType CreateDurationType(ref Utf8JsonReader reader, st } wireType = wireType ?? throw new JsonException("Duration type must have wireType"); - var dateTimeType = new InputDurationType(new DurationKnownEncoding(encode), name, crossLanguageDefinitionId, wireType, baseType) { Decorators = decorators ?? [], External = external }; + var dateTimeType = new InputDurationType(new DurationKnownEncoding(encode), name, crossLanguageDefinitionId, wireType, baseType) { Decorators = decorators ?? [], External = external, Experimental = experimental }; if (id != null) { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputPrimitiveTypeConverter.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputPrimitiveTypeConverter.cs index 2388d03a364..295797a8a0a 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputPrimitiveTypeConverter.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputPrimitiveTypeConverter.cs @@ -30,6 +30,7 @@ public static InputPrimitiveType CreatePrimitiveType(ref Utf8JsonReader reader, InputPrimitiveType? baseType = null; IReadOnlyList? decorators = null; InputExternalTypeMetadata? external = null; + InputExperimentalDetails? experimental = null; bool isFileType = false; while (reader.TokenType != JsonTokenType.EndObject) { @@ -41,6 +42,7 @@ public static InputPrimitiveType CreatePrimitiveType(ref Utf8JsonReader reader, || reader.TryReadComplexType("baseType", options, ref baseType) || reader.TryReadComplexType("decorators", options, ref decorators) || reader.TryReadComplexType("external", options, ref external) + || reader.TryReadComplexType("experimental", options, ref experimental) || reader.TryReadBoolean("isFileType", ref isFileType); if (!isKnownProperty) @@ -62,6 +64,7 @@ public static InputPrimitiveType CreatePrimitiveType(ref Utf8JsonReader reader, { Decorators = decorators ?? [], External = external, + Experimental = experimental, IsFileType = isFileType }; if (id != null) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputUnionTypeConverter.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputUnionTypeConverter.cs index 55e95f9f06f..0677f7fb83a 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputUnionTypeConverter.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputUnionTypeConverter.cs @@ -38,6 +38,7 @@ public static InputUnionType CreateInputUnionType(ref Utf8JsonReader reader, str IReadOnlyList? variantTypes = null; IReadOnlyList? decorators = null; InputExternalTypeMetadata? external = null; + InputExperimentalDetails? experimental = null; bool isExactName = false; while (reader.TokenType != JsonTokenType.EndObject) { @@ -45,6 +46,7 @@ public static InputUnionType CreateInputUnionType(ref Utf8JsonReader reader, str || reader.TryReadComplexType("variantTypes", options, ref variantTypes) || reader.TryReadComplexType("decorators", options, ref decorators) || reader.TryReadComplexType("external", options, ref external) + || reader.TryReadComplexType("experimental", options, ref experimental) || reader.TryReadBoolean("isExactName", ref isExactName); if (!isKnownProperty) @@ -61,6 +63,7 @@ public static InputUnionType CreateInputUnionType(ref Utf8JsonReader reader, str union.VariantTypes = variantTypes; union.Decorators = decorators ?? []; union.External = external; + union.Experimental = experimental; union.IsExactName = isExactName; return union; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/TypeSpecInputNullableTypeConverter.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/TypeSpecInputNullableTypeConverter.cs index 55c6e6d2d1a..f81a0beb230 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/TypeSpecInputNullableTypeConverter.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/TypeSpecInputNullableTypeConverter.cs @@ -27,13 +27,15 @@ public static InputNullableType CreateNullableType(ref Utf8JsonReader reader, st InputType? valueType = null; IReadOnlyList? decorators = null; InputExternalTypeMetadata? external = null; + InputExperimentalDetails? experimental = null; while (reader.TokenType != JsonTokenType.EndObject) { var isKnownProperty = reader.TryReadReferenceId(ref id) || reader.TryReadString("name", ref name) || reader.TryReadComplexType("type", options, ref valueType) || reader.TryReadComplexType("decorators", options, ref decorators) - || reader.TryReadComplexType("external", options, ref external); + || reader.TryReadComplexType("external", options, ref external) + || reader.TryReadComplexType("experimental", options, ref experimental); if (!isKnownProperty) { @@ -46,7 +48,8 @@ public static InputNullableType CreateNullableType(ref Utf8JsonReader reader, st var nullableType = new InputNullableType(valueType) { Decorators = decorators ?? [], - External = external + External = external, + Experimental = experimental }; if (id != null) { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/test/TypeSpecInputConverterTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/test/TypeSpecInputConverterTests.cs index eda6eedab83..9395368f15f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/test/TypeSpecInputConverterTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/test/TypeSpecInputConverterTests.cs @@ -10,6 +10,41 @@ namespace Microsoft.TypeSpec.Generator.Input.Tests { public class TypeSpecInputConverterTests { + [TestCase("model", "\"properties\": []")] + [TestCase("enum", "\"values\": [], \"valueType\": {\"kind\":\"string\"}")] + [TestCase("string", "\"crossLanguageDefinitionId\": \"External.Value\"")] + [TestCase("union", "\"variantTypes\": [{\"kind\":\"string\"}, {\"kind\":\"int32\"}]")] + [TestCase("array", "\"valueType\": {\"kind\":\"string\"}")] + [TestCase("dict", "\"keyType\": {\"kind\":\"string\"}, \"valueType\": {\"kind\":\"string\"}")] + [TestCase("nullable", "\"type\": {\"kind\":\"string\"}")] + [TestCase("utcDateTime", "\"crossLanguageDefinitionId\":\"External.Value\", \"encode\":\"rfc3339\", \"wireType\":{\"kind\":\"string\"}")] + [TestCase("duration", "\"crossLanguageDefinitionId\":\"External.Value\", \"encode\":\"ISO8601\", \"wireType\":{\"kind\":\"string\"}")] + public void LoadsExperimentalExternalTypes(string kind, string typeProperties) + { + var content = $$""" + { + "name": "Test", + "models": [{ + "$id": "wrapper", "name": "Wrapper", + "properties": [{ + "$id": "property", "name": "value", + "type": { + "$id": "external", "kind": "{{kind}}", "name": "External", + {{typeProperties}}, + "external": {"identity":"External.Value"}, + "experimental": {"diagnosticId":"EXTERNAL001","dependsOn":["DEP001"]} + } + }] + }] + } + """; + var type = TypeSpecSerialization.Deserialize(content)!.Models.Single().Properties.Single().Type; + Assert.AreEqual("External.Value", type.External?.Identity); + Assert.AreEqual("EXTERNAL001", type.Experimental?.DiagnosticId); + CollectionAssert.AreEqual(new[] { "DEP001" }, type.Experimental!.DependsOn); + Assert.IsNull(InputPrimitiveType.String.Experimental); + } + [TestCase(true)] [TestCase(false)] public void LoadsExperimentalTypesAndMembers(bool annotated) diff --git a/packages/http-client-csharp/readme.md b/packages/http-client-csharp/readme.md index 3c42389e2cb..e57a28ad14b 100644 --- a/packages/http-client-csharp/readme.md +++ b/packages/http-client-csharp/readme.md @@ -68,8 +68,10 @@ generic arguments, and implementation references; neither suppresses diagnostics consumer code. Dependencies identify diagnostics rather than individual types: one diagnostic can apply -to multiple types or members, including those defined in external libraries. External -experiments still require explicit `dependsOn` entries; they are not discovered by reflection. +to multiple types or members, including those defined in external libraries. For externally +mapped types, source `@experimental` metadata is retained for generated-reference suppressions; +the emitter does not generate the external declaration or add attributes to its library. +Other external experiments require explicit `dependsOn` entries. They are not discovered by reflection. Both metadata fields are optional. Without `diagnosticId`, no public experimental attribute is added. Without `dependsOn`, no additional dependency diagnostics are requested; generated @@ -82,7 +84,7 @@ An `ExperimentalAttribute` on a customized partial client or method takes preced the generated attribute. Some TypeSpec declarations have no corresponding C# declaration, such as scalars or unions -mapped to built-in C# types. C# also does not allow `ExperimentalAttribute` on parameters. +erased to built-in C# types without an explicit external mapping. C# also does not allow `ExperimentalAttribute` on parameters. The emitter reports `experimental-target-not-supported` for these annotations instead of silently dropping them or assigning their diagnostic to an unrelated API. From 0594eac41c7e87a827dc148bb0eea1c0a650ce40 Mon Sep 17 00:00:00 2001 From: jolov Date: Fri, 25 Sep 2026 21:08:01 -0700 Subject: [PATCH 8/8] test(http-client-csharp): clarify experimental union metadata Cover experimental models referenced by unions and retain rejection of annotations on erased variants. Clarify the declaration target and decorator syntax in the usage docs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../http-client-csharp/.tspd/docs/usage.md | 6 +- .../emitter/test/Unit/decorator-list.test.ts | 93 +++++++++++++++++++ packages/http-client-csharp/readme.md | 6 +- 3 files changed, 103 insertions(+), 2 deletions(-) diff --git a/packages/http-client-csharp/.tspd/docs/usage.md b/packages/http-client-csharp/.tspd/docs/usage.md index e245185dda3..df102df83f2 100644 --- a/packages/http-client-csharp/.tspd/docs/usage.md +++ b/packages/http-client-csharp/.tspd/docs/usage.md @@ -9,7 +9,7 @@ For detailed instructions on how to customize the generated C# code, see the [Cu ### Experimental types and members -Use `TypeSpec.HttpClient.@experimental` to assign a public diagnostic ID to a generated +Use `@TypeSpec.HttpClient.experimental` to assign a public diagnostic ID to a generated type or member and identify experiments used by its implementation: ```typespec @@ -76,6 +76,10 @@ erased to built-in C# types without an explicit external mapping. C# also does n The emitter reports `experimental-target-not-supported` for these annotations instead of silently dropping them or assigning their diagnostic to an unrelated API. +Models referenced by a union retain their own experimental metadata. Annotate the model +declaration, not the union variant that references it: a model variant has no separate C# +declaration on which to place an attribute. + Graduation is an explicit source change: dependencies becoming generally available, or removing entries from `dependsOn`, does not remove `[Experimental("C")]`. Remove the declaration's `@experimental` decorator when the public API is ready to graduate. diff --git a/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts b/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts index 4fae7562c1e..112070d2712 100644 --- a/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts @@ -159,6 +159,75 @@ describe("Test emitting decorator list", () => { }); }); + describe.each([ + { + name: "named model variants", + declaration: "union Choice { preview: Preview, stable: Stable }", + type: "Choice", + }, + { + name: "unnamed model variants", + declaration: "union Choice { Preview, Stable }", + type: "Choice", + }, + { + name: "inline model variants", + declaration: "", + type: "Preview | Stable", + }, + { + name: "nullable model variants", + declaration: "union Choice { preview: Preview, stable: Stable, null }", + type: "Choice", + }, + { + name: "a nullable model", + declaration: "union Choice { preview: Preview, null }", + type: "Choice", + }, + { + name: "mixed model and scalar variants", + declaration: "union Choice { preview: Preview, text: string }", + type: "Choice", + }, + ])("experimental models in $name", ({ declaration, type }) => { + it.each([ + { scope: "@typespec/http-client-csharp", applies: true }, + { scope: "!other-emitter", applies: true }, + { scope: "other-emitter", applies: false }, + ])("preserves model metadata scoped to $scope", async ({ scope, applies }) => { + const program = await typeSpecCompile( + ` + @TypeSpec.HttpClient.experimental(#{ + emitterScope: "${scope}", diagnosticId: "MODEL001", dependsOn: #["DEP001"] + }) + model Preview { value: string; } + model Stable { count: int32; } + ${declaration} + model Wrapper { value: ${type}; } + op read(): Wrapper; + `, + runner, + { IsHttpClientNeeded: true }, + ); + expectDiagnosticEmpty(program.diagnostics); + const sdkContext = await createCSharpSdkContext(createEmitterContext(program)); + const [root, diagnostics] = createModel(sdkContext); + expectDiagnosticEmpty(diagnostics); + const preview = root.models.find((model) => model.name === "Preview"); + const wrapper = root.models.find((model) => model.name === "Wrapper"); + ok(preview); + ok(wrapper); + deepStrictEqual( + preview.experimental, + applies ? { diagnosticId: "MODEL001", dependsOn: ["DEP001"] } : undefined, + ); + strictEqual(wrapper.experimental, undefined); + strictEqual(wrapper.properties[0].experimental, undefined); + strictEqual(wrapper.properties[0].type.experimental, undefined); + }); + }); + it.each([ `@TypeSpec.HttpClient.experimental(#{ diagnosticId: "SCALAR001" }) scalar CustomString extends string; op read(): CustomString;`, @@ -170,6 +239,30 @@ describe("Test emitting decorator list", () => { @TypeSpec.HttpClient.experimental(#{ diagnosticId: "VARIANT001" }) text: string, count: int32 } op read(): Choice;`, + `model Preview { value: string; } + model Stable { count: int32; } + union Choice { + @TypeSpec.HttpClient.experimental(#{ diagnosticId: "VARIANT001" }) + preview: Preview, stable: Stable + } + model Wrapper { value: Choice; } + op read(): Wrapper;`, + `@TypeSpec.HttpClient.experimental(#{ diagnosticId: "MODEL001" }) + model Preview { value: string; } + model Stable { count: int32; } + union Choice { + @TypeSpec.HttpClient.experimental(#{ diagnosticId: "VARIANT001" }) + preview: Preview, stable: Stable + } + model Wrapper { value: Choice; } + op read(): Wrapper;`, + `model Preview { value: string; } + union Choice { + @TypeSpec.HttpClient.experimental(#{ diagnosticId: "VARIANT001" }) + preview: Preview, null + } + model Wrapper { value: Choice; } + op read(): Wrapper;`, ])("reports annotations with no supported C# declaration", async (code) => { const program = await typeSpecCompile(code, runner, { IsHttpClientNeeded: true }); const sdkContext = await createCSharpSdkContext(createEmitterContext(program)); diff --git a/packages/http-client-csharp/readme.md b/packages/http-client-csharp/readme.md index e57a28ad14b..48e54ace296 100644 --- a/packages/http-client-csharp/readme.md +++ b/packages/http-client-csharp/readme.md @@ -21,7 +21,7 @@ For detailed instructions on how to customize the generated C# code, see the [Cu ### Experimental types and members -Use `TypeSpec.HttpClient.@experimental` to assign a public diagnostic ID to a generated +Use `@TypeSpec.HttpClient.experimental` to assign a public diagnostic ID to a generated type or member and identify experiments used by its implementation: ```typespec @@ -88,6 +88,10 @@ erased to built-in C# types without an explicit external mapping. C# also does n The emitter reports `experimental-target-not-supported` for these annotations instead of silently dropping them or assigning their diagnostic to an unrelated API. +Models referenced by a union retain their own experimental metadata. Annotate the model +declaration, not the union variant that references it: a model variant has no separate C# +declaration on which to place an attribute. + Graduation is an explicit source change: dependencies becoming generally available, or removing entries from `dependsOn`, does not remove `[Experimental("C")]`. Remove the declaration's `@experimental` decorator when the public API is ready to graduate.