Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ protected HttpRequestApi(CSharpType type, ValueExpression original) : base(type,
}

public abstract Type UriBuilderType { get; }
public virtual CSharpType? GetCollectionHeaderHelperType() => null;
public abstract MethodBodyStatement SetHeaders(IReadOnlyList<ValueExpression> arguments);
public abstract MethodBodyStatement AddCollectionHeaders(ValueExpression prefix, ValueExpression headers);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.ClientModel.Primitives;
using System.Collections.Generic;
using Microsoft.TypeSpec.Generator.Expressions;
using Microsoft.TypeSpec.Generator.Primitives;
using Microsoft.TypeSpec.Generator.Statements;

namespace Microsoft.TypeSpec.Generator.ClientModel.Providers
Expand All @@ -20,6 +21,9 @@ public PipelineRequestProvider(ValueExpression original) : base(typeof(PipelineR

public override Type UriBuilderType => typeof(ClientUriBuilderDefinition);

public override CSharpType GetCollectionHeaderHelperType()
=> ScmCodeModelGenerator.Instance.PipelineRequestHeadersExtensionsDefinition.Type;

public override ValueExpression Content()
=> Original.Property(nameof(PipelineRequest.Content));

Expand All @@ -34,7 +38,7 @@ public override MethodBodyStatement SetHeaders(IReadOnlyList<ValueExpression> ar

public override MethodBodyStatement AddCollectionHeaders(ValueExpression prefix, ValueExpression headers)
=> Original.Property(nameof(PipelineRequest.Headers))
.Invoke(nameof(PipelineRequestHeaders.Add), [prefix, headers], typeArguments: null, callAsAsync: false, extensionType: ScmCodeModelGenerator.Instance.PipelineRequestHeadersExtensionsDefinition.Type)
.Invoke(nameof(PipelineRequestHeaders.Add), [prefix, headers], typeArguments: null, callAsAsync: false, extensionType: GetCollectionHeaderHelperType())
.Terminate();

public override HttpRequestApi ToExpression() => this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ protected override FieldProvider[] BuildFields()

protected override IReadOnlyList<CSharpType> BuildHelperDependencyTypes()
{
var uriBuilderType = ScmCodeModelGenerator.Instance.TypeFactory.HttpRequestApi.ToExpression().UriBuilderType;
var requestApi = ScmCodeModelGenerator.Instance.TypeFactory.HttpRequestApi.ToExpression();
var uriBuilderType = requestApi.UriBuilderType;
var dependencies = new List<CSharpType>();
var dependencyNames = new HashSet<string>(StringComparer.Ordinal);
if (uriBuilderType == typeof(ClientUriBuilderDefinition))
Expand All @@ -104,9 +105,10 @@ protected override IReadOnlyList<CSharpType> BuildHelperDependencyTypes()
{
TryAddDependency(dependencies, dependencyNames, ScmCodeModelGenerator.Instance.TypeFactory.DictionaryInitializationType);
if (parameter is InputHeaderParameter headerParameter &&
!string.IsNullOrEmpty(headerParameter.CollectionHeaderPrefix))
!string.IsNullOrEmpty(headerParameter.CollectionHeaderPrefix) &&
requestApi.GetCollectionHeaderHelperType() is { } collectionHeaderHelperType)
{
TryAddDependency(dependencies, dependencyNames, ScmCodeModelGenerator.Instance.PipelineRequestHeadersExtensionsDefinition.Type);
TryAddDependency(dependencies, dependencyNames, collectionHeaderHelperType);
}
}
else if (type?.IsCollection == true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.TypeSpec.Generator.Expressions;
using Microsoft.TypeSpec.Generator.ClientModel.Providers;
using Microsoft.TypeSpec.Generator.Input;
using Microsoft.TypeSpec.Generator.Input.Extensions;
Expand Down Expand Up @@ -1600,6 +1601,43 @@ public void TestCollectionHeaderPrefix_UsesAddWithPrefixCall(bool hasPrefix)
Assert.AreEqual(Helpers.GetExpectedFromFile(parameters: hasPrefix.ToString()), file.Content);
}

[TestCase(true)]
[TestCase(false)]
public void TestCollectionHeaderPrefix_AddsHelperRequiredByRequestApi(bool useDefaultRequestApi)
{
if (!useDefaultRequestApi)
{
MockHelpers.LoadMockGenerator(httpRequestApi: TestHttpRequestApi.Instance);
}

var metadataHeaderParam = InputFactory.HeaderParameter(
"metadata",
InputFactory.Dictionary(InputPrimitiveType.String),
isRequired: true,
serializedName: "x-ms-meta",
collectionHeaderPrefix: "x-ms-meta-");
var inputServiceMethod = InputFactory.BasicServiceMethod(
"TestServiceMethod",
InputFactory.Operation(
"TestOperation",
parameters: [metadataHeaderParam]),
parameters:
[
InputFactory.MethodParameter(
"metadata",
InputFactory.Dictionary(InputPrimitiveType.String),
isRequired: true,
location: InputRequestLocation.Header)
]);
var restClient = new ClientProvider(
InputFactory.Client("TestClient", methods: [inputServiceMethod])).RestClient;

Assert.AreEqual(
useDefaultRequestApi,
restClient.HelperDependencyTypes.Contains(
ScmCodeModelGenerator.Instance.PipelineRequestHeadersExtensionsDefinition.Type));
}


private static void ValidateResponseClassifier(MethodBodyStatements bodyStatements, string parsedStatusCodes)
{
Expand Down Expand Up @@ -2379,5 +2417,32 @@ public void PageSizeParameterSerializedNameUsedInCreateRequestMethod()
Assert.IsTrue(file.Content.Contains("uri.AppendQuery(\"maxpagesize\""),
"Generated code should use the serialized name 'maxpagesize' in the query string");
}

private sealed record TestHttpRequestApi : HttpRequestApi
{
public static TestHttpRequestApi Instance { get; } = new(Empty);

public TestHttpRequestApi(ValueExpression original)
: base(typeof(object), original)
{
}

public override Type UriBuilderType => typeof(UriBuilder);

public override MethodBodyStatement SetHeaders(IReadOnlyList<ValueExpression> arguments)
=> Original.Invoke("SetHeaders", arguments).Terminate();

public override MethodBodyStatement AddCollectionHeaders(ValueExpression prefix, ValueExpression headers)
=> Original.Invoke("AddCollectionHeaders", [prefix, headers]).Terminate();

public override ValueExpression Content() => Original;

public override ValueExpression ClientRequestId() => Original;

public override HttpRequestApi FromExpression(ValueExpression original)
=> new TestHttpRequestApi(original);

public override HttpRequestApi ToExpression() => this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,23 @@ await GenerateAndAssertFiles(
expectedFiles: [Path.Combine("src", "Generated", "Internal", "PipelineRequestHeadersExtensions.cs")]);
}

[Test]
public async Task GeneratedScalarHeaderDoesNotKeepExtensions()
{
var header = InputFactory.HeaderParameter("x-ms-custom", InputPrimitiveType.String, isRequired: true);
var operation = InputFactory.Operation("Create", parameters: [header]);
var method = InputFactory.BasicServiceMethod("Create", operation);
var client = InputFactory.Client("TestClient", methods: [method]);

await GenerateAndAssertFiles(
enums: [],
models: [],
clients: [client],
customFiles: [],
expectedFiles: [],
unexpectedFiles: [Path.Combine("src", "Generated", "Internal", "PipelineRequestHeadersExtensions.cs")]);
}

[Test]
public async Task BinaryDataBodyParameterDoesNotKeepBinaryContentHelpers()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public static Mock<ScmCodeModelGenerator> LoadMockGenerator(
ClientResponseApi? clientResponseApi = null,
ClientPipelineApi? clientPipelineApi = null,
HttpMessageApi? httpMessageApi = null,
HttpRequestApi? httpRequestApi = null,
RequestContentApi? requestContentApi = null,
Func<InputAuth>? auth = null,
Func<OutputLibrary>? createOutputLibrary = null,
Expand Down Expand Up @@ -173,6 +174,11 @@ public static Mock<ScmCodeModelGenerator> LoadMockGenerator(
mockTypeFactory.Setup(p => p.HttpMessageApi).Returns(httpMessageApi);
}

if (httpRequestApi is not null)
{
mockTypeFactory.Setup(p => p.HttpRequestApi).Returns(httpRequestApi);
}

if (requestContentApi is not null)
{
mockTypeFactory.Setup(p => p.RequestContentApi).Returns(requestContentApi);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@

namespace Microsoft.TypeSpec.Generator.Primitives
{
internal enum UnionItemTypeReferenceKind
{
PublicSurface,
MetadataOnly
}

/// <summary>
/// CSharpType represents the C# type of an input type.
/// It is constructed from a <see cref="Type"/> and its properties.
Expand All @@ -21,6 +27,7 @@ public class CSharpType
private object? _literal;
private Type? _underlyingType;
private IReadOnlyList<CSharpType>? _unionItemTypes;
private UnionItemTypeReferenceKind _unionItemTypeReferenceKind;

private bool? _isReadOnlyMemory;
private bool? _isList;
Expand Down Expand Up @@ -221,6 +228,7 @@ private init
public CSharpType InputType => _inputType ??= GetInputType();
public CSharpType OutputType => _outputType ??= GetOutputType();
public IReadOnlyList<CSharpType> UnionItemTypes => _unionItemTypes ?? throw new InvalidOperationException("Not a union type");
internal UnionItemTypeReferenceKind UnionItemTypeReferenceKind => _unionItemTypeReferenceKind;

private bool TypeIsReadOnlyMemory()
=> IsFrameworkType && _type == typeof(ReadOnlyMemory<>);
Expand Down Expand Up @@ -559,6 +567,7 @@ public CSharpType WithNullable(bool isNullable)

type._literal = _literal;
type._unionItemTypes = _unionItemTypes;
type._unionItemTypeReferenceKind = _unionItemTypeReferenceKind;

return type;
}
Expand All @@ -582,6 +591,7 @@ internal CSharpType WithUnderlyingEnumType(Type underlyingEnumType)
type._underlyingType = underlyingEnumType;
type._literal = _literal;
type._unionItemTypes = _unionItemTypes;
type._unionItemTypeReferenceKind = _unionItemTypeReferenceKind;

return type;
}
Expand Down Expand Up @@ -691,9 +701,16 @@ public static CSharpType FromLiteral(CSharpType type, object literalValue)
/// <param name="isNullable">Flag used to determine if a type is nullable.</param>
/// <returns>A <see cref="CSharpType"/> instance representing those unioned types.</returns>
public static CSharpType FromUnion(IReadOnlyList<CSharpType> unionItemTypes, bool isNullable = false)
=> FromUnion(unionItemTypes, isNullable, UnionItemTypeReferenceKind.PublicSurface);

internal static CSharpType FromUnion(
IReadOnlyList<CSharpType> unionItemTypes,
bool isNullable,
UnionItemTypeReferenceKind referenceKind)
{
var type = new CSharpType(typeof(BinaryData), isNullable);
type._unionItemTypes = unionItemTypes;
type._unionItemTypeReferenceKind = referenceKind;

return type;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1355,7 +1355,7 @@ _ when type.Equals(_additionalPropsUnknownType, ignoreNullable: true) => type,
_ when type.IsUnion => type,
_ when type.IsList => type.MakeGenericType([ReplaceUnverifiableType(type.Arguments[0])]),
_ when type.IsDictionary => type.MakeGenericType([ReplaceUnverifiableType(type.Arguments[0]), ReplaceUnverifiableType(type.Arguments[1])]),
_ => CSharpType.FromUnion([type])
_ => CSharpType.FromUnion([type], false, UnionItemTypeReferenceKind.MetadataOnly)
};
}

Expand Down
Loading
Loading