diff --git a/src/SDK/Language/DotNet.php b/src/SDK/Language/DotNet.php index 085a503a3b..d304aa18cc 100644 --- a/src/SDK/Language/DotNet.php +++ b/src/SDK/Language/DotNet.php @@ -467,7 +467,7 @@ public function getFilters(): array } /** - * get sub_scheme and property_name functions + * get sub_scheme, property_name and parse_value functions * @return TwigFunction[] */ public function getFunctions(): array @@ -494,7 +494,7 @@ public function getFunctions(): array } return $result; - }), + }, ['is_safe' => ['html']]), new TwigFunction('property_name', function (array $definition, array $property) { $name = $property['name']; $name = \str_replace('$', '', $name); @@ -504,6 +504,82 @@ public function getFunctions(): array } return $name; }), + new TwigFunction('parse_value', function (array $property, string $mapAccess, string $v) { + $required = $property['required'] ?? false; + + // Handle sub_schema + if (isset($property['sub_schema']) && !empty($property['sub_schema'])) { + $subSchema = \ucfirst($property['sub_schema']); + + if ($property['type'] === 'array') { + $arraySource = $required + ? "((IEnumerable){$mapAccess})" + : "({$v} as IEnumerable)?"; + return "{$arraySource}.Select(it => {$subSchema}.From(map: (Dictionary)it)).ToList()"; + } else { + if ($required) { + return "{$subSchema}.From(map: (Dictionary){$mapAccess})"; + } + return "({$v} as Dictionary) is { } obj ? {$subSchema}.From(map: obj) : null"; + } + } + + // Handle enum + if (isset($property['enum']) && !empty($property['enum'])) { + $enumName = $property['enumName'] ?? $property['name']; + $enumClass = \ucfirst($enumName); + + if ($required) { + return "new {$enumClass}({$mapAccess}.ToString())"; + } + return "{$v} == null ? null : new {$enumClass}({$v}.ToString())"; + } + + // Handle arrays + if ($property['type'] === 'array') { + $itemsType = $property['items']['type'] ?? 'object'; + $src = $required ? $mapAccess : $v; + $arraySource = $required + ? "((IEnumerable){$src})" + : "({$src} as IEnumerable)?"; + + $selectExpression = match ($itemsType) { + 'string' => 'x.ToString()', + 'integer' => 'Convert.ToInt64(x)', + 'number' => 'Convert.ToDouble(x)', + 'boolean' => '(bool)x', + default => 'x' + }; + + return "{$arraySource}.Select(x => {$selectExpression}).ToList()"; + } + + // Handle integer/number + if ($property['type'] === 'integer' || $property['type'] === 'number') { + $convertMethod = $property['type'] === 'integer' ? 'Int64' : 'Double'; + + if ($required) { + return "Convert.To{$convertMethod}({$mapAccess})"; + } + return "{$v} == null ? null : Convert.To{$convertMethod}({$v})"; + } + + // Handle boolean + if ($property['type'] === 'boolean') { + $typeName = $this->getTypeName($property); + + if ($required) { + return "({$typeName}){$mapAccess}"; + } + return "({$typeName}?){$v}"; + } + + // Handle string type + if ($required) { + return "{$mapAccess}.ToString()"; + } + return "{$v}?.ToString()"; + }, ['is_safe' => ['html']]), ]; } diff --git a/templates/dotnet/Package/Client.cs.twig b/templates/dotnet/Package/Client.cs.twig index 53bd7ba62c..d4bd56af0f 100644 --- a/templates/dotnet/Package/Client.cs.twig +++ b/templates/dotnet/Package/Client.cs.twig @@ -154,6 +154,7 @@ namespace {{ spec.title | caseUcfirst }} foreach (var parameter in parameters) { + if (parameter.Value == null) continue; if (parameter.Key == "file") { var fileContent = parameters["file"] as MultipartFormDataContent; diff --git a/templates/dotnet/Package/Converters/ObjectToInferredTypesConverter.cs.twig b/templates/dotnet/Package/Converters/ObjectToInferredTypesConverter.cs.twig index 563f92992a..ce772c93df 100644 --- a/templates/dotnet/Package/Converters/ObjectToInferredTypesConverter.cs.twig +++ b/templates/dotnet/Package/Converters/ObjectToInferredTypesConverter.cs.twig @@ -7,32 +7,60 @@ namespace {{ spec.title | caseUcfirst }}.Converters { public class ObjectToInferredTypesConverter : JsonConverter { - public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - switch (reader.TokenType) + using (JsonDocument document = JsonDocument.ParseValue(ref reader)) { - case JsonTokenType.True: - return true; - case JsonTokenType.False: - return false; - case JsonTokenType.Number: - if (reader.TryGetInt64(out long l)) + return ConvertElement(document.RootElement); + } + } + + private object? ConvertElement(JsonElement element) + { + switch (element.ValueKind) + { + case JsonValueKind.Object: + var dictionary = new Dictionary(); + foreach (var property in element.EnumerateObject()) { - return l; + dictionary[property.Name] = ConvertElement(property.Value); + } + return dictionary; + + case JsonValueKind.Array: + var list = new List(); + foreach (var item in element.EnumerateArray()) + { + list.Add(ConvertElement(item)); } - return reader.GetDouble(); - case JsonTokenType.String: - if (reader.TryGetDateTime(out DateTime datetime)) + return list; + + case JsonValueKind.String: + if (element.TryGetDateTime(out DateTime datetime)) { return datetime; } - return reader.GetString()!; - case JsonTokenType.StartObject: - return JsonSerializer.Deserialize>(ref reader, options)!; - case JsonTokenType.StartArray: - return JsonSerializer.Deserialize(ref reader, options)!; + return element.GetString(); + + case JsonValueKind.Number: + if (element.TryGetInt64(out long l)) + { + return l; + } + return element.GetDouble(); + + case JsonValueKind.True: + return true; + + case JsonValueKind.False: + return false; + + case JsonValueKind.Null: + case JsonValueKind.Undefined: + return null; + default: - return JsonDocument.ParseValue(ref reader).RootElement.Clone(); + throw new JsonException($"Unsupported JsonValueKind: {element.ValueKind}"); } } diff --git a/templates/dotnet/Package/Exception.cs.twig b/templates/dotnet/Package/Exception.cs.twig index e78d78c2cc..31d9c70adc 100644 --- a/templates/dotnet/Package/Exception.cs.twig +++ b/templates/dotnet/Package/Exception.cs.twig @@ -18,10 +18,10 @@ namespace {{spec.title | caseUcfirst}} this.Type = type; this.Response = response; } + public {{spec.title | caseUcfirst}}Exception(string message, Exception inner) : base(message, inner) { } } } - diff --git a/templates/dotnet/Package/Extensions/Extensions.cs.twig b/templates/dotnet/Package/Extensions/Extensions.cs.twig index d57318077e..ec325429fb 100644 --- a/templates/dotnet/Package/Extensions/Extensions.cs.twig +++ b/templates/dotnet/Package/Extensions/Extensions.cs.twig @@ -624,4 +624,4 @@ namespace {{ spec.title | caseUcfirst }}.Extensions return GetMimeTypeFromExtension(System.IO.Path.GetExtension(path)); } } -} \ No newline at end of file +} diff --git a/templates/dotnet/Package/Models/InputFile.cs.twig b/templates/dotnet/Package/Models/InputFile.cs.twig index 241a3adad5..aaf7a66202 100644 --- a/templates/dotnet/Package/Models/InputFile.cs.twig +++ b/templates/dotnet/Package/Models/InputFile.cs.twig @@ -1,5 +1,5 @@ using System.IO; -using Appwrite.Extensions; +using {{ spec.title | caseUcfirst }}.Extensions; namespace {{ spec.title | caseUcfirst }}.Models { @@ -38,4 +38,4 @@ namespace {{ spec.title | caseUcfirst }}.Models SourceType = "bytes" }; } -} \ No newline at end of file +} diff --git a/templates/dotnet/Package/Models/Model.cs.twig b/templates/dotnet/Package/Models/Model.cs.twig index b4f8aebeb8..978b6757d4 100644 --- a/templates/dotnet/Package/Models/Model.cs.twig +++ b/templates/dotnet/Package/Models/Model.cs.twig @@ -1,27 +1,29 @@ - +{% set DefinitionClass = definition.name | caseUcfirst | overrideIdentifier %} using System; using System.Linq; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; +{% if definition.properties | filter(p => p.enum) | length > 0 %} using {{ spec.title | caseUcfirst }}.Enums; +{% endif %} namespace {{ spec.title | caseUcfirst }}.Models { - public class {{ definition.name | caseUcfirst | overrideIdentifier }} + public class {{ DefinitionClass }} { {%~ for property in definition.properties %} [JsonPropertyName("{{ property.name }}")] - public {{ sub_schema(property) | raw }} {{ property_name(definition, property) | overrideProperty(definition.name) }} { get; private set; } + public {{ sub_schema(property) }} {{ property_name(definition, property) | overrideProperty(definition.name) }} { get; private set; } {%~ endfor %} {%~ if definition.additionalProperties %} public Dictionary Data { get; private set; } {%~ endif %} - public {{ definition.name | caseUcfirst | overrideIdentifier }}( + public {{ DefinitionClass }}( {%~ for property in definition.properties %} - {{ sub_schema(property) | raw }} {{ property.name | caseCamel | escapeKeyword }}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} + {{ sub_schema(property) }} {{ property.name | caseCamel | escapeKeyword }}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} {%~ endfor %} {%~ if definition.additionalProperties %} @@ -36,45 +38,14 @@ namespace {{ spec.title | caseUcfirst }}.Models {%~ endif %} } - public static {{ definition.name | caseUcfirst | overrideIdentifier }} From(Dictionary map) => new {{ definition.name | caseUcfirst | overrideIdentifier }}( + public static {{ DefinitionClass }} From(Dictionary map) => new {{ DefinitionClass }}( {%~ for property in definition.properties %} + {%~ set v = 'v' ~ loop.index0 %} + {%~ set mapAccess = 'map["' ~ property.name ~ '"]' %} {{ property.name | caseCamel | escapeKeyword | removeDollarSign }}:{{' '}} - {%- if property.sub_schema %} - {%- if property.type == 'array' -%} - map["{{ property.name }}"] is JsonElement jsonArray{{ loop.index }} ? jsonArray{{ loop.index }}.Deserialize>>()!.Select(it => {{ property.sub_schema | caseUcfirst | overrideIdentifier }}.From(map: it)).ToList() : ((IEnumerable>)map["{{ property.name }}"]).Select(it => {{ property.sub_schema | caseUcfirst | overrideIdentifier }}.From(map: it)).ToList() - {%- else -%} - {{ property.sub_schema | caseUcfirst | overrideIdentifier }}.From(map: map["{{ property.name }}"] is JsonElement jsonObj{{ loop.index }} ? jsonObj{{ loop.index }}.Deserialize>()! : (Dictionary)map["{{ property.name }}"]) - {%- endif %} - {%- elseif property.enum %} - {%- set enumName = property['enumName'] ?? property.name -%} - {%- if not property.required -%} - map.TryGetValue("{{ property.name }}", out var enumRaw{{ loop.index }}) - ? enumRaw{{ loop.index }} == null - ? null - : new {{ enumName | caseUcfirst }}(enumRaw{{ loop.index }}.ToString()!) - : null - {%- else -%} - new {{ enumName | caseUcfirst }}(map["{{ property.name }}"].ToString()!) - {%- endif %} - {%- else %} - {%- if property.type == 'array' -%} - map["{{ property.name }}"] is JsonElement jsonArrayProp{{ loop.index }} ? jsonArrayProp{{ loop.index }}.Deserialize<{{ property | typeName }}>()! : ({{ property | typeName }})map["{{ property.name }}"] - {%- else %} - {%- if property.type == "integer" or property.type == "number" %} - {%- if not property.required -%}map["{{ property.name }}"] == null ? null :{% endif %}Convert.To{% if property.type == "integer" %}Int64{% else %}Double{% endif %}(map["{{ property.name }}"]) - {%- else %} - {%- if property.type == "boolean" -%} - ({{ property | typeName }}{% if not property.required %}?{% endif %})map["{{ property.name }}"] - {%- else %} - {%- if not property.required -%} - map.TryGetValue("{{ property.name }}", out var {{ property.name | caseCamel | escapeKeyword | removeDollarSign }}) ? {{ property.name | caseCamel | escapeKeyword | removeDollarSign }}?.ToString() : null - {%- else -%} - map["{{ property.name }}"].ToString() - {%- endif %} - {%- endif %} - {%~ endif %} - {%~ endif %} - {%~ endif %} + {%- if not property.required -%}map.TryGetValue("{{ property.name }}", out var {{ v }}) ? {% endif -%} +{{ parse_value(property, mapAccess, v) }} + {%- if not property.required %} : null{% endif -%} {%- if not loop.last or (loop.last and definition.additionalProperties) %}, {%~ endif %} {%~ endfor %} diff --git a/templates/dotnet/Package/Models/UploadProgress.cs.twig b/templates/dotnet/Package/Models/UploadProgress.cs.twig index 47c78391ce..ee6fb58ba3 100644 --- a/templates/dotnet/Package/Models/UploadProgress.cs.twig +++ b/templates/dotnet/Package/Models/UploadProgress.cs.twig @@ -23,4 +23,4 @@ namespace {{ spec.title | caseUcfirst }} ChunksUploaded = chunksUploaded; } } -} \ No newline at end of file +} diff --git a/templates/dotnet/Package/Query.cs.twig b/templates/dotnet/Package/Query.cs.twig index 3230a2d5ad..5a8d0ec55d 100644 --- a/templates/dotnet/Package/Query.cs.twig +++ b/templates/dotnet/Package/Query.cs.twig @@ -274,4 +274,4 @@ namespace {{ spec.title | caseUcfirst }} return new Query("notTouches", attribute, new List { values }).ToString(); } } -} \ No newline at end of file +} diff --git a/templates/dotnet/Package/Role.cs.twig b/templates/dotnet/Package/Role.cs.twig index b3ecf2610b..3c7b2b33f3 100644 --- a/templates/dotnet/Package/Role.cs.twig +++ b/templates/dotnet/Package/Role.cs.twig @@ -1,4 +1,4 @@ -namespace Appwrite +namespace {{ spec.title | caseUcfirst }} { /// /// Helper class to generate role strings for Permission. @@ -89,4 +89,4 @@ namespace Appwrite return $"label:{name}"; } } -} \ No newline at end of file +} diff --git a/templates/dotnet/Package/Services/ServiceTemplate.cs.twig b/templates/dotnet/Package/Services/ServiceTemplate.cs.twig index 811078dbd6..ea96412674 100644 --- a/templates/dotnet/Package/Services/ServiceTemplate.cs.twig +++ b/templates/dotnet/Package/Services/ServiceTemplate.cs.twig @@ -1,5 +1,4 @@ {% import 'dotnet/base/utils.twig' as utils %} - using System; using System.Collections.Generic; using System.Linq;