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 25fa85839f1..8f197c6b458 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 @@ -500,7 +500,7 @@ private IEnumerable AppendHeaderParameters(HttpRequestApi r CSharpType? type; SerializationFormat? serializationFormat; ValueExpression? valueExpression; - GetParamInfo(paramMap, operation, inputHeaderParameter, out type, out serializationFormat, out valueExpression); + GetParamInfo(paramMap, operation, inputHeaderParameter, out type, out serializationFormat, out valueExpression, out _, out _); if (valueExpression == null) { continue; @@ -576,7 +576,7 @@ private List AppendQueryParameters(ScopedApi uri, InputOper InputOperation operation, bool isNextLinkRequest = false) { - GetParamInfo(paramMap, operation, inputQueryParameter, out var paramType, out var serializationFormat, out var valueExpression); + GetParamInfo(paramMap, operation, inputQueryParameter, out var paramType, out var serializationFormat, out var valueExpression, out _, out _); if (valueExpression == null) { return null; @@ -907,18 +907,13 @@ private void AddUriSegments( var paramEndIndex = pathSpan.IndexOf('}'); var paramName = pathSpan.Slice(0, paramEndIndex).ToString(); - /* An optional path parameter that is null must not leave a dangling - * path separator behind. For example "/foo/{bar}/{baz}" with an absent - * optional "baz" should produce "/foo/{bar}", not "/foo/{bar}/". When the - * upcoming parameter is optional, defer the trailing '/' of the preceding - * literal so it is only written together with the parameter value inside - * the null check below. - */ + bool willEmitNullGuard = inputParamMap.TryGetValue(paramName, out var optionalCheckParam) + && optionalCheckParam is InputPathParameter or InputEndpointParameter + && !optionalCheckParam.IsRequired; var pathLiteral = path.ToString(); bool separatorDeferred = false; if (pathLiteral.EndsWith('/') - && inputParamMap.TryGetValue(paramName, out var optionalCheckParam) - && optionalCheckParam is InputPathParameter { IsRequired: false }) + && willEmitNullGuard) { pathLiteral = pathLiteral.Substring(0, pathLiteral.Length - 1); separatorDeferred = true; @@ -932,17 +927,19 @@ private void AddUriSegments( CSharpType? type; SerializationFormat? serializationFormat; ValueExpression? valueExpression; + ValueExpression? rawValueExpression; + bool isSerialized; InputParameter? inputParam = null; if (isClientParameter) { - GetParamInfo(paramMap[paramName], out type, out serializationFormat, out valueExpression); + GetParamInfo(paramMap[paramName], out type, out serializationFormat, out valueExpression, out rawValueExpression, out isSerialized); } else { inputParam = inputParamMap[paramName]; - if (inputParam is InputPathParameter || inputParam is InputEndpointParameter) + if (inputParam is InputPathParameter or InputEndpointParameter) { - GetParamInfo(paramMap, operation, inputParam, out type, out serializationFormat, out valueExpression); + GetParamInfo(paramMap, operation, inputParam, out type, out serializationFormat, out valueExpression, out rawValueExpression, out isSerialized); if (valueExpression == null) { break; @@ -957,17 +954,49 @@ private void AddUriSegments( ValueExpression[] toStringParams = format is null ? [] : [Literal(format)]; InputPathParameter? inputPathParameter = inputParam as InputPathParameter; bool escape = !inputPathParameter?.SkipUrlEncoding ?? true; + /* The null check must always test the raw parameter or field, never the + * serialized form: `x.ToString("R") != null` is vacuously true and + * double-evaluates the serialization. + */ + ValueExpression nullCheckExpression = rawValueExpression ?? valueExpression; if (type?.OutputType.IsCollection == true) { - statements.Add(uri.AppendPathDelimited(valueExpression, GetFormatEnumValue(serializationFormat), escape).Terminate()); + MethodBodyStatement collectionStatement = uri.AppendPathDelimited(valueExpression, GetFormatEnumValue(serializationFormat), escape).Terminate(); + if (willEmitNullGuard) + { + bool shouldPrependWithPathSeparator = separatorDeferred || (path.Length > 0 && path[^1] != '/'); + List appendPathStatements = shouldPrependWithPathSeparator + ? [uri.AppendPath(Literal("/"), false).Terminate(), collectionStatement] + : [collectionStatement]; + collectionStatement = BuildQueryOrHeaderOrPathParameterNullCheck( + type, + nullCheckExpression, + appendPathStatements); + } + statements.Add(collectionStatement); } else { - valueExpression = type?.Equals(typeof(string)) == true + if (type is { IsNullable: true, IsValueType: true, IsEnum: false }) + { + valueExpression = willEmitNullGuard + ? valueExpression.Property(nameof(Nullable.Value)) + : valueExpression.NullConditional(); + } + else if (type is { IsNullable: true, IsEnum: true } && willEmitNullGuard) + { + /* GetParamInfo serialized the nullable enum through a null-conditional + * access. The guard makes that redundant, so re-serialize from the + * unwrapped value instead. + */ + valueExpression = type.ToSerial(nullCheckExpression.Property(nameof(Nullable.Value))); + isSerialized = true; + } + valueExpression = isSerialized || type?.Equals(typeof(string)) == true ? valueExpression : valueExpression.Invoke(nameof(ToString), toStringParams); MethodBodyStatement statement; - if (inputParam?.IsRequired == false) + if (willEmitNullGuard) { bool shouldPrependWithPathSeparator = separatorDeferred || (path.Length > 0 && path[^1] != '/'); List appendPathStatements = shouldPrependWithPathSeparator @@ -975,7 +1004,7 @@ private void AddUriSegments( : [uri.AppendPath(valueExpression, escape).Terminate()]; statement = BuildQueryOrHeaderOrPathParameterNullCheck( type, - valueExpression, + nullCheckExpression, appendPathStatements); } else @@ -1027,12 +1056,18 @@ private static void AppendLiteralSegment(ScopedApi uri, string literal, List + /// The unformatted parameter or field access backing . Null checks must be + /// written against this rather than , which may already be serialized. + /// + /// Whether has already had serialization applied. + private void GetParamInfo(ParameterProviderMap paramMap, InputOperation operation, InputParameter inputParam, out CSharpType? type, out SerializationFormat? serializationFormat, out ValueExpression? valueExpression, out ValueExpression? rawValueExpression, out bool isSerialized) { type = IsContentTypeParameter(inputParam, includeInputHeaderParameter: false) ? null : ScmCodeModelGenerator.Instance.TypeFactory.CreateCSharpType(inputParam.Type); serializationFormat = null; + isSerialized = false; if (inputParam.IsApiVersion && ClientProvider.IsMultiServiceClient) { @@ -1042,6 +1077,7 @@ private void GetParamInfo(ParameterProviderMap paramMap, InputOperation operatio type = apiVersionField.Type; serializationFormat = apiVersionField.WireInfo?.SerializationFormat; valueExpression = apiVersionField; + rawValueExpression = apiVersionField; return; } } @@ -1049,45 +1085,53 @@ private void GetParamInfo(ParameterProviderMap paramMap, InputOperation operatio if (inputParam.Scope == InputParameterScope.Constant && !(operation.IsMultipartFormData && inputParam is InputHeaderParameter headerParameter && headerParameter.IsContentType)) { valueExpression = Literal((inputParam.Type as InputLiteralType)?.Value); + rawValueExpression = valueExpression; serializationFormat = ScmCodeModelGenerator.Instance.TypeFactory.GetSerializationFormat(inputParam.Type); } else if (TryGetAcceptHeaderWithMultipleContentTypes(inputParam, operation, out var contentTypes)) { string joinedContentTypes = string.Join(", ", contentTypes); valueExpression = Literal(joinedContentTypes); + rawValueExpression = valueExpression; serializationFormat = ScmCodeModelGenerator.Instance.TypeFactory.GetSerializationFormat(inputParam.Type); } else if (TryGetSpecialHeaderParam(inputParam, out var parameterProvider)) { valueExpression = parameterProvider.DefaultValue!; + rawValueExpression = valueExpression; serializationFormat = ScmCodeModelGenerator.Instance.TypeFactory.GetSerializationFormat(inputParam.Type); } else { if (paramMap.TryGetValue(inputParam.Name, out var paramProvider)) { - GetParamInfo(paramProvider, out type, out serializationFormat, out valueExpression); + GetParamInfo(paramProvider, out type, out serializationFormat, out valueExpression, out var raw, out isSerialized); + rawValueExpression = raw; } else { type = null; valueExpression = null; + rawValueExpression = null; } } } - private static void GetParamInfo(ParameterProvider paramProvider, out CSharpType? type, out SerializationFormat? serializationFormat, out ValueExpression valueExpression) + private static void GetParamInfo(ParameterProvider paramProvider, out CSharpType? type, out SerializationFormat? serializationFormat, out ValueExpression valueExpression, out ValueExpression rawValueExpression, out bool isSerialized) { type = paramProvider.Field is null ? paramProvider.Type : paramProvider.Field.Type; + rawValueExpression = paramProvider.Field is null ? paramProvider : paramProvider.Field; if (type.IsEnum) { valueExpression = type.ToSerial(paramProvider); serializationFormat = SerializationFormat.Default; + isSerialized = true; } else { - valueExpression = paramProvider.Field is null ? paramProvider : paramProvider.Field; + valueExpression = rawValueExpression; serializationFormat = paramProvider.WireInfo.SerializationFormat; + isSerialized = false; } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs index 282e21350bf..b5863307f4b 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs @@ -1329,6 +1329,306 @@ public void TestBuildCreateRequestMethodWithPathParameters() Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); } + [TestCase(true)] + [TestCase(false)] + public void NullableDatePathParameterIsGuardedBeforeFormatting(bool isRequired) + { + var dateType = new InputDateTimeType( + DateTimeKnownEncoding.Rfc7231, + "utcDateTime", + "TypeSpec.utcDateTime", + InputPrimitiveType.String); + var operation = InputFactory.Operation( + "GetThing", + parameters: [InputFactory.PathParameter("requestOn", dateType, isRequired: isRequired)], + path: "/things/{requestOn}"); + var serviceMethod = InputFactory.BasicServiceMethod( + "GetThing", + operation, + parameters: + [ + InputFactory.MethodParameter( + "requestOn", + dateType, + isRequired: isRequired, + location: InputRequestLocation.Path) + ]); + var client = InputFactory.Client("TestClient", methods: [serviceMethod]); + var restClient = new ClientProvider(client).RestClient; + + var file = new TypeProviderWriter(restClient).Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(parameters: isRequired.ToString()), file.Content); + } + + [Test] + public void OptionalClientEnumPathParameterIsGuardedThenUnwrapped() + { + var enumType = InputFactory.StringEnum( + "Color", + [("Red", "red"), ("Blue", "blue")], + isExtensible: true); + var operation = InputFactory.Operation( + "GetThing", + parameters: [InputFactory.PathParameter("color", enumType, isRequired: false, scope: InputParameterScope.Client)], + path: "/things/{color}"); + var serviceMethod = InputFactory.BasicServiceMethod( + "GetThing", + operation); + var client = InputFactory.Client("TestClient", methods: [serviceMethod]); + var restClient = new ClientProvider(client).RestClient; + + var file = new TypeProviderWriter(restClient).Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } + + [Test] + public void OptionalMethodEnumPathParameterIsSerializedOnce() + { + var enumType = InputFactory.StringEnum( + "Color", + [("Red", "red"), ("Blue", "blue")], + isExtensible: true); + var operation = InputFactory.Operation( + "GetThing", + parameters: [InputFactory.PathParameter("color", enumType, isRequired: false)], + path: "/things/{color}"); + var serviceMethod = InputFactory.BasicServiceMethod( + "GetThing", + operation, + parameters: + [ + InputFactory.MethodParameter( + "color", + enumType, + isRequired: false, + location: InputRequestLocation.Path) + ]); + var client = InputFactory.Client("TestClient", methods: [serviceMethod]); + var restClient = new ClientProvider(client).RestClient; + + var file = new TypeProviderWriter(restClient).Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } + + [Test] + public void OptionalClientEnumPathParameterWithDistinctSerializedNameIsGuarded() + { + var enumType = InputFactory.StringEnum( + "Color", + [("Red", "red"), ("Blue", "blue")], + isExtensible: true); + var operation = InputFactory.Operation( + "GetThing", + parameters: [InputFactory.PathParameter("color", enumType, isRequired: false, serializedName: "colour", scope: InputParameterScope.Client)], + path: "/things/{colour}"); + var serviceMethod = InputFactory.BasicServiceMethod( + "GetThing", + operation); + var client = InputFactory.Client("TestClient", methods: [serviceMethod]); + var restClient = new ClientProvider(client).RestClient; + + var file = new TypeProviderWriter(restClient).Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } + + /* A required path parameter is never guarded, even when its type is nullable. + * Skipping the segment would silently target a different resource + * (e.g. "/things" instead of "/things/{name}"), so a null value is deliberately + * left to fail loudly inside ClientUriBuilder.AppendPath. Only optional path + * parameters are omitted from the URL when absent. + */ + [Test] + public void RequiredNullableStringPathParameterIsNotGuarded() + { + var nullableString = new InputNullableType(InputPrimitiveType.String); + var operation = InputFactory.Operation( + "GetThing", + parameters: [InputFactory.PathParameter("name", nullableString, isRequired: true)], + path: "/things/{name}"); + var serviceMethod = InputFactory.BasicServiceMethod( + "GetThing", + operation, + parameters: + [ + InputFactory.MethodParameter( + "name", + nullableString, + isRequired: true, + location: InputRequestLocation.Path) + ]); + var client = InputFactory.Client("TestClient", methods: [serviceMethod]); + var restClient = new ClientProvider(client).RestClient; + + var file = new TypeProviderWriter(restClient).Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } + + [Test] + public void RequiredNullableEnumPathParameterIsNotGuarded() + { + var enumType = InputFactory.StringEnum( + "Color", + [("Red", "red"), ("Blue", "blue")], + isExtensible: true); + var nullableEnum = new InputNullableType(enumType); + var operation = InputFactory.Operation( + "GetThing", + parameters: [InputFactory.PathParameter("color", nullableEnum, isRequired: true)], + path: "/things/{color}"); + var serviceMethod = InputFactory.BasicServiceMethod( + "GetThing", + operation, + parameters: + [ + InputFactory.MethodParameter( + "color", + nullableEnum, + isRequired: true, + location: InputRequestLocation.Path) + ]); + var client = InputFactory.Client("TestClient", methods: [serviceMethod]); + var restClient = new ClientProvider(client).RestClient; + + var file = new TypeProviderWriter(restClient).Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } + + // The client-scoped variant resolves to the nullable enum field rather than the + // flattened serialized primitive, so it exercises a different code path. + [Test] + public void RequiredNullableClientEnumPathParameterIsNotGuarded() + { + var enumType = InputFactory.StringEnum( + "Color", + [("Red", "red"), ("Blue", "blue")], + isExtensible: true); + var nullableEnum = new InputNullableType(enumType); + var operation = InputFactory.Operation( + "GetThing", + parameters: [InputFactory.PathParameter("color", nullableEnum, isRequired: true, scope: InputParameterScope.Client)], + path: "/things/{color}"); + var serviceMethod = InputFactory.BasicServiceMethod( + "GetThing", + operation); + var client = InputFactory.Client("TestClient", methods: [serviceMethod]); + var restClient = new ClientProvider(client).RestClient; + + var file = new TypeProviderWriter(restClient).Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } + + [Test] + public void RequiredNullableCollectionPathParameterIsNotGuarded() + { + var nullableArray = new InputNullableType(InputFactory.Array(InputPrimitiveType.String)); + var operation = InputFactory.Operation( + "GetThing", + parameters: [InputFactory.PathParameter("ids", nullableArray, isRequired: true)], + path: "/things/{ids}"); + var serviceMethod = InputFactory.BasicServiceMethod( + "GetThing", + operation, + parameters: + [ + InputFactory.MethodParameter( + "ids", + nullableArray, + isRequired: true, + location: InputRequestLocation.Path) + ]); + var client = InputFactory.Client("TestClient", methods: [serviceMethod]); + var restClient = new ClientProvider(client).RestClient; + + var file = new TypeProviderWriter(restClient).Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } + + // An optional collection path parameter previously had its separator deferred + // without a matching guard, producing "/thingsa,b" when the value was present. + [Test] + public void OptionalCollectionPathParameterIsGuarded() + { + var arrayType = InputFactory.Array(InputPrimitiveType.String); + var operation = InputFactory.Operation( + "GetThing", + parameters: [InputFactory.PathParameter("ids", arrayType, isRequired: false)], + path: "/things/{ids}"); + var serviceMethod = InputFactory.BasicServiceMethod( + "GetThing", + operation, + parameters: + [ + InputFactory.MethodParameter( + "ids", + arrayType, + isRequired: false, + location: InputRequestLocation.Path) + ]); + var client = InputFactory.Client("TestClient", methods: [serviceMethod]); + var restClient = new ClientProvider(client).RestClient; + + var file = new TypeProviderWriter(restClient).Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } + + [Test] + public void OptionalClientDatePathParameterIsGuardedBeforeFormatting() + { + var dateType = new InputDateTimeType( + DateTimeKnownEncoding.Rfc7231, + "utcDateTime", + "TypeSpec.utcDateTime", + InputPrimitiveType.String); + var operation = InputFactory.Operation( + "GetThing", + parameters: [InputFactory.PathParameter("requestOn", dateType, isRequired: false, scope: InputParameterScope.Client)], + path: "/things/{requestOn}"); + var serviceMethod = InputFactory.BasicServiceMethod( + "GetThing", + operation); + var client = InputFactory.Client("TestClient", methods: [serviceMethod]); + var restClient = new ClientProvider(client).RestClient; + + var file = new TypeProviderWriter(restClient).Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } + + // The guard covers InputEndpointParameter as well as InputPathParameter; a missing + // or stray separator in a server URL template is especially easy to overlook. + [Test] + public void OptionalEndpointParameterInServerTemplateIsGuarded() + { + const string serverTemplate = "{endpoint}/{region}"; + var regionParameter = InputFactory.EndpointParameter( + "region", + InputPrimitiveType.String, + isRequired: false, + isEndpoint: false, + serverUrlTemplate: serverTemplate, + scope: InputParameterScope.Client); + var endpointParameter = InputFactory.EndpointParameter( + "endpoint", + InputPrimitiveType.String, + isRequired: true, + serverUrlTemplate: serverTemplate); + var operation = InputFactory.Operation( + "GetThing", + uri: serverTemplate, + path: "/things", + parameters: [endpointParameter, regionParameter]); + var serviceMethod = InputFactory.BasicServiceMethod( + "GetThing", + operation); + var client = InputFactory.Client( + "TestClient", + methods: [serviceMethod], + parameters: [endpointParameter, regionParameter]); + var restClient = new ClientProvider(client).RestClient; + + var file = new TypeProviderWriter(restClient).Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } + // An optional trailing path parameter must not emit a dangling separator when null. // e.g. "/certificates/{certificateName}/{certificateVersion}" with a null version // should produce "/certificates/{name}", not "/certificates/{name}/". diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/NullableDatePathParameterIsGuardedBeforeFormatting(False).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/NullableDatePathParameterIsGuardedBeforeFormatting(False).cs new file mode 100644 index 00000000000..6e4a8a5786a --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/NullableDatePathParameterIsGuardedBeforeFormatting(False).cs @@ -0,0 +1,32 @@ +// + +#nullable disable + +using System; +using System.ClientModel.Primitives; + +namespace Sample +{ + public partial class TestClient + { + private static global::System.ClientModel.Primitives.PipelineMessageClassifier _pipelineMessageClassifier200; + + private static global::System.ClientModel.Primitives.PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= global::System.ClientModel.Primitives.PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); + + internal global::System.ClientModel.Primitives.PipelineMessage CreateGetThingRequest(global::System.DateTimeOffset? requestOn, global::System.ClientModel.Primitives.RequestOptions options) + { + global::Sample.ClientUriBuilder uri = new global::Sample.ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/things", false); + if ((requestOn != null)) + { + uri.AppendPath("/", false); + uri.AppendPath(requestOn.Value.ToString("R"), true); + } + global::System.ClientModel.Primitives.PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier200); + global::System.ClientModel.Primitives.PipelineRequest request = message.Request; + message.Apply(options); + return message; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/NullableDatePathParameterIsGuardedBeforeFormatting(True).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/NullableDatePathParameterIsGuardedBeforeFormatting(True).cs new file mode 100644 index 00000000000..bd73910a526 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/NullableDatePathParameterIsGuardedBeforeFormatting(True).cs @@ -0,0 +1,28 @@ +// + +#nullable disable + +using System; +using System.ClientModel.Primitives; + +namespace Sample +{ + public partial class TestClient + { + private static global::System.ClientModel.Primitives.PipelineMessageClassifier _pipelineMessageClassifier200; + + private static global::System.ClientModel.Primitives.PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= global::System.ClientModel.Primitives.PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); + + internal global::System.ClientModel.Primitives.PipelineMessage CreateGetThingRequest(global::System.DateTimeOffset requestOn, global::System.ClientModel.Primitives.RequestOptions options) + { + global::Sample.ClientUriBuilder uri = new global::Sample.ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/things/", false); + uri.AppendPath(requestOn.ToString("R"), true); + global::System.ClientModel.Primitives.PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier200); + global::System.ClientModel.Primitives.PipelineRequest request = message.Request; + message.Apply(options); + return message; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/OptionalClientDatePathParameterIsGuardedBeforeFormatting.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/OptionalClientDatePathParameterIsGuardedBeforeFormatting.cs new file mode 100644 index 00000000000..5765ad6d73b --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/OptionalClientDatePathParameterIsGuardedBeforeFormatting.cs @@ -0,0 +1,31 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; + +namespace Sample +{ + public partial class TestClient + { + private static global::System.ClientModel.Primitives.PipelineMessageClassifier _pipelineMessageClassifier200; + + private static global::System.ClientModel.Primitives.PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= global::System.ClientModel.Primitives.PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); + + internal global::System.ClientModel.Primitives.PipelineMessage CreateGetThingRequest(global::System.ClientModel.Primitives.RequestOptions options) + { + global::Sample.ClientUriBuilder uri = new global::Sample.ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/things", false); + if ((_requestOn != null)) + { + uri.AppendPath("/", false); + uri.AppendPath(_requestOn.Value.ToString("R"), true); + } + global::System.ClientModel.Primitives.PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier200); + global::System.ClientModel.Primitives.PipelineRequest request = message.Request; + message.Apply(options); + return message; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/OptionalClientEnumPathParameterIsGuardedThenUnwrapped.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/OptionalClientEnumPathParameterIsGuardedThenUnwrapped.cs new file mode 100644 index 00000000000..f63a0a0ac07 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/OptionalClientEnumPathParameterIsGuardedThenUnwrapped.cs @@ -0,0 +1,31 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; + +namespace Sample +{ + public partial class TestClient + { + private static global::System.ClientModel.Primitives.PipelineMessageClassifier _pipelineMessageClassifier200; + + private static global::System.ClientModel.Primitives.PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= global::System.ClientModel.Primitives.PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); + + internal global::System.ClientModel.Primitives.PipelineMessage CreateGetThingRequest(global::System.ClientModel.Primitives.RequestOptions options) + { + global::Sample.ClientUriBuilder uri = new global::Sample.ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/things", false); + if ((_color != null)) + { + uri.AppendPath("/", false); + uri.AppendPath(_color.Value.ToString(), true); + } + global::System.ClientModel.Primitives.PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier200); + global::System.ClientModel.Primitives.PipelineRequest request = message.Request; + message.Apply(options); + return message; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/OptionalClientEnumPathParameterWithDistinctSerializedNameIsGuarded.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/OptionalClientEnumPathParameterWithDistinctSerializedNameIsGuarded.cs new file mode 100644 index 00000000000..f63a0a0ac07 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/OptionalClientEnumPathParameterWithDistinctSerializedNameIsGuarded.cs @@ -0,0 +1,31 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; + +namespace Sample +{ + public partial class TestClient + { + private static global::System.ClientModel.Primitives.PipelineMessageClassifier _pipelineMessageClassifier200; + + private static global::System.ClientModel.Primitives.PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= global::System.ClientModel.Primitives.PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); + + internal global::System.ClientModel.Primitives.PipelineMessage CreateGetThingRequest(global::System.ClientModel.Primitives.RequestOptions options) + { + global::Sample.ClientUriBuilder uri = new global::Sample.ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/things", false); + if ((_color != null)) + { + uri.AppendPath("/", false); + uri.AppendPath(_color.Value.ToString(), true); + } + global::System.ClientModel.Primitives.PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier200); + global::System.ClientModel.Primitives.PipelineRequest request = message.Request; + message.Apply(options); + return message; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/OptionalCollectionPathParameterIsGuarded.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/OptionalCollectionPathParameterIsGuarded.cs new file mode 100644 index 00000000000..a3b81a192fe --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/OptionalCollectionPathParameterIsGuarded.cs @@ -0,0 +1,32 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; +using System.Collections.Generic; + +namespace Sample +{ + public partial class TestClient + { + private static global::System.ClientModel.Primitives.PipelineMessageClassifier _pipelineMessageClassifier200; + + private static global::System.ClientModel.Primitives.PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= global::System.ClientModel.Primitives.PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); + + internal global::System.ClientModel.Primitives.PipelineMessage CreateGetThingRequest(global::System.Collections.Generic.IEnumerable ids, global::System.ClientModel.Primitives.RequestOptions options) + { + global::Sample.ClientUriBuilder uri = new global::Sample.ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/things", false); + if (((ids != null) && !((ids is global::Sample.ChangeTrackingList changeTrackingList) && changeTrackingList.IsUndefined))) + { + uri.AppendPath("/", false); + uri.AppendPathDelimited(ids, ",", escape: true); + } + global::System.ClientModel.Primitives.PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier200); + global::System.ClientModel.Primitives.PipelineRequest request = message.Request; + message.Apply(options); + return message; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/OptionalEndpointParameterInServerTemplateIsGuarded.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/OptionalEndpointParameterInServerTemplateIsGuarded.cs new file mode 100644 index 00000000000..b3750c5c1e4 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/OptionalEndpointParameterInServerTemplateIsGuarded.cs @@ -0,0 +1,31 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; + +namespace Sample +{ + public partial class TestClient + { + private static global::System.ClientModel.Primitives.PipelineMessageClassifier _pipelineMessageClassifier200; + + private static global::System.ClientModel.Primitives.PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= global::System.ClientModel.Primitives.PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); + + internal global::System.ClientModel.Primitives.PipelineMessage CreateGetThingRequest(global::System.ClientModel.Primitives.RequestOptions options) + { + global::Sample.ClientUriBuilder uri = new global::Sample.ClientUriBuilder(); + uri.Reset(_endpoint); + if ((_region != null)) + { + uri.AppendPath("/", false); + uri.AppendPath(_region, true); + } + uri.AppendPath("/things", false); + global::System.ClientModel.Primitives.PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier200); + global::System.ClientModel.Primitives.PipelineRequest request = message.Request; + message.Apply(options); + return message; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/OptionalMethodEnumPathParameterIsSerializedOnce.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/OptionalMethodEnumPathParameterIsSerializedOnce.cs new file mode 100644 index 00000000000..b205e84100e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/OptionalMethodEnumPathParameterIsSerializedOnce.cs @@ -0,0 +1,31 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; + +namespace Sample +{ + public partial class TestClient + { + private static global::System.ClientModel.Primitives.PipelineMessageClassifier _pipelineMessageClassifier200; + + private static global::System.ClientModel.Primitives.PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= global::System.ClientModel.Primitives.PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); + + internal global::System.ClientModel.Primitives.PipelineMessage CreateGetThingRequest(string color, global::System.ClientModel.Primitives.RequestOptions options) + { + global::Sample.ClientUriBuilder uri = new global::Sample.ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/things", false); + if ((color != null)) + { + uri.AppendPath("/", false); + uri.AppendPath(color, true); + } + global::System.ClientModel.Primitives.PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier200); + global::System.ClientModel.Primitives.PipelineRequest request = message.Request; + message.Apply(options); + return message; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/RequiredNullableClientEnumPathParameterIsNotGuarded.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/RequiredNullableClientEnumPathParameterIsNotGuarded.cs new file mode 100644 index 00000000000..1414333d973 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/RequiredNullableClientEnumPathParameterIsNotGuarded.cs @@ -0,0 +1,27 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; + +namespace Sample +{ + public partial class TestClient + { + private static global::System.ClientModel.Primitives.PipelineMessageClassifier _pipelineMessageClassifier200; + + private static global::System.ClientModel.Primitives.PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= global::System.ClientModel.Primitives.PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); + + internal global::System.ClientModel.Primitives.PipelineMessage CreateGetThingRequest(global::System.ClientModel.Primitives.RequestOptions options) + { + global::Sample.ClientUriBuilder uri = new global::Sample.ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/things/", false); + uri.AppendPath(_color.ToString(), true); + global::System.ClientModel.Primitives.PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier200); + global::System.ClientModel.Primitives.PipelineRequest request = message.Request; + message.Apply(options); + return message; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/RequiredNullableCollectionPathParameterIsNotGuarded.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/RequiredNullableCollectionPathParameterIsNotGuarded.cs new file mode 100644 index 00000000000..24c1b08440e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/RequiredNullableCollectionPathParameterIsNotGuarded.cs @@ -0,0 +1,28 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; +using System.Collections.Generic; + +namespace Sample +{ + public partial class TestClient + { + private static global::System.ClientModel.Primitives.PipelineMessageClassifier _pipelineMessageClassifier200; + + private static global::System.ClientModel.Primitives.PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= global::System.ClientModel.Primitives.PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); + + internal global::System.ClientModel.Primitives.PipelineMessage CreateGetThingRequest(global::System.Collections.Generic.IEnumerable ids, global::System.ClientModel.Primitives.RequestOptions options) + { + global::Sample.ClientUriBuilder uri = new global::Sample.ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/things/", false); + uri.AppendPathDelimited(ids, ",", escape: true); + global::System.ClientModel.Primitives.PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier200); + global::System.ClientModel.Primitives.PipelineRequest request = message.Request; + message.Apply(options); + return message; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/RequiredNullableEnumPathParameterIsNotGuarded.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/RequiredNullableEnumPathParameterIsNotGuarded.cs new file mode 100644 index 00000000000..510157fdb9d --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/RequiredNullableEnumPathParameterIsNotGuarded.cs @@ -0,0 +1,27 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; + +namespace Sample +{ + public partial class TestClient + { + private static global::System.ClientModel.Primitives.PipelineMessageClassifier _pipelineMessageClassifier200; + + private static global::System.ClientModel.Primitives.PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= global::System.ClientModel.Primitives.PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); + + internal global::System.ClientModel.Primitives.PipelineMessage CreateGetThingRequest(string color, global::System.ClientModel.Primitives.RequestOptions options) + { + global::Sample.ClientUriBuilder uri = new global::Sample.ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/things/", false); + uri.AppendPath(color, true); + global::System.ClientModel.Primitives.PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier200); + global::System.ClientModel.Primitives.PipelineRequest request = message.Request; + message.Apply(options); + return message; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/RequiredNullableStringPathParameterIsNotGuarded.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/RequiredNullableStringPathParameterIsNotGuarded.cs new file mode 100644 index 00000000000..65c06f4fc5a --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/RequiredNullableStringPathParameterIsNotGuarded.cs @@ -0,0 +1,27 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; + +namespace Sample +{ + public partial class TestClient + { + private static global::System.ClientModel.Primitives.PipelineMessageClassifier _pipelineMessageClassifier200; + + private static global::System.ClientModel.Primitives.PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= global::System.ClientModel.Primitives.PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); + + internal global::System.ClientModel.Primitives.PipelineMessage CreateGetThingRequest(string name, global::System.ClientModel.Primitives.RequestOptions options) + { + global::Sample.ClientUriBuilder uri = new global::Sample.ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/things/", false); + uri.AppendPath(name, true); + global::System.ClientModel.Primitives.PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier200); + global::System.ClientModel.Primitives.PipelineRequest request = message.Request; + message.Apply(options); + return message; + } + } +}