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 @@ -32,6 +32,9 @@ public class BinaryContentHelperDefinition : InternalHelperProvider

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

protected override IReadOnlyList<CSharpType> BuildBodyDependencyTypes() =>
[ScmCodeModelGenerator.Instance.ModelSerializationExtensionsDefinition.Type];

protected override MethodProvider[] BuildMethods()
{
var methods = new List<MethodProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,20 @@ public class ClientOptionsProvider : TypeProvider

private readonly InputClient _inputClient;
private readonly ClientProvider _clientProvider;
private readonly Dictionary<InputEnumType, EnumProvider>? _serviceVersionsEnums;
private readonly Lazy<Dictionary<InputEnumType, EnumProvider>?> _serviceVersionsEnums;
private static ClientOptionsProvider? _singletonInstance;
// All clients sharing the singleton are tracked so its accessibility reflects every owner.
private static readonly List<ClientProvider> _singletonClientProviders = [];

internal ClientOptionsProvider(InputClient inputClient, ClientProvider clientProvider)
{
_inputClient = inputClient;
_clientProvider = clientProvider;
_serviceVersionsEnums = new(BuildServiceVersionsEnums);
}

private Dictionary<InputEnumType, EnumProvider>? BuildServiceVersionsEnums()
{
List<InputEnumType> inputEnums = [.. _inputClient.Parameters
.Where(p => p.IsApiVersion && p.Type is InputEnumType)
.Select(p => (InputEnumType)p.Type)];
Expand All @@ -45,15 +52,15 @@ internal ClientOptionsProvider(InputClient inputClient, ClientProvider clientPro

if (inputEnums.Count > 0)
{
_serviceVersionsEnums = [];
Dictionary<InputEnumType, EnumProvider> serviceVersionsEnums = [];
foreach (var inputEnum in inputEnums)
{
var enumProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateEnum(inputEnum, this);
if (enumProvider != null)
{
// Ensure the service version enum uses the same namespace as the options class since it is nested.
enumProvider.Update(@namespace: Type.Namespace);
_serviceVersionsEnums.Add(inputEnum, enumProvider);
serviceVersionsEnums.Add(inputEnum, enumProvider);
}

// Only create one version property for single service clients
Expand All @@ -62,7 +69,11 @@ internal ClientOptionsProvider(InputClient inputClient, ClientProvider clientPro
break;
}
}

return serviceVersionsEnums;
}

return null;
}

/// <summary>
Expand All @@ -77,12 +88,15 @@ public static ClientOptionsProvider CreateClientOptionsProvider(InputClient inpu
{
if (UseSingletonInstance(inputClient))
{
// Use singleton instance
if (_singletonInstance == null)
{
// Create singleton with namespace-based naming
_singletonClientProviders.Clear();
_singletonInstance = new ClientOptionsProvider(inputClient, clientProvider);
}
if (!_singletonClientProviders.Contains(clientProvider))
{
_singletonClientProviders.Add(clientProvider);
}
return _singletonInstance;
}

Expand Down Expand Up @@ -160,13 +174,14 @@ private static bool UseSingletonInstance(InputClient inputClient)

private Dictionary<EnumProvider, PropertyProvider>? BuildVersionProperties()
{
if (_serviceVersionsEnums is null)
var serviceVersionsEnums = _serviceVersionsEnums.Value;
if (serviceVersionsEnums is null)
{
return null;
}

var properties = new Dictionary<EnumProvider, PropertyProvider>(_serviceVersionsEnums.Count);
foreach (var (inputEnum, enumProvider) in _serviceVersionsEnums)
var properties = new Dictionary<EnumProvider, PropertyProvider>(serviceVersionsEnums.Count);
foreach (var (inputEnum, enumProvider) in serviceVersionsEnums)
{
string versionPropertyName;
if (!_inputClient.IsMultiServiceClient)
Expand All @@ -177,10 +192,10 @@ private static bool UseSingletonInstance(InputClient inputClient)
{
var serviceNamespace = inputEnum.Namespace;
if (!string.IsNullOrEmpty(serviceNamespace) &&
ClientHelper.HasLastSegmentCollision(serviceNamespace, inputEnum, _serviceVersionsEnums.Keys))
ClientHelper.HasLastSegmentCollision(serviceNamespace, inputEnum, serviceVersionsEnums.Keys))
{
// Last segment collides — find the shortest unique namespace suffix.
string uniquePrefix = ClientHelper.GetShortestUniqueNamespacePrefix(serviceNamespace, inputEnum, _serviceVersionsEnums.Keys);
string uniquePrefix = ClientHelper.GetShortestUniqueNamespacePrefix(serviceNamespace, inputEnum, serviceVersionsEnums.Keys);
versionPropertyName = $"{uniquePrefix.ToIdentifierName()}{ApiVersionSuffix}";
}
else
Expand All @@ -205,13 +220,14 @@ private static bool UseSingletonInstance(InputClient inputClient)

private Dictionary<FieldProvider, EnumProvider>? BuildLatestVersionsFields()
{
if (_serviceVersionsEnums is null)
var serviceVersionsEnums = _serviceVersionsEnums.Value;
if (serviceVersionsEnums is null)
{
return null;
}

Dictionary<FieldProvider, EnumProvider> latestVersionFields = new(_serviceVersionsEnums.Count);
foreach (var (inputEnum, enumProvider) in _serviceVersionsEnums)
Dictionary<FieldProvider, EnumProvider> latestVersionFields = new(serviceVersionsEnums.Count);
foreach (var (inputEnum, enumProvider) in serviceVersionsEnums)
{
string fieldName;
if (!_inputClient.IsMultiServiceClient)
Expand All @@ -222,10 +238,10 @@ private static bool UseSingletonInstance(InputClient inputClient)
{
var serviceNamespace = inputEnum.Namespace;
if (!string.IsNullOrEmpty(serviceNamespace) &&
ClientHelper.HasLastSegmentCollision(serviceNamespace, inputEnum, _serviceVersionsEnums.Keys))
ClientHelper.HasLastSegmentCollision(serviceNamespace, inputEnum, serviceVersionsEnums.Keys))
{
// Last segment collides — find the shortest unique namespace suffix.
string uniquePrefix = ClientHelper.GetShortestUniqueNamespacePrefix(serviceNamespace, inputEnum, _serviceVersionsEnums.Keys);
string uniquePrefix = ClientHelper.GetShortestUniqueNamespacePrefix(serviceNamespace, inputEnum, serviceVersionsEnums.Keys);
fieldName = $"{LatestPrefix}{uniquePrefix.ToIdentifierName()}{VersionSuffix}";
}
else
Expand All @@ -248,6 +264,30 @@ private static bool UseSingletonInstance(InputClient inputClient)

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

// TODO https://github.com/microsoft/typespec/issues/11181: Remove this once generated source parity no longer depends on internal ClientOptions XML docs.
protected override bool ShouldWriteTypeXmlDocs => CustomCodeView is null;

protected override TypeSignatureModifiers BuildDeclarationModifiers()
{
if (this == _singletonInstance)
{
foreach (var clientProvider in _singletonClientProviders)
{
if (clientProvider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public))
{
return TypeSignatureModifiers.Public;
}
}

return TypeSignatureModifiers.Internal;
}

return GetAccessibilityModifiers(_clientProvider.DeclarationModifiers);
}

private static TypeSignatureModifiers GetAccessibilityModifiers(TypeSignatureModifiers modifiers)
=> modifiers & (TypeSignatureModifiers.Public | TypeSignatureModifiers.Internal | TypeSignatureModifiers.Protected | TypeSignatureModifiers.Private);

protected override string BuildName()
{
if (UseSingletonInstance(_inputClient))
Expand Down Expand Up @@ -289,12 +329,13 @@ protected override FieldProvider[] BuildFields()

protected override TypeProvider[] BuildNestedTypes()
{
if (_serviceVersionsEnums is null)
var serviceVersionsEnums = _serviceVersionsEnums.Value;
if (serviceVersionsEnums is null)
{
return [];
}

return [.. _serviceVersionsEnums.Values.OrderBy(e => e.Name)];
return [.. serviceVersionsEnums.Values.OrderBy(e => e.Name)];
}

protected override ConstructorProvider[] BuildConstructors()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ namespace Microsoft.TypeSpec.Generator.ClientModel.Providers
{
public class ClientProvider : TypeProvider
{
protected override bool IsClientProvider => true;

private record AuthFields(FieldProvider AuthField);
private record ApiKeyFields(FieldProvider AuthField, FieldProvider AuthorizationHeaderField, FieldProvider? AuthorizationApiKeyPrefixField) : AuthFields(AuthField);
private record OAuth2Fields(FieldProvider AuthField, FieldProvider AuthorizationScopesField) : AuthFields(AuthField);
Expand Down Expand Up @@ -72,7 +74,9 @@ private record ApiVersionFields(FieldProvider Field, PropertyProvider? Correspon
/// </summary>
internal TypeProvider BackCompatProvider => _backCompatProvider ?? this;

public ParameterProvider? ClientOptionsParameter { get; }
private ParameterProvider? _clientOptionsParameter;
public ParameterProvider? ClientOptionsParameter
=> ClientOptions is null ? null : _clientOptionsParameter ??= ScmKnownParameters.ClientOptions(ClientOptions.Type);

protected override FormattableString BuildDescription()
{
Expand Down Expand Up @@ -108,7 +112,6 @@ public ClientProvider(InputClient inputClient)
_subClientEndpointParameter = BuildSubClientEndpointParameter();
_publicCtorDescription = $"Initializes a new instance of {Name}.";
ClientOptions = _inputClient.Parent is null ? ClientOptionsProvider.CreateClientOptionsProvider(_inputClient, this) : null;
ClientOptionsParameter = ClientOptions != null ? ScmKnownParameters.ClientOptions(ClientOptions.Type) : null;
bool isIndividuallyInitialized = (_inputClient.InitializedBy & InputClientInitializedBy.Individually) != 0;
ClientSettings = isIndividuallyInitialized
&& DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public)
Expand Down Expand Up @@ -426,6 +429,42 @@ private IReadOnlyList<ParameterProvider> GetClientParameters()

protected override string BuildName() => _inputClient.IsExactName ? _inputClient.Name : _inputClient.Name.ToIdentifierName();

protected override IReadOnlyList<CSharpType> BuildHelperDependencyTypes()
{
foreach (var method in Methods.OfType<ScmMethodProvider>())
{
if (method.BodyStatements != null)
{
return [ScmCodeModelGenerator.Instance.ClientPipelineExtensionsDefinition.Type];
}
}

return [];
}

protected override IReadOnlyList<CSharpType> BuildBodyDependencyTypes()
{
var dependencies = new List<CSharpType>();
foreach (var method in Methods.OfType<ScmMethodProvider>())
{
if (method.BodyStatements == null)
{
continue;
}

if (method.CollectionDefinition != null)
{
dependencies.Add(method.CollectionDefinition.Type);
}

// Service method metadata can mention wire-only request/response models that are not
// emitted in the generated method signature or body. The graph builder and structured
// body scanner capture the generated types that are actually referenced.
}

return dependencies;
}

protected override FieldProvider[] BuildFields()
{
List<FieldProvider> fields = [EndpointField];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,22 @@ private bool HasPagingOperationNameCollision(string operationName)
protected override TypeSignatureModifiers BuildDeclarationModifiers()
=> TypeSignatureModifiers.Internal | TypeSignatureModifiers.Partial | TypeSignatureModifiers.Class;

protected override IReadOnlyList<CSharpType> BuildBodyDependencyTypes()
{
var dependencies = new List<CSharpType> { Client.Type, ResponseModelType, NextPagePropertyType };
if (ItemModelType != null)
{
dependencies.Add(ItemModelType);
}

foreach (var field in RequestFields)
{
dependencies.Add(field.Type);
}

return dependencies;
}

protected override FieldProvider[] BuildFields() => [ClientField, .. RequestFields];

protected override CSharpType[] BuildImplements() =>
Expand Down
Loading
Loading