|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Frozen;
using System.Diagnostics.CodeAnalysis;
namespace Aspire.Hosting.Ats;
/// <summary>
/// Categories of ATS types for serialization and handling.
/// </summary>
[Experimental("ASPIREATS001")]
public enum AtsTypeCategory
{
/// <summary>
/// Primitive types that serialize directly to JSON values.
/// Examples: string, number, boolean, datetime, guid.
/// </summary>
Primitive,
/// <summary>
/// Enum types that serialize as string values.
/// </summary>
Enum,
/// <summary>
/// Handle types that are opaque references to .NET objects.
/// Serialized as { "$handle": "type:id", "$type": "type" }.
/// </summary>
Handle,
/// <summary>
/// Data Transfer Objects that serialize as JSON objects.
/// Must be marked with [AspireDto].
/// </summary>
Dto,
/// <summary>
/// Callback types (delegates) that are registered and invoked by ID.
/// </summary>
Callback,
/// <summary>
/// Readonly array/collection types that serialize as JSON arrays (copied).
/// Examples: T[], IReadOnlyList<T>, IReadOnlyCollection<T>.
/// </summary>
Array,
/// <summary>
/// Mutable list types that are handles to .NET List<T>.
/// </summary>
List,
/// <summary>
/// Dictionary types that serialize as JSON objects.
/// Mutable dictionaries are handles; readonly dictionaries are copied.
/// </summary>
Dict,
/// <summary>
/// Union types that can hold one of multiple alternative types.
/// Serialization depends on the member types.
/// </summary>
Union,
/// <summary>
/// Unknown types that couldn't be resolved during the first pass of scanning.
/// In Pass 2, these are either resolved to Handle (if the type is in the universe)
/// or filtered out (if the type is not a valid ATS type).
/// </summary>
Unknown
}
/// <summary>
/// Kinds of ATS capabilities for code generation.
/// </summary>
[Experimental("ASPIREATS001")]
public enum AtsCapabilityKind
{
/// <summary>
/// Regular extension method capability.
/// </summary>
Method,
/// <summary>
/// Property getter capability (from ExposeProperties=true).
/// </summary>
PropertyGetter,
/// <summary>
/// Property setter capability (from ExposeProperties=true).
/// </summary>
PropertySetter,
/// <summary>
/// Instance method capability (from ExposeMethods=true).
/// </summary>
InstanceMethod
}
/// <summary>
/// Constants for ATS (Aspire Type System) type IDs and capability IDs.
/// </summary>
internal static class AtsConstants
{
/// <summary>
/// The Aspire.Hosting assembly name.
/// </summary>
public const string AspireHostingAssembly = "Aspire.Hosting";
#region Primitive Type IDs
/// <summary>
/// String type ID. Maps from .NET <see cref="System.String"/>.
/// </summary>
public const string String = "string";
/// <summary>
/// Char type ID. Maps from .NET <see cref="System.Char"/>.
/// Serializes to JSON string.
/// </summary>
public const string Char = "char";
/// <summary>
/// Number type ID. Maps from .NET numeric types (int, long, float, double, decimal, etc.).
/// Serializes to JSON number.
/// </summary>
public const string Number = "number";
/// <summary>
/// Boolean type ID. Maps from .NET <see cref="System.Boolean"/>.
/// </summary>
public const string Boolean = "boolean";
/// <summary>
/// Void type ID. Represents no return value.
/// </summary>
public const string Void = "void";
#endregion
#region Date/Time Type IDs
/// <summary>
/// DateTime type ID. Maps from .NET <see cref="System.DateTime"/>.
/// Serializes to JSON string (ISO 8601).
/// </summary>
public const string DateTime = "datetime";
/// <summary>
/// DateTimeOffset type ID. Maps from .NET <see cref="System.DateTimeOffset"/>.
/// Serializes to JSON string (ISO 8601).
/// </summary>
public const string DateTimeOffset = "datetimeoffset";
/// <summary>
/// DateOnly type ID. Maps from .NET <see cref="System.DateOnly"/>.
/// Serializes to JSON string (YYYY-MM-DD).
/// </summary>
public const string DateOnly = "dateonly";
/// <summary>
/// TimeOnly type ID. Maps from .NET <see cref="System.TimeOnly"/>.
/// Serializes to JSON string (HH:mm:ss).
/// </summary>
public const string TimeOnly = "timeonly";
/// <summary>
/// TimeSpan type ID. Maps from .NET <see cref="System.TimeSpan"/>.
/// Serializes to JSON number (total milliseconds).
/// </summary>
public const string TimeSpan = "timespan";
#endregion
#region Other Scalar Type IDs
/// <summary>
/// Guid type ID. Maps from .NET <see cref="System.Guid"/>.
/// Serializes to JSON string.
/// </summary>
public const string Guid = "guid";
/// <summary>
/// Uri type ID. Maps from .NET <see cref="System.Uri"/>.
/// Serializes to JSON string.
/// </summary>
public const string Uri = "uri";
/// <summary>
/// Any type ID. Maps from .NET <see cref="System.Object"/>.
/// Accepts any supported ATS type. Use when a parameter needs to accept
/// multiple types without explicit union declaration.
/// </summary>
public const string Any = "any";
/// <summary>
/// CancellationToken type ID. Maps from .NET <see cref="System.Threading.CancellationToken"/>.
/// In TypeScript, maps to AbortSignal for cancellation support.
/// </summary>
public const string CancellationToken = "cancellationToken";
/// <summary>
/// Enum type ID prefix. Maps from .NET enum types.
/// Full format: "enum:{FullTypeName}". Serializes to JSON string (enum name).
/// </summary>
public const string EnumPrefix = "enum:";
#endregion
#region Well-known Type IDs
/// <summary>
/// Type ID for IDistributedApplicationBuilder.
/// </summary>
public const string BuilderTypeId = "Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder";
/// <summary>
/// Type ID for DistributedApplication.
/// </summary>
public const string ApplicationTypeId = "Aspire.Hosting/Aspire.Hosting.DistributedApplication";
/// <summary>
/// Type ID for ReferenceExpression.
/// </summary>
public const string ReferenceExpressionTypeId = "Aspire.Hosting/Aspire.Hosting.ApplicationModel.ReferenceExpression";
#endregion
#region Well-known Capability IDs
/// <summary>
/// Capability ID for creating a builder.
/// </summary>
public const string CreateBuilderCapability = "Aspire.Hosting/createBuilder";
/// <summary>
/// Capability ID for building the application.
/// </summary>
public const string BuildCapability = "Aspire.Hosting/build";
/// <summary>
/// Capability ID for running the application.
/// </summary>
public const string RunCapability = "Aspire.Hosting/run";
#endregion
#region Collection Type ID Helpers
/// <summary>
/// Creates an array type ID for the given element type.
/// </summary>
/// <param name="elementType">The element type ID.</param>
/// <returns>The array type ID.</returns>
public static string ArrayTypeId(string elementType) => $"{elementType}[]";
/// <summary>
/// Creates a List type ID for the given element type.
/// </summary>
/// <param name="elementType">The element type ID.</param>
/// <returns>The List type ID.</returns>
public static string ListTypeId(string elementType) => $"{AspireHostingAssembly}/List<{elementType}>";
/// <summary>
/// Creates a Dict type ID for the given key and value types.
/// </summary>
/// <param name="keyType">The key type ID.</param>
/// <param name="valueType">The value type ID.</param>
/// <returns>The Dict type ID.</returns>
public static string DictTypeId(string keyType, string valueType) => $"{AspireHostingAssembly}/Dict<{keyType},{valueType}>";
/// <summary>
/// Creates an enum type ID for the given enum type full name.
/// </summary>
/// <param name="enumFullName">The full name of the enum type.</param>
/// <returns>The enum type ID.</returns>
public static string EnumTypeId(string enumFullName) => $"{EnumPrefix}{enumFullName}";
/// <summary>
/// Checks if a type ID represents an enum type.
/// </summary>
/// <param name="typeId">The ATS type ID to check.</param>
/// <returns>True if the type is an enum.</returns>
public static bool IsEnum(string? typeId) => typeId?.StartsWith(EnumPrefix, StringComparison.Ordinal) == true;
/// <summary>
/// Checks if a type ID represents an array type.
/// </summary>
/// <param name="typeId">The ATS type ID to check.</param>
/// <returns>True if the type is an array.</returns>
public static bool IsArray(string? typeId) => typeId?.EndsWith("[]", StringComparison.Ordinal) == true;
/// <summary>
/// Checks if a type ID represents a List type.
/// </summary>
/// <param name="typeId">The ATS type ID to check.</param>
/// <returns>True if the type is a List.</returns>
public static bool IsList(string? typeId) => typeId?.Contains("/List<", StringComparison.Ordinal) == true;
/// <summary>
/// Checks if a type ID represents a Dict type.
/// </summary>
/// <param name="typeId">The ATS type ID to check.</param>
/// <returns>True if the type is a Dict.</returns>
public static bool IsDict(string? typeId) => typeId?.Contains("/Dict<", StringComparison.Ordinal) == true;
#endregion
#region Type Category Helpers
/// <summary>
/// Checks if a type ID represents a primitive type.
/// </summary>
/// <param name="typeId">The ATS type ID to check.</param>
/// <returns>True if the type is a primitive.</returns>
public static bool IsPrimitive(string? typeId) => typeId switch
{
String or Char or Number or Boolean or Void => true,
DateTime or DateTimeOffset or DateOnly or TimeOnly or TimeSpan => true,
Guid or Uri or Any or CancellationToken => true,
_ => false
};
/// <summary>
/// Checks if a type ID represents a handle type (has Assembly/Type format).
/// </summary>
/// <param name="typeId">The ATS type ID to check.</param>
/// <returns>True if the type is a handle.</returns>
public static bool IsHandle(string? typeId)
{
if (string.IsNullOrEmpty(typeId))
{
return false;
}
// Primitives are not handles
if (IsPrimitive(typeId))
{
return false;
}
// Handle types have the format {AssemblyName}/{TypeName}
return typeId.Contains('/');
}
/// <summary>
/// Gets the type category for a type ID.
/// Note: This method cannot distinguish DTOs from Handles based on type ID alone.
/// Use the scanner's type mapping to determine if a handle type is actually a DTO.
/// </summary>
/// <param name="typeId">The ATS type ID.</param>
/// <param name="isCallback">True if this is a callback parameter.</param>
/// <returns>The type category.</returns>
public static AtsTypeCategory GetCategory(string? typeId, bool isCallback = false)
{
if (isCallback)
{
return AtsTypeCategory.Callback;
}
if (IsPrimitive(typeId))
{
return AtsTypeCategory.Primitive;
}
if (IsEnum(typeId))
{
return AtsTypeCategory.Enum;
}
if (IsArray(typeId))
{
return AtsTypeCategory.Array;
}
if (IsList(typeId))
{
return AtsTypeCategory.List;
}
if (IsDict(typeId))
{
return AtsTypeCategory.Dict;
}
// Union types are explicitly created by the scanner when [AspireUnion] is present.
// They are not inferred from type ID format.
// For handle-format types, we default to Handle.
// The scanner/runtime can override this to Dto if the type has [AspireDto].
return AtsTypeCategory.Handle;
}
#endregion
#region Type-based Classification
/// <summary>
/// Set of CLR types that map to ATS primitive types.
/// </summary>
private static readonly FrozenSet<Type> s_primitiveTypes = new HashSet<Type>
{
// Core primitives
typeof(string),
typeof(char),
typeof(bool),
// Numeric types (all map to "number")
typeof(byte),
typeof(sbyte),
typeof(short),
typeof(ushort),
typeof(int),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(float),
typeof(double),
typeof(decimal),
// Date/time types
typeof(DateTime),
typeof(DateTimeOffset),
typeof(DateOnly),
typeof(TimeOnly),
typeof(TimeSpan),
// Other scalar types
typeof(Guid),
typeof(Uri),
typeof(CancellationToken),
}.ToFrozenSet();
/// <summary>
/// Checks if a CLR type is a primitive ATS type.
/// </summary>
/// <param name="type">The CLR type to check.</param>
/// <returns>True if the type is a primitive.</returns>
public static bool IsPrimitiveType(Type type) => s_primitiveTypes.Contains(type);
/// <summary>
/// Gets the type category for a CLR type.
/// </summary>
/// <param name="type">The CLR type.</param>
/// <returns>The type category.</returns>
public static AtsTypeCategory GetCategory(Type type)
{
if (s_primitiveTypes.Contains(type))
{
return AtsTypeCategory.Primitive;
}
if (type.IsEnum)
{
return AtsTypeCategory.Enum;
}
if (type.IsArray)
{
return AtsTypeCategory.Array;
}
if (IsListType(type))
{
return AtsTypeCategory.List;
}
if (IsReadOnlyListType(type))
{
return AtsTypeCategory.Array; // ReadOnly collections serialize as arrays
}
if (IsDictType(type))
{
return AtsTypeCategory.Dict;
}
if (typeof(Delegate).IsAssignableFrom(type))
{
return AtsTypeCategory.Callback;
}
// Check for [AspireDto] attribute
if (type.GetCustomAttributes(typeof(AspireDtoAttribute), inherit: false).Length > 0)
{
return AtsTypeCategory.Dto;
}
// Check for [AspireExport] attribute - these are handle types
if (type.GetCustomAttributes(typeof(AspireExportAttribute), inherit: false).Length > 0)
{
return AtsTypeCategory.Handle;
}
// Unknown types - not recognized as valid ATS types
return AtsTypeCategory.Unknown;
}
/// <summary>
/// Checks if a type is a mutable List type.
/// </summary>
private static bool IsListType(Type type)
{
if (!type.IsGenericType)
{
return false;
}
var genericDef = type.GetGenericTypeDefinition();
return genericDef == typeof(List<>) ||
genericDef == typeof(IList<>);
}
/// <summary>
/// Checks if a type is a readonly list/collection type.
/// </summary>
private static bool IsReadOnlyListType(Type type)
{
if (!type.IsGenericType)
{
return false;
}
var genericDef = type.GetGenericTypeDefinition();
return genericDef == typeof(IReadOnlyList<>) ||
genericDef == typeof(IReadOnlyCollection<>) ||
genericDef == typeof(IEnumerable<>);
}
/// <summary>
/// Checks if a type is a dictionary type.
/// </summary>
private static bool IsDictType(Type type)
{
if (!type.IsGenericType)
{
return false;
}
var genericDef = type.GetGenericTypeDefinition();
return genericDef == typeof(Dictionary<,>) ||
genericDef == typeof(IDictionary<,>) ||
genericDef == typeof(IReadOnlyDictionary<,>);
}
/// <summary>
/// Checks if a dictionary type is readonly.
/// </summary>
public static bool IsReadOnlyDictType(Type type)
{
if (!type.IsGenericType)
{
return false;
}
var genericDef = type.GetGenericTypeDefinition();
return genericDef == typeof(IReadOnlyDictionary<,>);
}
#endregion
}
|