Skip to content

Commit

Permalink
implement array as header and query, and format different type to str…
Browse files Browse the repository at this point in the history
…ing (#4213)

Fix #4205
Fixes #3973

Problem to solve:
When any type except string (e.g. int, TimeSpan and so on) as
header/query, it is not correct covert to correct format, and when there
is an array as header/query, it does not serialize correct in connection
format (such as cvs, pipe and so on).

- call TypeFormatters.ConvertToString to covert this type to correct
format
- when it is an array, we need to convert to string in correct
collection format.
  • Loading branch information
chunyu3 authored Aug 23, 2024
1 parent 4196cd3 commit d932462
Show file tree
Hide file tree
Showing 33 changed files with 2,843 additions and 28 deletions.
1 change: 0 additions & 1 deletion packages/http-client-csharp/eng/scripts/Generate.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ $failingSpecs = @(
Join-Path 'http' 'client' 'structure' 'renamed-operation'
Join-Path 'http' 'client' 'structure' 'two-operation-group'
Join-Path 'http' 'encode' 'datetime'
Join-Path 'http' 'encode' 'duration'
Join-Path 'http' 'parameters' 'body-optionality'
Join-Path 'http' 'parameters' 'collection-format'
Join-Path 'http' 'parameters' 'spread'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Generator.CSharp.ClientModel.Snippets;
using Microsoft.Generator.CSharp.Expressions;
using Microsoft.Generator.CSharp.Primitives;
using Microsoft.Generator.CSharp.Providers;
using Microsoft.Generator.CSharp.Snippets;
using static Microsoft.Generator.CSharp.Snippets.Snippet;

namespace Microsoft.Generator.CSharp.ClientModel.Providers
{
internal class PipelineRequestHeadersExtensionsDefinition : TypeProvider
{
private const string _setDelimited = "SetDelimited";
private ParameterProvider _pipelineRequestHeadersParam;
public PipelineRequestHeadersExtensionsDefinition()
{
_pipelineRequestHeadersParam = new ParameterProvider("headers", FormattableStringHelpers.Empty, typeof(PipelineRequestHeaders));
}
private readonly CSharpType _t = typeof(IEnumerable<>).GetGenericArguments()[0];
protected override TypeSignatureModifiers GetDeclarationModifiers()
{
return TypeSignatureModifiers.Internal | TypeSignatureModifiers.Static;
}

protected override string BuildRelativeFilePath() => Path.Combine("src", "Generated", "Internal", $"{Name}.cs");

protected override string BuildName() => "PipelineRequestHeadersExtensions";

protected override MethodProvider[] BuildMethods()
{
return
[
BuildSetDelimited(false),
BuildSetDelimited(true),
];
}

private MethodProvider BuildSetDelimited(bool hasFormat)
{
var nameParameter = new ParameterProvider("name", $"The name.", typeof(string));
var valueParameter = new ParameterProvider("value", $"The value.", new CSharpType(typeof(IEnumerable<>), _t));
var delimiterParameter = new ParameterProvider("delimiter", $"The delimiter.", typeof(string));
var formatParameter = new ParameterProvider("format", $"The format.", typeof(string));
var modifiers = MethodSignatureModifiers.Public | MethodSignatureModifiers.Static | MethodSignatureModifiers.Extension;
var parameters = hasFormat
? new[] { _pipelineRequestHeadersParam, nameParameter, valueParameter, delimiterParameter, formatParameter }
: new[] { _pipelineRequestHeadersParam, nameParameter, valueParameter, delimiterParameter };
MethodSignature signature = new MethodSignature(
Name: _setDelimited,
Modifiers: modifiers,
Parameters: parameters,
ReturnType: null,
GenericArguments: [_t],
Description: null,
ReturnDescription: null);

var value = valueParameter.As(_t);
var v = new VariableExpression(_t, "v");
var convertToStringExpression = TypeFormattersSnippets.ConvertToString(v, hasFormat ? formatParameter : (ValueExpression?)null);
var selector = new FuncExpression([v.Declaration], convertToStringExpression).As<string>();
var body = new[]
{
Declare("stringValues", value.Select(selector), out var stringValues),
new InvokeMethodExpression(_pipelineRequestHeadersParam, "Set", [nameParameter, StringSnippets.Join(delimiterParameter, stringValues)]).Terminate()
};

return new(signature, body, this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
Expand Down Expand Up @@ -207,23 +208,12 @@ private IEnumerable<MethodBodyStatement> AppendHeaderParameters(ScopedApi<Pipeli
string? format;
ValueExpression valueExpression;
GetParamInfo(paramMap, inputParameter, out type, out format, out valueExpression);
ValueExpression[] toStringParams = format is null ? [] : [Literal(format)];
ValueExpression toStringExpression = type?.Equals(typeof(string)) == true ? valueExpression : valueExpression.Invoke(nameof(ToString), toStringParams);
var convertToStringExpression = TypeFormattersSnippets.ConvertToString(valueExpression, Literal(format));
ValueExpression toStringExpression = type?.Equals(typeof(string)) == true ? valueExpression : convertToStringExpression;
MethodBodyStatement statement;
if (type?.Equals(typeof(BinaryData)) == true)
if (type?.IsCollection == true)
{
statement = request.SetHeaderValue(
inputParameter.NameInRequest,
TypeFormattersSnippets.ToString(valueExpression.Invoke("ToArray"), Literal(format)));
}
else if (type?.Equals(typeof(IList<BinaryData>)) == true)
{
statement =
new ForeachStatement("item", valueExpression.As<IEnumerable<BinaryData>>(), out var item)
{
request.AddHeaderValue(inputParameter.NameInRequest, TypeFormattersSnippets.ToString(item.Invoke("ToArray"),
Literal(format)))
};
statement = request.SetHeaderDelimited(inputParameter.NameInRequest, valueExpression, Literal(inputParameter.ArraySerializationDelimiter), format != null ? Literal(format) : null);
}
else
{
Expand All @@ -247,18 +237,13 @@ private IEnumerable<MethodBodyStatement> AppendQueryParameters(ScopedApi<ClientU
string? format;
ValueExpression valueExpression;
GetParamInfo(paramMap, inputParameter, out var type, out format, out valueExpression);
ValueExpression[] toStringParams = format is null ? [] : [Literal(format)];
var toStringExpression = type?.Equals(typeof(string)) == true ? valueExpression : valueExpression.Invoke(nameof(ToString), toStringParams);
var convertToStringExpression = TypeFormattersSnippets.ConvertToString(valueExpression, Literal(format));
ValueExpression toStringExpression = type?.Equals(typeof(string)) == true ? valueExpression : convertToStringExpression;
MethodBodyStatement statement;
if (type?.Equals(typeof(BinaryData)) == true)
{
statement = uri.AppendQuery(Literal(inputParameter.NameInRequest),
valueExpression.Invoke("ToArray"), format, true).Terminate();
}
else if (type?.Equals(typeof(IList<BinaryData>)) == true)
if (type?.IsCollection == true)
{
statement = uri.AppendQueryDelimited(Literal(inputParameter.NameInRequest),
valueExpression, format, true).Terminate();
valueExpression, format, true).Terminate();
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ protected override TypeProvider[] BuildTypeProviders()
new ClientUriBuilderDefinition(),
new Utf8JsonBinaryContentDefinition(),
new BinaryContentHelperDefinition(),
new PipelineRequestHeadersExtensionsDefinition(),
];
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,11 @@ public static MethodBodyStatement AddHeaderValue(this ScopedApi<PipelineRequest>

public static MethodBodyStatement SetContent(this ScopedApi<PipelineRequest> pipelineRequest, ValueExpression content)
=> pipelineRequest.Property(nameof(PipelineRequest.Content)).Assign(content).Terminate();

public static MethodBodyStatement SetHeaderDelimited(this ScopedApi<PipelineRequest> pipelineRequest, string name, ValueExpression value, ValueExpression delimiter, ValueExpression? format = null)
{
ValueExpression[] parameters = format != null ? [Literal(name), value, delimiter, format] : [Literal(name), value, delimiter];
return pipelineRequest.Property(nameof(PipelineRequest.Headers)).Invoke("SetDelimited", parameters).Terminate();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public partial class TestClient
global::sample.namespace.ClientUriBuilder uri = new global::sample.namespace.ClientUriBuilder();
uri.Reset(_endpoint);
request.Uri = uri.ToUri();
request.Headers.Set("repeatability-first-sent", global::System.DateTimeOffset.Now.ToString("R"));
request.Headers.Set("repeatability-first-sent", global::sample.namespace.TypeFormatters.ConvertToString(global::System.DateTimeOffset.Now, "R"));
request.Headers.Set("repeatability-request-ID", global::System.Guid.NewGuid().ToString());
request.Content = content;
message.Apply(options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@
"commandName": "Executable",
"executablePath": "$(SolutionDir)/../dist/generator/Microsoft.Generator.CSharp.exe"
},
"http-encode-duration": {
"commandLineArgs": "$(SolutionDir)/TestProjects/CadlRanch/http/encode/duration -p StubLibraryPlugin",
"commandName": "Executable",
"executablePath": "$(SolutionDir)/../dist/generator/Microsoft.Generator.CSharp.exe"
},
"http-parameters-basic": {
"commandLineArgs": "$(SolutionDir)/TestProjects/CadlRanch/http/parameters/basic -p StubLibraryPlugin",
"commandName": "Executable",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<Compile Remove="../../TestProjects/Local/Unbranded-TypeSpec/src/Generated/Internal/ErrorResult.cs" />
<Compile Remove="../../TestProjects/Local/Unbranded-TypeSpec/src/Generated/Internal/BinaryContentHelper.cs" />
<Compile Remove="../../TestProjects/Local/Unbranded-TypeSpec/src/Generated/Internal/Utf8JsonBinaryContent.cs" />
<Compile Remove="../../TestProjects/Local/Unbranded-TypeSpec/src/Generated/Internal/PipelineRequestHeadersExtensions.cs" />
</ItemGroup>

<ItemGroup>
Expand Down
Loading

0 comments on commit d932462

Please sign in to comment.