diff --git a/README.md b/README.md index 6c3df9379e..1206080c86 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,14 @@ internal sealed partial class SourceGenerationContext : JsonSerializerContext; ``` - It's all! Now you can build your project and use the generated code with full trimming/nativeAOT support. +## Known Errors + +### Generator error: "Could not write to output file 'Path/to/file'. Could not find part of the path" + +This error happens if the generated file path is too long. This happens if you didn't activated long path support on windows. +To enable it follow the offical docs: +https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry#registry-setting-to-enable-long-paths + ## 📚Examples of use in real SDKs📚 - https://github.com/tryAGI/OpenAI - https://github.com/tryAGI/Ollama diff --git a/src/libs/AutoSDK/Helpers/Box.cs b/src/libs/AutoSDK/Helpers/Box.cs new file mode 100644 index 0000000000..e12b1a44be --- /dev/null +++ b/src/libs/AutoSDK/Helpers/Box.cs @@ -0,0 +1,63 @@ +namespace AutoSDK.Helpers; + +public static class BoxEtensions +{ + public static T Unbox(this Box box) + { + return (T)box.Value; + } + public static Box Box(this T tobox) + { + return new Box() { Value = tobox }; + } +} + +/// +/// This class is used to box any value type or reference type into an object. +/// This is used to avoid generic type cycling in structs. +/// This is only here to support visual studio. Other dotnet build tools do not require this. +/// More information can be found here: +/// https://github.com/dotnet/runtime/issues/6924 +/// +public struct Box : IEquatable +{ + private object _value; + public object Value + { + get => _value; + set => _value = value; + } + + public override bool Equals(object? obj) + { + if (obj is null) + { + return false; + } + if (obj is Box other) + { + return ReferenceEquals(Value, other.Value); + } + return false; + } + + bool IEquatable.Equals(Box other) + { + return this.Equals(other); + } + + public override int GetHashCode() + { + return Value?.GetHashCode() ?? 0; + } + + public static bool operator ==(Box left, Box right) + { + return left.Equals(right); + } + + public static bool operator !=(Box left, Box right) + { + return !(left == right); + } +} diff --git a/src/libs/AutoSDK/Models/ModelData.cs b/src/libs/AutoSDK/Models/ModelData.cs index e792e10bed..9f5aaf7d90 100644 --- a/src/libs/AutoSDK/Models/ModelData.cs +++ b/src/libs/AutoSDK/Models/ModelData.cs @@ -1,12 +1,13 @@ -using System.Collections.Immutable; using AutoSDK.Extensions; +using AutoSDK.Helpers; +using System.Collections.Immutable; namespace AutoSDK.Models; public record struct ModelData( SchemaContext SchemaContext, string Id, - ImmutableArray Parents, + ImmutableArray Parents, string Namespace, Settings Settings, ModelStyle Style, @@ -25,7 +26,7 @@ public static ModelData FromSchemaContext( SchemaContext context) { context = context ?? throw new ArgumentNullException(nameof(context)); - + var parents = new List(); var parent = context.Parent; while (parent != null) @@ -38,11 +39,11 @@ public static ModelData FromSchemaContext( } parents.Reverse(); - + return new ModelData( SchemaContext: context, Id: context.Id, - Parents: parents.ToImmutableArray(), + Parents: parents.Select(p => p.Box()).ToImmutableArray(), Namespace: context.Settings.Namespace, Style: context.Schema.IsEnum() ? ModelStyle.Enumeration : context.Settings.ModelStyle, Settings: context.Settings, @@ -82,13 +83,13 @@ public static ModelData FromSchemaContext( // }; public string GlobalClassName => $"global::{Namespace}.{ClassName}"; - + public string ExternalClassName => Settings.NamingConvention switch { NamingConvention.ConcatNames => ClassName, - NamingConvention.InnerClasses => string.Join(".", Parents.Select(x => x.ClassName).Concat([ClassName])), + NamingConvention.InnerClasses => string.Join(".", Parents.Select(x => x.Unbox().ClassName).Concat([ClassName])), _ => string.Empty, }; - + public string FileNameWithoutExtension => $"{Namespace}.Models.{ExternalClassName}"; } \ No newline at end of file diff --git a/src/libs/AutoSDK/Models/PropertyData.cs b/src/libs/AutoSDK/Models/PropertyData.cs index 087e4f7c85..ed281ac62c 100644 --- a/src/libs/AutoSDK/Models/PropertyData.cs +++ b/src/libs/AutoSDK/Models/PropertyData.cs @@ -1,4 +1,5 @@ using AutoSDK.Extensions; +using AutoSDK.Helpers; using AutoSDK.Naming.Properties; namespace AutoSDK.Models; @@ -39,14 +40,14 @@ public static PropertyData FromSchemaContext(SchemaContext context) { context = context ?? throw new ArgumentNullException(nameof(context)); var type = context.TypeData; - + // OpenAPI doesn't allow metadata for references so sometimes allOf with single item is used to add metadata. if (context.HasAllOfTypeForMetadata() && !type.SubTypes.IsEmpty) { - type = type.SubTypes[0] with + type = type.SubTypes[0].Unbox() with { - CSharpTypeRaw = type.SubTypes[0].CSharpTypeRaw, + CSharpTypeRaw = type.SubTypes[0].Unbox().CSharpTypeRaw, CSharpTypeNullability = type.CSharpTypeNullability, }; } @@ -54,7 +55,7 @@ public static PropertyData FromSchemaContext(SchemaContext context) var requiredProperties = context.Parent != null ? new HashSet(context.Parent.Schema.Required) : []; - + var propertyName = context.PropertyName ?? throw new InvalidOperationException("Property name or parameter name is required."); var isRequired = requiredProperties.Contains(propertyName) && @@ -64,7 +65,7 @@ public static PropertyData FromSchemaContext(SchemaContext context) { isRequired = false; } - + return new PropertyData( Id: propertyName, Name: CSharpPropertyNameGenerator.ComputePropertyName(context), @@ -81,7 +82,7 @@ public static PropertyData FromSchemaContext(SchemaContext context) DefaultValue: context.Schema is { ReadOnly: true } && !type.CSharpTypeNullability ? "default!" : context.GetDefaultValue(), - Example: context.Schema.Example?.GetString() is {} example && + Example: context.Schema.Example?.GetString() is { } example && !string.IsNullOrWhiteSpace(example) ? example.ClearForXml() : null, @@ -89,11 +90,11 @@ public static PropertyData FromSchemaContext(SchemaContext context) ConverterType: type.ConverterType, DiscriminatorValue: string.Empty); } - + public string ParameterName => Name .Replace(".", string.Empty) .ToParameterName() - + // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/ .ReplaceIfEquals("abstract", "@abstract") .ReplaceIfEquals("as", "@as") diff --git a/src/libs/AutoSDK/Models/TypeData.cs b/src/libs/AutoSDK/Models/TypeData.cs index 0018099a24..4f6fdd8125 100644 --- a/src/libs/AutoSDK/Models/TypeData.cs +++ b/src/libs/AutoSDK/Models/TypeData.cs @@ -1,5 +1,6 @@ -using System.Collections.Immutable; using AutoSDK.Extensions; +using AutoSDK.Helpers; +using System.Collections.Immutable; namespace AutoSDK.Models; @@ -24,7 +25,7 @@ public record struct TypeData( bool HasDiscriminator, EquatableArray Properties, EquatableArray EnumValues, - EquatableArray SubTypes, + EquatableArray SubTypes, string Namespace, bool IsDeprecated, Settings Settings) @@ -54,7 +55,7 @@ public record struct TypeData( Namespace: string.Empty, IsDeprecated: false, Settings: Settings.Default); - + public string CSharpTypeWithoutNullability => CSharpTypeRaw.TrimEnd('?'); public string CSharpTypeWithNullability => CSharpTypeWithoutNullability + "?"; public string ShortCSharpTypeWithoutNullability => CSharpTypeWithoutNullability.Replace($"global::{Namespace}.", string.Empty); @@ -75,24 +76,24 @@ public record struct TypeData( CSharpTypeWithoutNullability is "string" || IsAnyOfLike || IsEnum; - + public string ConverterType => IsUnixTimestamp ? $"global::{Settings.Namespace}.JsonConverters.UnixTimestampJsonConverter" : IsEnum || (IsAnyOfLike && (IsComponent || HasDiscriminator)) ? $"global::{Settings.Namespace}.JsonConverters.{ShortCSharpTypeWithoutNullability}JsonConverter" : AnyOfCount > 0 - ? $"global::{Settings.Namespace}.JsonConverters.AnyOfJsonConverter<{string.Join(", ", SubTypes.Select(y => y.CSharpTypeWithNullabilityForValueTypes))}>" + ? $"global::{Settings.Namespace}.JsonConverters.AnyOfJsonConverter<{string.Join(", ", SubTypes.Select(y => y.Unbox().CSharpTypeWithNullabilityForValueTypes))}>" : OneOfCount > 0 - ? $"global::{Settings.Namespace}.JsonConverters.OneOfJsonConverter<{string.Join(", ", SubTypes.Select(y => y.CSharpTypeWithNullabilityForValueTypes))}>" + ? $"global::{Settings.Namespace}.JsonConverters.OneOfJsonConverter<{string.Join(", ", SubTypes.Select(y => y.Unbox().CSharpTypeWithNullabilityForValueTypes))}>" : AllOfCount > 0 - ? $"global::{Settings.Namespace}.JsonConverters.AllOfJsonConverter<{string.Join(", ", SubTypes.Select(y => y.CSharpTypeWithNullabilityForValueTypes))}>" + ? $"global::{Settings.Namespace}.JsonConverters.AllOfJsonConverter<{string.Join(", ", SubTypes.Select(y => y.Unbox().CSharpTypeWithNullabilityForValueTypes))}>" : string.Empty; - + public static TypeData FromSchemaContext(SchemaContext context) { context = context ?? throw new ArgumentNullException(nameof(context)); - + var properties = ImmutableArray.Empty; if (context.Schema.ResolveIfRequired() is { } referenceSchema) { @@ -100,7 +101,7 @@ public static TypeData FromSchemaContext(SchemaContext context) .Select(x => x.Key) .ToImmutableArray(); } - + var subTypes = ImmutableArray.Empty; if (context.Schema.IsAnyOf()) { @@ -144,7 +145,7 @@ Default with }, ]; } - + var enumValues = ImmutableArray.Empty; if (context.Schema.IsEnum()) { @@ -159,9 +160,9 @@ Default with .Select(x => x.Id) .ToImmutableArray(); } - + var type = GetCSharpType(context); - + return new TypeData( CSharpTypeRaw: type, CSharpTypeNullability: GetCSharpNullability(context), @@ -189,18 +190,18 @@ Default with HasDiscriminator: context.Schema.Discriminator != null, Properties: properties, EnumValues: enumValues, - SubTypes: subTypes, + SubTypes: subTypes.Select(s => s.Box()).ToImmutableArray(), Namespace: type.StartsWith("global::System.", StringComparison.Ordinal) ? "System" : context.Settings.Namespace, IsDeprecated: context.Schema.Deprecated, Settings: context.Settings); } - + public static bool ContextIsValueType(SchemaContext context) { context = context ?? throw new ArgumentNullException(nameof(context)); - + return (context.Schema.Type, context.Schema.Format) switch { (_, _) when context.IsAnyOfLikeStructure => true, @@ -213,30 +214,30 @@ public static bool ContextIsValueType(SchemaContext context) ("string", "date-time") => true, ("string", "password") => true, ("string", "uuid") => true, - + // AssemblyAI format (null, "float") => true, (null, "double") => true, (null, "boolean") => true, - + _ => false, }; } - + public static string GetCSharpType(SchemaContext context) { context = context ?? throw new ArgumentNullException(nameof(context)); - + var type = (context.Schema.Type, context.Schema.Format) switch { (_, _) when context.Schema.IsUnixTimestamp() => "global::System.DateTimeOffset", - + (_, _) when context.Schema.IsArray() => $"{context.Children.FirstOrDefault(x => x.Hint == Hint.ArrayItem)?.TypeData.CSharpTypeWithoutNullability}".AsArray(), (_, _) when context.IsNamedAnyOfLike => $"global::{context.Settings.Namespace}.{context.Id}", (_, _) when context.IsDerivedClass => $"global::{context.Settings.Namespace}.{context.Id}", - + (_, _) when context.Schema.IsAnyOf() => $"global::{context.Settings.Namespace}.AnyOf<{string.Join(", ", context.Children.Where(x => x.Hint == Hint.AnyOf).Select(x => x.TypeData.CSharpTypeWithNullabilityForValueTypes))}>", (_, _) when context.Schema.IsOneOf() => $"global::{context.Settings.Namespace}.OneOf<{string.Join(", ", context.Children.Where(x => x.Hint == Hint.OneOf).Select(x => x.TypeData.CSharpTypeWithNullabilityForValueTypes))}>", (_, _) when context.Schema.IsAllOf() => $"global::{context.Settings.Namespace}.AllOf<{string.Join(", ", context.Children.Where(x => x.Hint == Hint.AllOf).Select(x => x.TypeData.CSharpTypeWithNullabilityForValueTypes))}>", @@ -246,27 +247,27 @@ public static string GetCSharpType(SchemaContext context) (context.Schema.ResolveIfRequired().Properties.Count > 0 || !context.Schema.ResolveIfRequired().AdditionalPropertiesAllowed) => $"global::{context.Settings.Namespace}.{context.Id}", - + // ("object", _) or (null, "object") when context.Schema.Reference == null => // $"global::{context.Settings.Namespace}.{context.Id}", - + ("object", _) or (null, "object") when context.Schema.Reference == null && (context.Schema.Properties.Count > 0 || !context.Schema.AdditionalPropertiesAllowed) => $"global::{context.Settings.Namespace}.{context.Id}", - + ("object", _) when context.Schema.AdditionalProperties?.Type is not null => $"global::System.Collections.Generic.Dictionary x.Hint == Hint.AdditionalProperties)?.TypeData.CSharpType}>", - + ("string", _) when context.Schema.Enum.Any() => $"global::{context.Settings.Namespace}.{context.Id}", (null, "boolean") => "bool", (null, "float") => "float", (null, "double") => "double", - + // https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/include-metadata?view=aspnetcore-9.0&tabs=minimal-apis#type-and-format ("boolean", _) => "bool", ("integer", "int64") => "long", @@ -282,7 +283,7 @@ public static string GetCSharpType(SchemaContext context) ("string", "date") => "global::System.DateTime", ("string", "date-time") => "global::System.DateTime", ("string", "password") => "string", - + // Possible future types - not supported yet // ("string", "time") => "global::System.TimeOnly", // ("string", "date") => "global::System.DateOnly", @@ -291,7 +292,7 @@ public static string GetCSharpType(SchemaContext context) // ("string", "duration") => "global::System.TimeSpan", // ("string", "uri") => "global::System.Uri", ("string", "uuid") => "global::System.Guid", - + (null, "url") => "string", ("integer", _) => "int", @@ -299,12 +300,12 @@ public static string GetCSharpType(SchemaContext context) ("string", _) => "string", (null, "string") => "string", ("object", _) => "object", - + (null, null) when (context.IsClass && context.ClassData?.Properties.Length > 0) || context.IsEnum => $"global::{context.Settings.Namespace}.{context.Id}", - (null, null) => "object", - ("null", _) => "object", - ("any", _) => "object", + (null, null) => "object", + ("null", _) => "object", + ("any", _) => "object", _ => throw new NotSupportedException($"Type {context.Schema.Type} is not supported."), }; @@ -316,7 +317,7 @@ public static bool GetCSharpNullability( SchemaContext? additionalContext = null) { context = context ?? throw new ArgumentNullException(nameof(context)); - + return context.Schema.Nullable || !context.IsRequired && additionalContext?.IsRequired != true; } diff --git a/src/libs/AutoSDK/Serialization/Form/ParameterSerializer.cs b/src/libs/AutoSDK/Serialization/Form/ParameterSerializer.cs index 952d19e5a1..d235c87a1e 100644 --- a/src/libs/AutoSDK/Serialization/Form/ParameterSerializer.cs +++ b/src/libs/AutoSDK/Serialization/Form/ParameterSerializer.cs @@ -1,7 +1,8 @@ using AutoSDK.Extensions; -using Microsoft.OpenApi.Models; +using AutoSDK.Helpers; using AutoSDK.Models; using AutoSDK.Serialization.Json; +using Microsoft.OpenApi.Models; namespace AutoSDK.Serialization.Form; @@ -10,7 +11,7 @@ public static class ParameterSerializer public static string SerializePathParameters(IList parameters, string path) { path = path ?? throw new ArgumentNullException(nameof(path)); - + foreach (var parameter in parameters.Where(x => x.Location == ParameterLocation.Path)) { path = path.Replace($"{{{parameter.Id}}}", $"{{{parameter.ArgumentName}}}"); @@ -26,7 +27,7 @@ public static string SerializePathParameters(IList parameters, public static IList SerializeQueryParameters(IList parameters) { parameters = parameters ?? throw new ArgumentNullException(nameof(parameters)); - + return parameters .Where(x => x.Location == ParameterLocation.Query) .SelectMany(SerializeQueryParameter) @@ -126,7 +127,7 @@ public static IReadOnlyCollection SerializeQueryParameter(Metho Value = parameter.ArgumentName, Selector = SerializeQueryParameter(parameter with { Name = "x", - Type = parameter.Type.SubTypes[0], + Type = parameter.Type.SubTypes[0].Unbox(), IsRequired = true, }).FirstOrDefault().Value ?? "x", Delimiter = parameter.Style switch @@ -145,7 +146,8 @@ public static IReadOnlyCollection SerializeQueryParameter(Metho var pairs = parameter.Properties .Select(x => ( Name: x.Id.ToParameterName(), - Value: $"{parameter.ArgumentName}{(parameter.IsRequired ? "" : "?")}." + SerializeQueryParameter(parameter with { + Value: $"{parameter.ArgumentName}{(parameter.IsRequired ? "" : "?")}." + SerializeQueryParameter(parameter with + { Name = x.Id, Type = x.Type, IsRequired = x.IsRequired, @@ -179,7 +181,7 @@ public static IReadOnlyCollection SerializeQueryParameter(Metho return []; } } - + if (parameter.Type.IsDate) { return [parameter with @@ -201,7 +203,7 @@ public static IReadOnlyCollection SerializeQueryParameter(Metho Value = $"{parameter.ArgumentName}{(parameter.IsRequired ? "" : "?")}.ToString() ?? string.Empty", }]; } - + return [parameter with { Value = $"{parameter.ArgumentName}{(parameter.IsRequired ? "" : "?")}.ToString()", diff --git a/src/libs/AutoSDK/Serialization/Json/SystemTextJsonSerializer.cs b/src/libs/AutoSDK/Serialization/Json/SystemTextJsonSerializer.cs index 4f5a97d707..94448e516c 100644 --- a/src/libs/AutoSDK/Serialization/Json/SystemTextJsonSerializer.cs +++ b/src/libs/AutoSDK/Serialization/Json/SystemTextJsonSerializer.cs @@ -1,4 +1,5 @@ using AutoSDK.Extensions; +using AutoSDK.Helpers; using AutoSDK.Models; namespace AutoSDK.Serialization.Json; @@ -16,17 +17,17 @@ public string GenerateExtensionDataAttribute() { return "[global::System.Text.Json.Serialization.JsonExtensionData]"; } - + public string GenerateRequiredAttribute() { return "[global::System.Text.Json.Serialization.JsonRequired]"; } - + public string GetOptionsType() { return "global::System.Text.Json.JsonSerializerOptions"; } - + public string CreateDefaultSettings(IReadOnlyList converters) { return @$"new global::System.Text.Json.JsonSerializerOptions @@ -48,7 +49,7 @@ public string GenerateConverterAttribute(string type) { return string.Empty; } - + return $"[global::System.Text.Json.Serialization.JsonConverter(typeof({type}))]"; } @@ -58,35 +59,35 @@ public static string GetContextType(TypeData typeData, bool makeNullableRootIfVa var shortTypeWithoutSubTypes = index >= 0 ? typeData.ShortCSharpTypeWithoutNullability.Substring(0, index) : typeData.ShortCSharpTypeWithoutNullability; - + return (typeData.IsValueType && (typeData.CSharpType.EndsWith("?", StringComparison.Ordinal) || makeNullableRootIfValueType) ? "Nullable" : string.Empty) + typeData switch - { - _ when typeData.IsBase64 || typeData.IsBinary => string.Empty, - { IsArray: true, SubTypes.Length: 1 } => "IList", - { AnyOfCount: > 0, IsComponent: false } => "AnyOf", - { OneOfCount: > 0, IsComponent: false } => "OneOf", - { AllOfCount: > 0, IsComponent: false } => "AllOf", - { ShortCSharpTypeWithoutNullability: "bool" } => "Boolean", - { ShortCSharpTypeWithoutNullability: "string" } => "String", - { ShortCSharpTypeWithoutNullability: "short" } => "Int16", - { ShortCSharpTypeWithoutNullability: "int" } => "Int32", - { ShortCSharpTypeWithoutNullability: "long" } => "Int64", - { ShortCSharpTypeWithoutNullability: "sbyte" } => "SByte", - { ShortCSharpTypeWithoutNullability: "ushort" } => "UInt16", - { ShortCSharpTypeWithoutNullability: "uint" } => "UInt32", - { ShortCSharpTypeWithoutNullability: "ulong" } => "UInt64", - { ShortCSharpTypeWithoutNullability: "float" } => "Single", - { ShortCSharpTypeWithoutNullability: "double" } => "Double", - { ShortCSharpTypeWithoutNullability: "decimal" } => "Decimal", - { ShortCSharpTypeWithoutNullability: "char" } => "Char", - { ShortCSharpTypeWithoutNullability: "byte" } => "Byte", - { ShortCSharpTypeWithoutNullability: "object" } => "Object", - _ => shortTypeWithoutSubTypes, - } + (typeData is { IsComponent: true, IsAnyOfLike: true } + { + _ when typeData.IsBase64 || typeData.IsBinary => string.Empty, + { IsArray: true, SubTypes.Length: 1 } => "IList", + { AnyOfCount: > 0, IsComponent: false } => "AnyOf", + { OneOfCount: > 0, IsComponent: false } => "OneOf", + { AllOfCount: > 0, IsComponent: false } => "AllOf", + { ShortCSharpTypeWithoutNullability: "bool" } => "Boolean", + { ShortCSharpTypeWithoutNullability: "string" } => "String", + { ShortCSharpTypeWithoutNullability: "short" } => "Int16", + { ShortCSharpTypeWithoutNullability: "int" } => "Int32", + { ShortCSharpTypeWithoutNullability: "long" } => "Int64", + { ShortCSharpTypeWithoutNullability: "sbyte" } => "SByte", + { ShortCSharpTypeWithoutNullability: "ushort" } => "UInt16", + { ShortCSharpTypeWithoutNullability: "uint" } => "UInt32", + { ShortCSharpTypeWithoutNullability: "ulong" } => "UInt64", + { ShortCSharpTypeWithoutNullability: "float" } => "Single", + { ShortCSharpTypeWithoutNullability: "double" } => "Double", + { ShortCSharpTypeWithoutNullability: "decimal" } => "Decimal", + { ShortCSharpTypeWithoutNullability: "char" } => "Char", + { ShortCSharpTypeWithoutNullability: "byte" } => "Byte", + { ShortCSharpTypeWithoutNullability: "object" } => "Object", + _ => shortTypeWithoutSubTypes, + } + (typeData is { IsComponent: true, IsAnyOfLike: true } ? string.Empty - : string.Concat(typeData.SubTypes.Select(x => GetContextType(x, makeNullableRootIfValueType: false)))) + : string.Concat(typeData.SubTypes.Select(x => GetContextType(x.Unbox(), makeNullableRootIfValueType: false)))) + (typeData.IsBase64 || typeData.IsBinary ? "Array" : string.Empty); @@ -100,12 +101,12 @@ public string GenerateSerializeCall(TypeData type, string jsonSerializerContext) ? "request.ToJson(JsonSerializerOptions)" : "request.ToJson(JsonSerializerContext)"; } - + return string.IsNullOrWhiteSpace(jsonSerializerContext) ? "global::System.Text.Json.JsonSerializer.Serialize(request, JsonSerializerOptions)" : $"global::System.Text.Json.JsonSerializer.Serialize(request, request.GetType(), JsonSerializerContext)"; } - + public string GenerateDeserializeCall(string variableName, TypeData type, string jsonSerializerContext) { var typeToDeserializeIfRequired = type.IsDerivedClass || type.IsBaseClass ? $"<{type.CSharpTypeWithoutNullability}>" : ""; @@ -115,7 +116,7 @@ public string GenerateDeserializeCall(string variableName, TypeData type, string ? $"{type.CSharpTypeWithoutNullability}.FromJson{typeToDeserializeIfRequired}({variableName}, JsonSerializerOptions)" : $"{type.CSharpTypeWithoutNullability}.FromJson{typeToDeserializeIfRequired}({variableName}, JsonSerializerContext)"; } - + return string.IsNullOrWhiteSpace(jsonSerializerContext) ? $"global::System.Text.Json.JsonSerializer.Deserialize<{type.CSharpTypeWithNullability}>({variableName}, JsonSerializerOptions)" : $"global::System.Text.Json.JsonSerializer.Deserialize({variableName}, typeof({type.CSharpTypeWithNullabilityForValueTypes}), JsonSerializerContext) as {type.CSharpTypeWithNullabilityForValueTypes}"; @@ -123,14 +124,14 @@ public string GenerateDeserializeCall(string variableName, TypeData type, string public string GenerateDeserializeFromStreamCall(string variableName, TypeData type, string jsonSerializerContext) { - var typeToDeserializeIfRequired = type.IsDerivedClass || type.IsBaseClass ? $"<{type.CSharpTypeWithoutNullability}>" : ""; + var typeToDeserializeIfRequired = type.IsDerivedClass || type.IsBaseClass ? $"<{type.CSharpTypeWithoutNullability}>" : ""; if (type.CSharpType.StartsWith($"global::{type.Settings.Namespace}", StringComparison.Ordinal)) { return string.IsNullOrWhiteSpace(jsonSerializerContext) ? $"await {type.CSharpTypeWithoutNullability}.FromJsonStreamAsync{typeToDeserializeIfRequired}({variableName}, JsonSerializerOptions).ConfigureAwait(false)" : $"await {type.CSharpTypeWithoutNullability}.FromJsonStreamAsync{typeToDeserializeIfRequired}({variableName}, JsonSerializerContext).ConfigureAwait(false)"; } - + return string.IsNullOrWhiteSpace(jsonSerializerContext) ? $"await global::System.Text.Json.JsonSerializer.DeserializeAsync<{type.CSharpTypeWithNullability}>({variableName}, JsonSerializerOptions).ConfigureAwait(false)" : $"await global::System.Text.Json.JsonSerializer.DeserializeAsync({variableName}, typeof({type.CSharpTypeWithNullabilityForValueTypes}), JsonSerializerContext).ConfigureAwait(false) as {type.CSharpTypeWithNullabilityForValueTypes}"; diff --git a/src/libs/AutoSDK/Sources/Sources.Methods.cs b/src/libs/AutoSDK/Sources/Sources.Methods.cs index b28cf21740..e1aeb6eb88 100644 --- a/src/libs/AutoSDK/Sources/Sources.Methods.cs +++ b/src/libs/AutoSDK/Sources/Sources.Methods.cs @@ -1,7 +1,8 @@ -using Microsoft.OpenApi.Models; using AutoSDK.Extensions; +using AutoSDK.Helpers; using AutoSDK.Models; using AutoSDK.Serialization.Json; +using Microsoft.OpenApi.Models; namespace AutoSDK.Generation; @@ -22,11 +23,11 @@ public static string GenerateEndPoint( ContentType.Stream => "global::System.IO.Stream", _ => "byte[]", }; - + return $@" #nullable enable{( - endPoint.Parameters.Any(x => x is { IsDeprecated: true, Location: not null }) || - endPoint.IsMultipartFormData && endPoint.Parameters.Any(x => x.IsDeprecated)? @" + endPoint.Parameters.Any(x => x is { IsDeprecated: true, Location: not null }) || + endPoint.IsMultipartFormData && endPoint.Parameters.Any(x => x.IsDeprecated) ? @" #pragma warning disable CS0618 // Type or member is obsolete" : "")} @@ -38,16 +39,14 @@ public partial class {endPoint.ClassName} global::System.Net.Http.HttpClient httpClient{endPoint.Parameters .Where(x => x.Location != null) .Select(x => $@", - {(x.Type.IsReferenceable ? "ref " : "")}{x.Type.CSharpType} {x.ParameterName}").Inject(emptyValue: "")}{ -(string.IsNullOrWhiteSpace(endPoint.RequestType.CSharpType) ? "" : @$", + {(x.Type.IsReferenceable ? "ref " : "")}{x.Type.CSharpType} {x.ParameterName}").Inject(emptyValue: "")}{(string.IsNullOrWhiteSpace(endPoint.RequestType.CSharpType) ? "" : @$", {endPoint.RequestType.CSharpTypeWithoutNullability} request")}); partial void Prepare{endPoint.NotAsyncMethodName}Request( global::System.Net.Http.HttpClient httpClient, global::System.Net.Http.HttpRequestMessage httpRequestMessage{endPoint.Parameters .Where(x => x.Location != null) .Select(x => $@", - {x.Type.CSharpType} {x.ParameterName}").Inject(emptyValue: "")}{ -(string.IsNullOrWhiteSpace(endPoint.RequestType.CSharpType) ? "" : @$", + {x.Type.CSharpType} {x.ParameterName}").Inject(emptyValue: "")}{(string.IsNullOrWhiteSpace(endPoint.RequestType.CSharpType) ? "" : @$", {endPoint.RequestType.CSharpTypeWithoutNullability} request")}); partial void Process{endPoint.NotAsyncMethodName}Response( global::System.Net.Http.HttpClient httpClient, @@ -84,17 +83,17 @@ public partial interface I{endPoint.ClassName} }} }}".RemoveBlankLinesWhereOnlyWhitespaces(); } - + public static string GetHttpMethod(OperationType operationType) { if (operationType == OperationType.Patch) { return "new global::System.Net.Http.HttpMethod(\"PATCH\")"; } - + return $"global::System.Net.Http.HttpMethod.{operationType:G}"; } - + public static string GenerateMethod( EndPoint endPoint, bool isInterface = false) { @@ -121,8 +120,7 @@ public static string GenerateMethod( httpClient: HttpClient{endPoint.Parameters .Where(x => x.Location != null) .Select(x => $@", - {x.ParameterName}: {(x.Type.IsReferenceable ? "ref " : "")}{x.ParameterName}").Inject(emptyValue: "")}{ - (string.IsNullOrWhiteSpace(endPoint.RequestType.CSharpType) ? "" : @", + {x.ParameterName}: {(x.Type.IsReferenceable ? "ref " : "")}{x.ParameterName}").Inject(emptyValue: "")}{(string.IsNullOrWhiteSpace(endPoint.RequestType.CSharpType) ? "" : @", request: request")}); {(endPoint.Settings.JsonSerializerType == JsonSerializerType.NewtonsoftJson ? endPoint.Parameters @@ -190,8 +188,7 @@ public static string GenerateMethod( httpRequestMessage: __httpRequest{endPoint.Parameters .Where(x => x.Location != null) .Select(x => $@", - {x.ParameterName}: {x.ParameterName}").Inject(emptyValue: "")}{ - (string.IsNullOrWhiteSpace(endPoint.RequestType.CSharpType) ? "" : @", + {x.ParameterName}: {x.ParameterName}").Inject(emptyValue: "")}{(string.IsNullOrWhiteSpace(endPoint.RequestType.CSharpType) ? "" : @", request: request")}); using var __response = await HttpClient.SendAsync( @@ -210,7 +207,7 @@ public static string GenerateMethod( httpResponseMessage: __response); {GenerateResponse(endPoint)} }}"; - + return $@" {endPoint.Summary.ToXmlDocumentationSummary(level: 8)} {endPoint.Parameters.Where(x => x.Location != null).Select(x => $@" @@ -230,7 +227,7 @@ public static string GenerateMethod( {cancellationTokenAttribute}global::System.Threading.CancellationToken cancellationToken = default){body} ".RemoveBlankLinesWhereOnlyWhitespaces(); } - + public static string SerializePropertyAsString( MethodParameter property) { @@ -240,21 +237,21 @@ public static string SerializePropertyAsString( if (property.Type.IsArray) { var subType = property.Type.SubTypes.First(); - var additionalConvertSubtype = subType.IsEnum + var additionalConvertSubtype = subType.Unbox().IsEnum ? ".ToValueString()" : string.Empty; return $"$\"[{{string.Join(\",\", global::System.Linq.Enumerable.Select({name}, x => x{additionalConvertSubtype}))}}]\""; } - + var additionalConvert = property.Type.IsEnum ? $"{(property.IsRequired ? "" : "?")}.ToValueString()" : string.Empty; - + return property.Type.IsAnyOfLike ? $"{name}{(property.IsRequired ? "" : "?")}.ToString() ?? string.Empty" : $"$\"{{{name}{additionalConvert}}}\""; } - + public static string GeneratePathAndQuery( EndPoint endPoint) { @@ -278,7 +275,7 @@ public static string GeneratePathAndQuery( var queryParameters = endPoint.QueryParameters .Where(x => x.Type.IsEnum || - (!x.Type.IsArray || (x.Type.SubTypes[0].Properties.Length == 0 && !x.Type.SubTypes[0].IsArray))) + (!x.Type.IsArray || (x.Type.SubTypes[0].Unbox().Properties.Length == 0 && !x.Type.SubTypes[0].Unbox().IsArray))) .ToArray(); if (queryParameters.Length > 0) @@ -292,7 +289,7 @@ public static string GeneratePathAndQuery( var additionalArguments = parameter.Type.IsArray ? $", delimiter: \"{parameter.Delimiter}\", explode: {(parameter.Explode ? "true" : "false")}" : string.Empty; - if (parameter.Type.IsArray && parameter.Type.SubTypes[0].CSharpTypeWithoutNullability is not "string") + if (parameter.Type.IsArray && parameter.Type.SubTypes[0].Unbox().CSharpTypeWithoutNullability is not "string") { additionalArguments = $", selector: static x => {parameter.Selector}" + additionalArguments; } @@ -307,16 +304,16 @@ public static string GeneratePathAndQuery( .AddOptionalParameter(""{parameter.Id}"", {parameter.Value}{additionalArguments})"; } } - + if (queryParameters.Length > 0) { code += @" ;"; } - + code += @" var __path = __pathBuilder.ToString();"; - + return code.RemoveBlankLinesWhereOnlyWhitespaces(); } @@ -324,7 +321,7 @@ public static string GenerateResponse( EndPoint endPoint) { var jsonSerializer = endPoint.Settings.JsonSerializerType.GetSerializer(); - + if (endPoint.Stream) { return $@" @@ -370,10 +367,10 @@ public static string GenerateResponse( .Concat(endPoint.ErrorResponses.Where(x => x is { IsPattern: true, IsDefault: false })) .Concat(endPoint.ErrorResponses.Where(x => x.IsDefault)) .ToArray(); - + var errors = endPoint.Settings.GenerateExceptions ? orderedErrorResponses.Select(x => $@" // {x.Description.Replace('\n', ' ').Replace('\r', ' ')} -{( x.IsDefault ? @" +{(x.IsDefault ? @" if (!__response.IsSuccessStatusCode)" : x.IsPattern ? $@" if ((int)__response.StatusCode >= {x.Min} && (int)__response.StatusCode <= {x.Max})" @@ -411,17 +408,17 @@ public static string GenerateResponse( h => h.Value), }}; }}").Inject() : " "; - + return @$"{errors} if (ReadResponseAsString) {{ var __content = await __response.Content.ReadAs{endPoint.ContentType switch - { - ContentType.String => "String", - ContentType.Stream => "Stream", - _ => "ByteArray", - }}Async( + { + ContentType.String => "String", + ContentType.Stream => "Stream", + _ => "ByteArray", + }}Async( #if NET5_0_OR_GREATER cancellationToken #endif @@ -489,17 +486,17 @@ public static string GenerateResponse( }} {endPoint.ContentType switch - { - ContentType.String when endPoint.SuccessResponse.Type.CSharpTypeWithoutNullability is not "string" => "using ", - ContentType.Stream => "using ", - _ => string.Empty, - }}var __content = await __response.Content.ReadAs{endPoint.ContentType switch - { - ContentType.String when endPoint.SuccessResponse.Type.CSharpTypeWithoutNullability is "string" => "String", - ContentType.String => "Stream", - ContentType.Stream => "Stream", - _ => "ByteArray", - }}Async( + { + ContentType.String when endPoint.SuccessResponse.Type.CSharpTypeWithoutNullability is not "string" => "using ", + ContentType.Stream => "using ", + _ => string.Empty, + }}var __content = await __response.Content.ReadAs{endPoint.ContentType switch + { + ContentType.String when endPoint.SuccessResponse.Type.CSharpTypeWithoutNullability is "string" => "String", + ContentType.String => "Stream", + ContentType.Stream => "Stream", + _ => "ByteArray", + }}Async( #if NET5_0_OR_GREATER cancellationToken #endif @@ -515,7 +512,7 @@ public static string GenerateResponse( }} "; } - + public static string GenerateRequestData( EndPoint endPoint) { @@ -523,7 +520,7 @@ public static string GenerateRequestData( { return " "; } - + var jsonSerializer = endPoint.Settings.JsonSerializerType.GetSerializer(); if (endPoint.IsMultipartFormData) { @@ -556,7 +553,7 @@ public static string GenerateRequestData( __httpRequest.Content = __httpRequestContent; ".RemoveBlankLinesWhereOnlyWhitespaces(); } - + var requestContent = endPoint.RequestType.IsBase64 ? "global::System.Convert.ToBase64String(request)" : jsonSerializer.GenerateSerializeCall(endPoint.RequestType, endPoint.Settings.JsonSerializerContext); @@ -570,7 +567,7 @@ public static string GenerateRequestData( __httpRequest.Content = __httpRequestContent; ".RemoveBlankLinesWhereOnlyWhitespaces(); } - + public static string GenerateExtensionMethod( EndPoint endPoint, bool isInterface = false) { @@ -582,7 +579,7 @@ public static string GenerateExtensionMethod( { return " "; } - + var taskType = endPoint.Stream ? string.IsNullOrWhiteSpace(endPoint.SuccessResponse.Type.CSharpType) ? throw new InvalidOperationException($"Streamed responses must have a response type. OperationId: {endPoint.Id}.") @@ -623,11 +620,11 @@ public static string GenerateExtensionMethod( yield return __response; }" : " ")} }}"; - + var parameters = endPoint.Parameters .Where(static x => x is { IsDeprecated: false } or { IsRequired: true } or { IsDeprecated: true, Location: not null }) .ToList(); - + return $@" {endPoint.Summary.ToXmlDocumentationSummary(level: 8)} {parameters.Select(x => $@" diff --git a/src/libs/AutoSDK/Sources/Sources.Models.cs b/src/libs/AutoSDK/Sources/Sources.Models.cs index 91d5e7f4e6..7f9553346c 100644 --- a/src/libs/AutoSDK/Sources/Sources.Models.cs +++ b/src/libs/AutoSDK/Sources/Sources.Models.cs @@ -1,4 +1,5 @@ using AutoSDK.Extensions; +using AutoSDK.Helpers; using AutoSDK.Models; using AutoSDK.Serialization.Json; @@ -20,7 +21,7 @@ namespace {modelData.Namespace} {GenerateModel(modelData, level: 0, cancellationToken: cancellationToken)} }}".RemoveBlankLinesWhereOnlyWhitespaces(); } - + private static string GenerateModel( ModelData modelData, int level, @@ -38,14 +39,14 @@ private static string GenerateModel( _ => throw new NotSupportedException($"Model style {modelData.Style} is not supported."), }; } - + return $@" -public sealed partial class {modelData.Parents[level].ClassName} +public sealed partial class {modelData.Parents[level].Unbox().ClassName} {{ {GenerateModel(modelData, level + 1, cancellationToken: cancellationToken)} }}".RemoveBlankLinesWhereOnlyWhitespaces().AddIndent(level: 1); } - + private static bool IsSupported(SdkFeatureUsage usage, string targetFramework) { return usage switch @@ -65,12 +66,12 @@ private static string GetDefaultValue(PropertyData property, bool isRequiredKeyw { return " = default!;"; } - + return property.Type.CSharpTypeNullability || string.IsNullOrWhiteSpace(property.DefaultValue) ? string.Empty : $" = {property.DefaultValue};"; } - + public static string GenerateClassModel( ModelData modelData, CancellationToken cancellationToken = default) @@ -86,7 +87,7 @@ public static string GenerateClassModel( var properties = modelData.Properties.Where(x => !modelData.IsBaseClass || x.Id != modelData.DiscriminatorPropertyName).ToArray(); - + return $@" {modelData.Summary.ToXmlDocumentationSummary(level: 4)} {(modelData.IsDeprecated ? "[global::System.Obsolete(\"This model marked as deprecated.\")]" : " ")} @@ -116,7 +117,7 @@ public static string GenerateClassModel( public global::System.Collections.Generic.IDictionary AdditionalProperties{additionalPropertiesPostfix} {{ get; set; }} = new global::System.Collections.Generic.Dictionary(); " : " ")} -{( properties.Any(static x => x.IsRequired || !x.IsDeprecated) ? $@" +{(properties.Any(static x => x.IsRequired || !x.IsDeprecated) ? $@" /// /// Initializes a new instance of the class. ///