diff --git a/packages/http-client-csharp/emitter/src/lib/decorators.ts b/packages/http-client-csharp/emitter/src/lib/decorators.ts index dfe7bd18522..1501845ef5d 100644 --- a/packages/http-client-csharp/emitter/src/lib/decorators.ts +++ b/packages/http-client-csharp/emitter/src/lib/decorators.ts @@ -14,6 +14,7 @@ import type { import { setTypeSpecNamespace } from "@typespec/compiler"; import type { DynamicModelDecorator } from "../../../generated-defs/TypeSpec.HttpClient.CSharp.js"; import type { ExternalDocs } from "../type/external-docs.js"; +import type { InputExperimentalDetails } from "../type/input-operation.js"; /** * The fully qualified decorator name pattern for the dynamicModel decorator. @@ -21,6 +22,57 @@ import type { ExternalDocs } from "../type/external-docs.js"; * @beta */ export const DYNAMIC_MODEL_DECORATOR_PATTERN = "TypeSpec\\.HttpClient\\.CSharp\\.@dynamicModel"; +export const EXPERIMENTAL_DECORATOR_PATTERN = "TypeSpec\\.HttpClient\\.@experimental"; +const experimentalDecoratorName = "TypeSpec.HttpClient.@experimental"; +const csharpEmitterName = "@typespec/http-client-csharp"; + +interface ExperimentalDecoratorOptions { + emitterScope?: string; + diagnosticId?: string; + dependsOn?: unknown[]; +} + +export function getExperimentalDetails( + decorators: readonly { name: string; arguments: Record }[], +): InputExperimentalDetails | undefined { + const decorator = decorators.find((item) => item.name === experimentalDecoratorName); + if (!decorator) { + return undefined; + } + + const options = decorator.arguments.options as ExperimentalDecoratorOptions | undefined; + // TCGC filters a top-level `scope` argument, but @experimental carries + // `emitterScope` inside its options object. + if (!isEmitterScopeApplicable(options?.emitterScope)) { + return undefined; + } + + return { + diagnosticId: typeof options?.diagnosticId === "string" ? options.diagnosticId : undefined, + dependsOn: (options?.dependsOn ?? []).filter( + (diagnosticId): diagnosticId is string => typeof diagnosticId === "string", + ), + }; +} + +function isEmitterScopeApplicable(emitterScope: string | undefined): boolean { + if (!emitterScope) { + return true; + } + + const scopes = emitterScope + .split(",") + .map((scope) => scope.trim()) + .filter((scope) => scope.length > 0); + const excludedScopes = scopes + .filter((scope) => scope.startsWith("!")) + .map((scope) => scope.slice(1)); + if (excludedScopes.length > 0) { + return !excludedScopes.includes(csharpEmitterName); + } + + return scopes.includes(csharpEmitterName); +} const externalDocsKey = Symbol("externalDocs"); export function getExternalDocs(context: SdkContext, entity: Type): ExternalDocs | undefined { 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..1832eb66add 100644 --- a/packages/http-client-csharp/emitter/src/lib/operation-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/operation-converter.ts @@ -76,7 +76,7 @@ import type { OperationResponse } from "../type/operation-response.js"; import { RequestLocation } from "../type/request-location.js"; import { parseHttpRequestMethod } from "../type/request-method.js"; import { ResponseLocation } from "../type/response-location.js"; -import { getExternalDocs, getOperationId } from "./decorators.js"; +import { getExperimentalDetails, getExternalDocs, getOperationId } from "./decorators.js"; import { fromSdkHttpExamples } from "./example-converter.js"; import { createDiagnostic } from "./lib.js"; import { fromSdkType } from "./type-converter.js"; @@ -252,6 +252,7 @@ export function fromSdkServiceMethodOperation( namespace: method.__raw?.namespace ? getClientNamespace(sdkContext, method.__raw.namespace) : undefined, + experimental: getExperimentalDetails(method.decorators), }; sdkContext.__typeCache.updateSdkOperationReferences(method.operation, operation); diff --git a/packages/http-client-csharp/emitter/src/options.ts b/packages/http-client-csharp/emitter/src/options.ts index 31bbc75d483..44592285481 100644 --- a/packages/http-client-csharp/emitter/src/options.ts +++ b/packages/http-client-csharp/emitter/src/options.ts @@ -2,7 +2,10 @@ import type { CreateSdkContextOptions } from "@azure-tools/typespec-client-gener import { UnbrandedSdkEmitterOptions } from "@azure-tools/typespec-client-generator-core"; import type { EmitContext, JSONSchemaType } from "@typespec/compiler"; import { _defaultGeneratorName } from "./constants.js"; -import { DYNAMIC_MODEL_DECORATOR_PATTERN } from "./lib/decorators.js"; +import { + DYNAMIC_MODEL_DECORATOR_PATTERN, + EXPERIMENTAL_DECORATOR_PATTERN, +} from "./lib/decorators.js"; import { LoggerLevel } from "./lib/logger-level.js"; /** @@ -176,7 +179,7 @@ export const defaultOptions = { logLevel: LoggerLevel.INFO, "generator-name": _defaultGeneratorName, "sdk-context-options": { - additionalDecorators: [DYNAMIC_MODEL_DECORATOR_PATTERN], + additionalDecorators: [DYNAMIC_MODEL_DECORATOR_PATTERN, EXPERIMENTAL_DECORATOR_PATTERN], }, }; 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/experimental-decorator.test.ts b/packages/http-client-csharp/emitter/test/Unit/experimental-decorator.test.ts new file mode 100644 index 00000000000..d75ccdf50b1 --- /dev/null +++ b/packages/http-client-csharp/emitter/test/Unit/experimental-decorator.test.ts @@ -0,0 +1,59 @@ +import { deepStrictEqual, strictEqual } from "assert"; +import { describe, it } from "vitest"; +import { getExperimentalDetails } from "../../src/lib/decorators.js"; + +describe("experimental decorator metadata", () => { + it("extracts diagnostic and dependency identifiers", () => { + const details = getExperimentalDetails([ + { + name: "TypeSpec.HttpClient.@experimental", + arguments: { + options: { + emitterScope: "@typespec/http-client-csharp", + diagnosticId: "C", + dependsOn: ["A", "B"], + }, + }, + }, + ]); + + deepStrictEqual(details, { + diagnosticId: "C", + dependsOn: ["A", "B"], + }); + }); + + it("ignores metadata scoped to another emitter", () => { + const details = getExperimentalDetails([ + { + name: "TypeSpec.HttpClient.@experimental", + arguments: { + options: { + emitterScope: "other-emitter", + diagnosticId: "C", + }, + }, + }, + ]); + + strictEqual(details, undefined); + }); + + it("applies unscoped metadata", () => { + const details = getExperimentalDetails([ + { + name: "TypeSpec.HttpClient.@experimental", + arguments: { + options: { + diagnosticId: "C", + }, + }, + }, + ]); + + deepStrictEqual(details, { + diagnosticId: "C", + dependsOn: [], + }); + }); +}); 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 747e51bcc2f..677489c2387 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; @@ -239,13 +240,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, 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 fb0bb6e6025..0787921f8b8 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 @@ -229,10 +229,11 @@ private ScmMethodProvider BuildConvenienceMethod(MethodProvider protocolMethod, GetConvenienceMethodModifiers(protocolMethod.Signature.Modifiers, signatureParameters), GetResponseType(ServiceMethod.Operation.Responses, true, isAsync, out _), null, - signatureParameters, - Attributes: BuildConvenienceMethodAttributes()); + signatureParameters); } + AddMethodAttributes(methodSignature, BuildConvenienceMethodAttributes()); + // Recompute the response body type so we can branch the body accordingly. GetResponseType(ServiceMethod.Operation.Responses, true, isAsync, out var responseBodyType); var streamingResponse = _streamingResponse.Value; @@ -315,6 +316,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) { @@ -938,16 +940,28 @@ private static bool IsConvertibleFromBinaryData(CSharpType type) type.Equals(typeof(TimeSpan?)); } - private IReadOnlyList? BuildConvenienceMethodAttributes() + private IReadOnlyList BuildConvenienceMethodAttributes() { + List attributes = [.. ExperimentalApiHelpers.BuildAttributes(ServiceMethod.Operation)]; var bodyInputParam = ServiceMethod.Parameters.FirstOrDefault(p => p.Location == InputRequestLocation.Body); - if (bodyInputParam?.Type is InputModelType bodyModel + if (attributes.Count == 0 + && bodyInputParam?.Type is InputModelType bodyModel && bodyModel.Usage.HasFlag(InputModelTypeUsage.MultipartFormData)) { - return [new AttributeStatement(typeof(ExperimentalAttribute), [Literal(ScmModelProvider.FileBinaryContentDiagnosticId)])]; + attributes.Add(new AttributeStatement(typeof(ExperimentalAttribute), [Literal(ScmModelProvider.FileBinaryContentDiagnosticId)])); } - return null; + return attributes; + } + + private static void AddMethodAttributes( + MethodSignature signature, + IReadOnlyList attributes) + { + if (attributes.Count > 0) + { + signature.Update(attributes: [.. signature.Attributes, .. attributes]); + } } private IReadOnlyList GetProtocolMethodArguments(Dictionary declarations) @@ -1260,6 +1274,8 @@ private ScmMethodProvider BuildProtocolMethod(MethodProvider createRequestMethod bodyParameters = parameters; } + AddMethodAttributes(methodSignature, ExperimentalApiHelpers.BuildAttributes(ServiceMethod.Operation)); + TypeProvider? collection = null; MethodBodyStatement[] methodBody; if (_pagingServiceMethod != null) @@ -1297,6 +1313,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..698151a57a7 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Utilities/ExperimentalApiHelpers.cs @@ -0,0 +1,49 @@ +// 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.ClientModel.Utilities +{ + internal static class ExperimentalApiHelpers + { + private const string DependencySuppressionJustification = + "This method depends on experimental functionality."; + + public static IReadOnlyList BuildAttributes(InputOperation operation) + { + var diagnosticId = operation.Experimental?.DiagnosticId; + return string.IsNullOrWhiteSpace(diagnosticId) + ? [] + : [new AttributeStatement(typeof(ExperimentalAttribute), [Literal(diagnosticId)])]; + } + + public static void AddDependencySuppressions(MethodProvider method, InputOperation operation) + { + var dependencies = operation.Experimental?.DependsOn; + if (dependencies is null || dependencies.Count == 0) + { + return; + } + + method.Update(suppressions: + [ + .. dependencies + .Where(diagnosticId => !string.IsNullOrWhiteSpace(diagnosticId)) + .Distinct(StringComparer.Ordinal) + .Select(diagnosticId => new SuppressionStatement( + null, + Literal(diagnosticId), + DependencySuppressionJustification)), + .. method.Suppressions + ]); + } + } +} 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 92a5c820965..7e04bf756a3 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 @@ -1767,6 +1767,66 @@ public void TestMethodTypeIdentification() Assert.AreEqual(ScmMethodKind.CreateRequest, createRequestMethod.Kind); } + [Test] + public void ExperimentalOperationGeneratesAttributeAndDependencySuppressions() + { + MockHelpers.LoadMockGenerator(); + + var inputOperation = InputFactory.Operation( + "Bar", + experimental: new InputExperimentalDetails("C", ["A", "B"])); + var inputServiceMethod = InputFactory.BasicServiceMethod("Bar", inputOperation); + var inputClient = InputFactory.Client("TestClient", methods: [inputServiceMethod]); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient); + + var methodCollection = new ScmMethodProviderCollection(inputServiceMethod, client!); + + foreach (var method in methodCollection) + { + using var writer = new CodeWriter(); + writer.WriteMethod(method); + var code = writer.ToString(false); + + StringAssert.Contains( + "[global::System.Diagnostics.CodeAnalysis.ExperimentalAttribute(\"C\")]", + code); + StringAssert.Contains("#pragma warning disable A", code); + StringAssert.Contains("#pragma warning disable B", code); + StringAssert.Contains("#pragma warning restore A", code); + StringAssert.Contains("#pragma warning restore B", code); + } + + using var createRequestWriter = new CodeWriter(); + createRequestWriter.WriteMethod(client!.RestClient.GetCreateRequestMethod(inputOperation)); + var createRequestCode = createRequestWriter.ToString(false); + StringAssert.DoesNotContain("ExperimentalAttribute", createRequestCode); + StringAssert.Contains("#pragma warning disable A", createRequestCode); + StringAssert.Contains("#pragma warning disable B", createRequestCode); + } + + [Test] + public void OperationWithoutExperimentalMetadataDoesNotGenerateExperimentalCode() + { + MockHelpers.LoadMockGenerator(); + + var inputOperation = InputFactory.Operation("Bar"); + var inputServiceMethod = InputFactory.BasicServiceMethod("Bar", inputOperation); + var inputClient = InputFactory.Client("TestClient", methods: [inputServiceMethod]); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient); + + var methodCollection = new ScmMethodProviderCollection(inputServiceMethod, client!); + + foreach (var method in methodCollection) + { + using var writer = new CodeWriter(); + writer.WriteMethod(method); + var code = writer.ToString(false); + + StringAssert.DoesNotContain("ExperimentalAttribute", code); + StringAssert.DoesNotContain("#pragma warning disable A", code); + } + } + [Test] public async Task CollectionResultDefinitionAddedEvenWhenPagingMethodsCustomized() { 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..ebe6f1880bb --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputExperimentalDetails.cs @@ -0,0 +1,27 @@ +// 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 + { + public InputExperimentalDetails() + { + } + + public InputExperimentalDetails(string? diagnosticId, IReadOnlyList dependsOn) + { + DiagnosticId = diagnosticId; + DependsOn = dependsOn; + } + + [JsonPropertyName("diagnosticId")] + public string? DiagnosticId { get; init; } + + [JsonPropertyName("dependsOn")] + public IReadOnlyList DependsOn { get; init; } = []; + } +} 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..ed638d50dd4 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 @@ -29,7 +29,8 @@ public InputOperation( bool generateProtocolMethod, bool generateConvenienceMethod, string crossLanguageDefinitionId, - string? ns) + string? ns, + InputExperimentalDetails? experimental = null) { Name = name; ResourceName = resourceName; @@ -49,6 +50,7 @@ public InputOperation( GenerateConvenienceMethod = generateConvenienceMethod; CrossLanguageDefinitionId = crossLanguageDefinitionId; Namespace = ns; + Experimental = experimental; } public InputOperation() : this( @@ -102,6 +104,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"; 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 1d9c03613e7..5a9f8a2e75a 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 @@ -9,6 +9,45 @@ namespace Microsoft.TypeSpec.Generator.Input.Tests { public class TypeSpecInputConverterTests { + [Test] + public void LoadsExperimentalOperationDetails() + { + const string content = """ + { + "$id": "1", + "name": "bar", + "parameters": [], + "responses": [], + "httpMethod": "GET", + "uri": "", + "path": "", + "bufferResponse": true, + "generateProtocolMethod": true, + "generateConvenienceMethod": true, + "crossLanguageDefinitionId": "Test.bar", + "experimental": { + "diagnosticId": "C", + "dependsOn": ["A", "B"] + } + } + """; + var referenceHandler = new TypeSpecReferenceHandler(); + var options = new JsonSerializerOptions + { + ReferenceHandler = referenceHandler, + Converters = + { + new InputOperationConverter(referenceHandler), + } + }; + + var operation = JsonSerializer.Deserialize(content, options); + + Assert.IsNotNull(operation?.Experimental); + Assert.AreEqual("C", operation!.Experimental!.DiagnosticId); + CollectionAssert.AreEqual(new[] { "A", "B" }, operation.Experimental.DependsOn); + } + [Test] public void LoadsPagingWithNextLink() { 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 3bd508eb2e7..78186849554 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 @@ -733,7 +733,8 @@ public static InputOperation Operation( string? ns = null, bool isExactName = false, bool generateProtocolMethod = true, - bool bufferResponse = true) + bool bufferResponse = true, + InputExperimentalDetails? experimental = null) { var operation = new InputOperation( name, @@ -753,7 +754,8 @@ public static InputOperation Operation( generateProtocolMethod, generateConvenienceMethod, name, - ns); + ns, + experimental); operation.OriginalName = name; operation.IsExactName = isExactName; return operation;