diff --git a/.travis.yml b/.travis.yml index 8c27dbadc..663053727 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,6 +29,8 @@ env: - SDK=DartStable - SDK=Deno1193 - SDK=Deno1303 + - SDK=DotNet60 + - SDK=DotNet70 - SDK=FlutterStable - SDK=FlutterBeta - SDK=Go112 diff --git a/src/SDK/Language/DotNet.php b/src/SDK/Language/DotNet.php index 985453203..5c83ed503 100644 --- a/src/SDK/Language/DotNet.php +++ b/src/SDK/Language/DotNet.php @@ -139,23 +139,26 @@ public function getKeywords(): array public function getIdentifierOverrides(): array { return [ - 'Jwt' => 'JWT' + 'Jwt' => 'JWT', + 'Domain' => 'XDomain', ]; } /** - * @param $type + * @param array $parameter * @return string */ public function getTypeName(array $parameter): string { switch ($parameter['type']) { case self::TYPE_INTEGER: - return 'int'; + return 'long'; + case self::TYPE_NUMBER: + return 'double'; case self::TYPE_STRING: return 'string'; case self::TYPE_FILE: - return 'FileInfo'; + return 'InputFile'; case self::TYPE_BOOLEAN: return 'bool'; case self::TYPE_ARRAY: @@ -249,7 +252,13 @@ public function getParamExample(array $param): string $output .= '[object]'; break; case self::TYPE_ARRAY: - $output .= '[List]'; + if (\str_starts_with($example, '[')) { + $example = \substr($example, 1); + } + if (\str_ends_with($example, ']')) { + $example = \substr($example, 0, -1); + } + $output .= 'new List<' . $this->getTypeName($param['array']) . '> {' . $example . '}'; break; } } else { @@ -283,14 +292,19 @@ public function getFiles(): array return [ [ 'scope' => 'default', - 'destination' => 'README.md', - 'template' => 'dotnet/README.md.twig', + 'destination' => '.travis.yml', + 'template' => 'dotnet/.travis.yml.twig', ], [ 'scope' => 'default', 'destination' => 'CHANGELOG.md', 'template' => 'dotnet/CHANGELOG.md.twig', ], + [ + 'scope' => 'copy', + 'destination' => '/icon.png', + 'template' => 'dotnet/icon.png', + ], [ 'scope' => 'default', 'destination' => 'LICENSE', @@ -298,8 +312,8 @@ public function getFiles(): array ], [ 'scope' => 'default', - 'destination' => '.travis.yml', - 'template' => 'dotnet/.travis.yml.twig', + 'destination' => 'README.md', + 'template' => 'dotnet/README.md.twig', ], [ 'scope' => 'method', @@ -308,53 +322,78 @@ public function getFiles(): array ], [ 'scope' => 'default', - 'destination' => '/src/Appwrite.sln', + 'destination' => '/src/{{ spec.title | caseUcfirst }}.sln', 'template' => 'dotnet/src/Appwrite.sln', ], - [ - 'scope' => 'copy', - 'destination' => '/icon.png', - 'template' => 'dotnet/icon.png', - ], [ 'scope' => 'default', - 'destination' => '/src/Appwrite/Appwrite.csproj', + 'destination' => '/src/{{ spec.title | caseUcfirst }}/{{ spec.title | caseUcfirst }}.csproj', 'template' => 'dotnet/src/Appwrite/Appwrite.csproj.twig', ], [ 'scope' => 'default', - 'destination' => '/{{ sdk.namespace | caseSlash }}/src/Appwrite/Client.cs', + 'destination' => '/src/{{ spec.title | caseUcfirst }}/Client.cs', 'template' => 'dotnet/src/Appwrite/Client.cs.twig', ], [ 'scope' => 'default', - 'destination' => '/{{ sdk.namespace | caseSlash }}/src/Appwrite/Helpers/ExtensionMethods.cs', - 'template' => 'dotnet/src/Appwrite/Helpers/ExtensionMethods.cs', + 'destination' => '/src/{{ spec.title | caseUcfirst }}/{{ spec.title | caseUcfirst }}Exception.cs', + 'template' => 'dotnet/src/Appwrite/Exception.cs.twig', ], [ 'scope' => 'default', - 'destination' => '/{{ sdk.namespace | caseSlash }}/src/Appwrite/Models/OrderType.cs', + 'destination' => '/src/{{ spec.title | caseUcfirst }}/ID.cs', + 'template' => 'dotnet/src/Appwrite/ID.cs.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/src/{{ spec.title | caseUcfirst }}/Permission.cs', + 'template' => 'dotnet/src/Appwrite/Permission.cs.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/src/{{ spec.title | caseUcfirst }}/Query.cs', + 'template' => 'dotnet/src/Appwrite/Query.cs.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/src/{{ spec.title | caseUcfirst }}/Role.cs', + 'template' => 'dotnet/src/Appwrite/Role.cs.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/src/{{ spec.title | caseUcfirst }}/Extensions/Extensions.cs', + 'template' => 'dotnet/src/Appwrite/Extensions/Extensions.cs.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/src/{{ spec.title | caseUcfirst }}/Models/OrderType.cs', 'template' => 'dotnet/src/Appwrite/Models/OrderType.cs.twig', ], [ 'scope' => 'default', - 'destination' => '/{{ sdk.namespace | caseSlash }}/src/Appwrite/Models/Rule.cs', - 'template' => 'dotnet/src/Appwrite/Models/Rule.cs.twig', + 'destination' => '/src/{{ spec.title | caseUcfirst }}/Models/UploadProgress.cs', + 'template' => 'dotnet/src/Appwrite/Models/UploadProgress.cs.twig', ], [ 'scope' => 'default', - 'destination' => '/{{ sdk.namespace | caseSlash }}/src/Appwrite/Models/Exception.cs', - 'template' => 'dotnet/src/Appwrite/Models/Exception.cs.twig', + 'destination' => '/src/{{ spec.title | caseUcfirst }}/Models/InputFile.cs', + 'template' => 'dotnet/src/Appwrite/Models/InputFile.cs.twig', ], [ 'scope' => 'default', - 'destination' => '/{{ sdk.namespace | caseSlash }}/src/Appwrite/Services/Service.cs', + 'destination' => '/src/{{ spec.title | caseUcfirst }}/Services/Service.cs', 'template' => 'dotnet/src/Appwrite/Services/Service.cs.twig', ], [ 'scope' => 'service', - 'destination' => '/{{ sdk.namespace | caseSlash }}/src/Appwrite/Services/{{service.name | caseUcfirst}}.cs', + 'destination' => '/src/{{ spec.title | caseUcfirst }}/Services/{{service.name | caseUcfirst}}.cs', 'template' => 'dotnet/src/Appwrite/Services/ServiceTemplate.cs.twig', + ], + [ + 'scope' => 'definition', + 'destination' => '/src/{{ spec.title | caseUcfirst }}/Models/{{ definition.name | caseUcfirst | overrideIdentifier }}.cs', + 'template' => 'dotnet/src/Appwrite/Models/Model.cs.twig', ] ]; } diff --git a/templates/dotnet/base/params.twig b/templates/dotnet/base/params.twig new file mode 100644 index 000000000..1ce2c6fd2 --- /dev/null +++ b/templates/dotnet/base/params.twig @@ -0,0 +1,21 @@ +{% import 'dotnet/base/utils.twig' as utils %} + {%~ for parameter in method.parameters.path %} + .Replace("{{ '{' ~ parameter.name | caseCamel ~ '}' }}", {{ parameter.name | caseCamel | escapeKeyword }}){% if loop.last %};{% endif %} + + {%~ endfor %} + + var parameters = new Dictionary() + { + {%~ for parameter in method.parameters.query | merge(method.parameters.body) %} + { "{{ parameter.name }}", {{ utils.map_parameter(parameter) }} }{% if not loop.last %},{% endif %} + + {%~ endfor %} + }; + + var headers = new Dictionary() + { + {%~ for key, header in method.headers %} + { "{{ key }}", "{{ header }}" }{% if not loop.last %},{% endif %} + + {%~ endfor %} + }; diff --git a/templates/dotnet/base/requests/api.twig b/templates/dotnet/base/requests/api.twig new file mode 100644 index 000000000..bf030e99c --- /dev/null +++ b/templates/dotnet/base/requests/api.twig @@ -0,0 +1,11 @@ +{% import 'dotnet/base/utils.twig' as utils %} + return _client.Call{% if method.type != 'webAuth' %}<{{ utils.resultType(spec.title, method) }}>{% endif %}( + method: "{{ method.method | caseUpper }}", + path: path, + headers: headers, + {%~ if not method.responseModel %} + parameters: parameters.Where(it => it.Value != null).ToDictionary(it => it.Key, it => it.Value)!); + {%~ else %} + parameters: parameters.Where(it => it.Value != null).ToDictionary(it => it.Key, it => it.Value)!, + convert: Convert); + {%~ endif %} \ No newline at end of file diff --git a/templates/dotnet/base/requests/file.twig b/templates/dotnet/base/requests/file.twig new file mode 100644 index 000000000..902b5043d --- /dev/null +++ b/templates/dotnet/base/requests/file.twig @@ -0,0 +1,18 @@ + string? idParamName = {% if method.parameters.all | filter(p => p.isUploadID) | length > 0 %}{% for parameter in method.parameters.all | filter(parameter => parameter.isUploadID) %}"{{ parameter.name }}"{% endfor %}{% else %}null{% endif %}; + + {%~ for parameter in method.parameters.all %} + {%~ if parameter.type == 'file' %} + var paramName = "{{ parameter.name }}"; + {%~ endif %} + {%~ endfor %} + + return _client.ChunkedUpload( + path, + headers, + parameters, + {%~ if method.responseModel %} + Convert, + {%~ endif %} + paramName, + idParamName, + onProgress); \ No newline at end of file diff --git a/templates/dotnet/base/requests/location.twig b/templates/dotnet/base/requests/location.twig new file mode 100644 index 000000000..6b6b1301e --- /dev/null +++ b/templates/dotnet/base/requests/location.twig @@ -0,0 +1,5 @@ + return _client.Call( + method: "{{ method.method | caseUpper }}", + path: path, + headers: headers, + parameters: parameters.Where(it => it.Value != null).ToDictionary(it => it.Key, it => it.Value)!); \ No newline at end of file diff --git a/templates/dotnet/base/utils.twig b/templates/dotnet/base/utils.twig new file mode 100644 index 000000000..35ec0a8b8 --- /dev/null +++ b/templates/dotnet/base/utils.twig @@ -0,0 +1,16 @@ +{% macro parameter(parameter) %} +{% if parameter.name == 'orderType' %}{{ 'OrderType orderType = OrderType.ASC' }}{% else %} +{{ parameter | typeName }}{% if not parameter.required %}?{% endif %} {{ parameter.name | caseCamel | escapeKeyword }}{% if not parameter.required %} = null{% endif %}{% endif %} +{% endmacro %} +{% macro method_parameters(parameters, consumes) %} +{% if parameters.all|length > 0 %}{% for parameter in parameters.all | filter((param) => not param.isGlobal) %}{{ _self.parameter(parameter) }}{% if not loop.last %}{{ ', ' }}{% endif %}{% endfor %}{% if 'multipart/form-data' in consumes %},{% endif %}{% endif %}{% if 'multipart/form-data' in consumes %} Action? onProgress = null{% endif %} +{% endmacro %} +{% macro map_parameter(parameter) %} +{% if parameter.name == 'orderType' %}{{ parameter.name | caseCamel ~ '.ToString()'}}{% elseif parameter.isGlobal %}{{ parameter.name | caseUcfirst | escapeKeyword }}{% else %}{{ parameter.name | caseCamel | escapeKeyword }}{% endif %} +{% endmacro %} +{% macro methodNeedsSecurityParameters(method) %} +{% if (method.type == "webAuth" or method.type == "location") and method.auth|length > 0 %}{{ true }}{% else %}{{false}}{% endif %} +{% endmacro %} +{% macro resultType(namespace, method) %} +{% if method.type == "webAuth" %}bool{% elseif method.type == "location" %}byte[]{% elseif not method.responseModel or method.responseModel == 'any' %}object{% else %}Models.{{method.responseModel | caseUcfirst | overrideIdentifier }}{% endif %} +{% endmacro %} \ No newline at end of file diff --git a/templates/dotnet/docs/example.md.twig b/templates/dotnet/docs/example.md.twig index e8af18c6b..9d08f17e5 100644 --- a/templates/dotnet/docs/example.md.twig +++ b/templates/dotnet/docs/example.md.twig @@ -1,17 +1,17 @@ using {{ spec.title | caseUcfirst }}; +using {{ spec.title | caseUcfirst }}.Models; -Client client = new Client(); - +Client client = new Client() {% if method.auth|length > 0 %} -client - .SetEndPoint("https://cloud.appwrite.io/v1") // Your API Endpoint + .SetEndPoint("https://cloud.appwrite.io/v1") // Your API Endpoint {% for node in method.auth %} {% for key,header in node|keys %} - .Set{{header | caseUcfirst}}("{{node[header]["x-appwrite"]["demo"]}}") // {{node[header].description}} -{% endfor %} -{% endfor %}; + .Set{{header | caseUcfirst}}("{{node[header]['x-appwrite']['demo']}}"){% if loop.last %};{% endif %} // {{node[header].description}} +{% endfor %}{% endfor %}{% endif %} + +{{ service.name | caseUcfirst }} {{ service.name | caseCamel }} = new {{ service.name | caseUcfirst }}(client); -{% endif %} -{{ service.name | caseUcfirst }} {{ service.name | caseCamel }} = new {{ service.name | caseUcfirst }}(client{% if service.globalParams | length %}{% for parameter in service.globalParams %}, {{ parameter | paramExample }}{% endfor %}{% endif %}); +{% if method.type == 'location' %}byte[]{% else %}{{ method.responseModel | caseUcfirst | overrideIdentifier }}{% endif %} result = await {{ service.name | caseCamel }}.{{ method.name | caseUcfirst }}({% if method.parameters.all | length == 0 %});{% endif %} +{% for parameter in method.parameters.all %}{% if parameter.required %}{% if not loop.first %},{% endif %} -{% if method.type == 'location' %}string{% else %}HttpResponseMessage{% endif %} result = await {{ service.name | caseCamel }}.{{ method.name | caseUcfirst }}({% for parameter in method.parameters.all %}{% if parameter.required %}{% if not loop.first %}, {% endif %}{{ parameter | paramExample }}{% endif %}{% endfor %}); + {{ parameter.name }}: {{ parameter | paramExample }}{% endif %}{% endfor %}{% if method.parameters.all | length > 0 %});{% endif %} diff --git a/templates/dotnet/src/Appwrite/Appwrite.csproj.twig b/templates/dotnet/src/Appwrite/Appwrite.csproj.twig index 7cec3a863..fbb3d1b75 100644 --- a/templates/dotnet/src/Appwrite/Appwrite.csproj.twig +++ b/templates/dotnet/src/Appwrite/Appwrite.csproj.twig @@ -1,6 +1,6 @@ - netcoreapp3.1;net461;netstandard2.0; + netstandard2.0;net461 {{spec.title}} {{sdk.version}} {{spec.contactName}} @@ -14,11 +14,12 @@ git {{sdk.gitURL}} true + latest + enable - - + diff --git a/templates/dotnet/src/Appwrite/Client.cs.twig b/templates/dotnet/src/Appwrite/Client.cs.twig index 9611a6432..3692008d3 100644 --- a/templates/dotnet/src/Appwrite/Client.cs.twig +++ b/templates/dotnet/src/Appwrite/Client.cs.twig @@ -1,33 +1,61 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; +using {{ spec.title | caseUcfirst }}.Extensions; +using {{ spec.title | caseUcfirst }}.Models; namespace {{ spec.title | caseUcfirst }} { public class Client { - private readonly HttpClient http; - private readonly Dictionary headers; - private readonly Dictionary config; - private string endPoint; - private bool selfSigned; - - public Client() : this("https://appwrite.io/v1", false, new HttpClient()) + public string Endpoint => _endpoint; + public Dictionary Config => _config; + + private HttpClient _http; + private readonly Dictionary _headers; + private readonly Dictionary _config; + private string _endpoint; + + private static readonly int ChunkSize = 5 * 1024 * 1024; + + public static JsonSerializerSettings DeserializerSettings { get; set; } = new JsonSerializerSettings { - } + MetadataPropertyHandling = MetadataPropertyHandling.Ignore, + NullValueHandling = NullValueHandling.Ignore, + ContractResolver = new CamelCasePropertyNamesContractResolver(), + Converters = new List + { + new StringEnumConverter() + } + }; + + public static JsonSerializerSettings SerializerSettings { get; set; } = new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore, + ContractResolver = new CamelCasePropertyNamesContractResolver(), + Converters = new List + { + new StringEnumConverter() + } + }; - public Client(string endPoint, bool selfSigned, HttpClient http) + public Client( + string endpoint = "{{spec.endpoint}}", + bool selfSigned = false, + HttpClient? http = null) { - this.endPoint = endPoint; - this.selfSigned = selfSigned; - this.headers = new Dictionary() + _endpoint = endpoint; + _http = http ?? new HttpClient(); + _headers = new Dictionary() { { "content-type", "application/json" }, { "user-agent" , "{{spec.title | caseUcfirst}}{{ language.name | caseUcfirst }}SDK/{{ sdk.version }} (${Environment.OSVersion.Platform}; ${Environment.OSVersion.VersionString})"}, @@ -35,93 +63,111 @@ namespace {{ spec.title | caseUcfirst }} { "x-sdk-platform", "{{ sdk.platform }}" }, { "x-sdk-language", "{{ language.name | caseLower }}" }, { "x-sdk-version", "{{ sdk.version }}"}{% if spec.global.defaultHeaders | length > 0 %},{% endif %} - -{% for key,header in spec.global.defaultHeaders %} + {%~ for key,header in spec.global.defaultHeaders %} { "{{key}}", "{{header}}" }{% if not loop.last %},{% endif %} -{% endfor %} + {%~ endfor %} }; - this.config = new Dictionary(); - this.http = http; + + _config = new Dictionary(); + + if (selfSigned) + { + SetSelfSigned(true); + } + + JsonConvert.DefaultSettings = () => DeserializerSettings; } public Client SetSelfSigned(bool selfSigned) { - this.selfSigned = selfSigned; - return this; - } + var handler = new HttpClientHandler() + { + ServerCertificateCustomValidationCallback = (request, cert, chain, errors) => true + }; + + _http = selfSigned + ? new HttpClient(handler) + : new HttpClient(); - public Client SetEndPoint(string endPoint) - { - this.endPoint = endPoint; return this; } - public string GetEndPoint() + public Client SetEndpoint(string endpoint) { - return endPoint; - } + _endpoint = endpoint; - public Dictionary GetConfig() - { - return config; + return this; } -{% for header in spec.global.headers %} -{% if header.description %} + {%~ for header in spec.global.headers %} + {%~ if header.description %} /// {{header.description}} -{% endif %} + {%~ endif %} public Client Set{{header.key | caseUcfirst}}(string value) { - config.Add("{{ header.key | caseCamel }}", value); + _config.Add("{{ header.key | caseCamel }}", value); AddHeader("{{header.name}}", value); + return this; } -{% endfor %} - public Client AddHeader(String key, String value) + {%~ endfor %} + public Client AddHeader(string key, string value) { - headers.Add(key, value); + _headers.Add(key, value); + return this; } - public async Task Call(string method, string path, Dictionary headers, Dictionary parameters) + public Task> Call( + string method, + string path, + Dictionary headers, + Dictionary parameters) { - if (selfSigned) - { - ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true; - } + return Call>(method, path, headers, parameters); + } - bool methodGet = "GET".Equals(method, StringComparison.InvariantCultureIgnoreCase); + public async Task Call( + string method, + string path, + Dictionary headers, + Dictionary parameters, + Func, T>? convert = null) where T : class + { + var methodGet = "GET".Equals(method, StringComparison.OrdinalIgnoreCase); - string queryString = methodGet ? "?" + parameters.ToQueryString() : string.Empty; + var queryString = methodGet ? + "?" + parameters.ToQueryString() : + string.Empty; - HttpRequestMessage request = new HttpRequestMessage(new HttpMethod(method), endPoint + path + queryString); + var request = new HttpRequestMessage( + new HttpMethod(method), + _endpoint + path + queryString); - if ("multipart/form-data".Equals(headers["content-type"], StringComparison.InvariantCultureIgnoreCase)) + if ("multipart/form-data".Equals( + headers["content-type"], + StringComparison.OrdinalIgnoreCase)) { - MultipartFormDataContent form = new MultipartFormDataContent(); + var form = new MultipartFormDataContent(); foreach (var parameter in parameters) { if (parameter.Key == "file") { - FileInfo fi = parameters["file"] as FileInfo; - - var file = File.ReadAllBytes(fi.FullName); - - form.Add(new ByteArrayContent(file, 0, file.Length), "file", fi.Name); + form.Add(((MultipartFormDataContent)parameters["file"]).First()!); } - else if (parameter.Value is IEnumerable) + else if (parameter.Value is IEnumerable enumerable) { - List list = new List((IEnumerable) parameter.Value); + var list = new List(enumerable); for (int index = 0; index < list.Count; index++) { - form.Add(new StringContent(list[index].ToString()), $"{parameter.Key}[{index}]"); + form.Add(new StringContent(list[index].ToString()!), $"{parameter.Key}[{index}]"); } } else { - form.Add(new StringContent(parameter.Value.ToString()), parameter.Key); + form.Add(new StringContent(parameter.Value.ToString()!), parameter.Key); } } request.Content = form; @@ -134,27 +180,31 @@ namespace {{ spec.title | caseUcfirst }} request.Content = new StringContent(body, Encoding.UTF8, "application/json"); } - foreach (var header in this.headers) + foreach (var header in _headers) { - if (header.Key.Equals("content-type", StringComparison.InvariantCultureIgnoreCase)) + if (header.Key.Equals("content-type", StringComparison.OrdinalIgnoreCase)) { - http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(header.Value)); + _http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(header.Value)); } else { - if (http.DefaultRequestHeaders.Contains(header.Key)) { - http.DefaultRequestHeaders.Remove(header.Key); + if (_http.DefaultRequestHeaders.Contains(header.Key)) { + _http.DefaultRequestHeaders.Remove(header.Key); } - http.DefaultRequestHeaders.Add(header.Key, header.Value); + _http.DefaultRequestHeaders.Add(header.Key, header.Value); } } foreach (var header in headers) { - if (header.Key.Equals("content-type", StringComparison.InvariantCultureIgnoreCase)) + if (header.Key.Equals("content-type", StringComparison.OrdinalIgnoreCase)) { request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header.Value)); } + else if (header.Key.Equals("content-range", StringComparison.OrdinalIgnoreCase)) + { + request.Content.Headers.TryAddWithoutValidation(header.Key, header.Value); + } else { if (request.Headers.Contains(header.Key)) { @@ -163,30 +213,180 @@ namespace {{ spec.title | caseUcfirst }} request.Headers.Add(header.Key, header.Value); } } - try + + var response = await _http.SendAsync(request); + var code = (int)response.StatusCode; + var contentType = response.Content.Headers + .GetValues("Content-Type") + .FirstOrDefault() ?? string.Empty; + + var isJson = contentType.Contains("application/json"); + var isBytes = contentType.Contains("application/octet-stream"); + + if (code >= 400) { + var message = await response.Content.ReadAsStringAsync(); + + if (isJson) { + message = JObject.Parse(message)["message"]!.ToString(); + } + + throw new {{spec.title | caseUcfirst}}Exception(message, code); + } + + if (isJson) { - var httpResponseMessage = await http.SendAsync(request); - var code = (int) httpResponseMessage.StatusCode; - var response = await httpResponseMessage.Content.ReadAsStringAsync(); + var responseString = await response.Content.ReadAsStringAsync(); - if (code >= 400) { - var message = response.ToString(); - var isJson = httpResponseMessage.Content.Headers.GetValues("Content-Type").FirstOrDefault().Contains("application/json"); + var dict = JsonConvert.DeserializeObject>( + responseString, + DeserializerSettings); - if (isJson) { - message = (JObject.Parse(message))["message"].ToString(); - } + if (convert != null) + { + return convert(dict!); + } + + return (dict as T)!; + } + else if (isBytes) + { + return ((await response.Content.ReadAsByteArrayAsync()) as T)!; + } + else + { + return default!; + } + } + + public async Task ChunkedUpload( + string path, + Dictionary headers, + Dictionary parameters, + Func, T> converter, + string paramName, + string? idParamName = null, + Action? onProgress = null) where T : class + { + var input = parameters[paramName] as InputFile; + var size = 0L; + switch(input.SourceType) + { + case "path": + var info = new FileInfo(input.Path); + input.Data = info.OpenRead(); + size = info.Length; + break; + case "stream": + size = (input.Data as Stream).Length; + break; + case "bytes": + size = ((byte[])input.Data).Length; + break; + }; - throw new {{spec.title | caseUcfirst}}Exception(message, code, response.ToString()); + var offset = 0L; + var buffer = new byte[Math.Min(size, ChunkSize)]; + var result = new Dictionary(); + + if (size < ChunkSize) + { + switch(input.SourceType) + { + case "path": + case "stream": + await (input.Data as Stream).ReadAsync(buffer, 0, (int)size); + break; + case "bytes": + buffer = (byte[])input.Data; + break; } - return httpResponseMessage; + var content = new MultipartFormDataContent { + { new ByteArrayContent(buffer), paramName, input.Filename } + }; + + parameters[paramName] = content; + + return await Call( + method: "POST", + path, + headers, + parameters, + converter + ); } - catch (System.Exception e) + + if (!string.IsNullOrEmpty(idParamName) && (string)parameters[idParamName] != "unique()") { - throw new {{spec.title | caseUcfirst}}Exception(e.Message, e); + // Make a request to check if a file already exists + var current = await Call>( + method: "GET", + path: "$path/${params[idParamName]}", + headers, + parameters = new Dictionary() + ); + var chunksUploaded = (long)current["chunksUploaded"]; + offset = Math.Min(chunksUploaded * ChunkSize, size); + } + + while (offset < size) + { + switch(input.SourceType) + { + case "path": + case "stream": + var stream = input.Data as Stream; + stream.Seek(offset, SeekOrigin.Begin); + await stream.ReadAsync(buffer, 0, ChunkSize); + break; + case "bytes": + buffer = ((byte[])input.Data) + .Skip((int)offset) + .Take((int)Math.Min(size - offset, ChunkSize)) + .ToArray(); + break; + } + + var content = new MultipartFormDataContent { + { new ByteArrayContent(buffer), paramName, input.Filename } + }; + + parameters[paramName] = content; + + headers["Content-Range"] = + $"bytes {offset}-{Math.Min(offset + ChunkSize - 1, size)}/{size}"; + + result = await Call>( + method: "POST", + path, + headers, + parameters + ); + + offset += ChunkSize; + + var id = result.ContainsKey("$id") + ? result["$id"]?.ToString() ?? string.Empty + : string.Empty; + var chunksTotal = result.ContainsKey("chunksTotal") + ? (long)result["chunksTotal"] + : 0; + var chunksUploaded = result.ContainsKey("chunksUploaded") + ? (long)result["chunksUploaded"] + : 0; + + headers["x-appwrite-id"] = id; + + onProgress?.Invoke( + new UploadProgress( + id: id, + progress: Math.Min(offset, size) / size * 100, + sizeUploaded: Math.Min(offset, size), + chunksTotal: chunksTotal, + chunksUploaded: chunksUploaded)); } + return converter(result); } } } diff --git a/templates/dotnet/src/Appwrite/Models/Exception.cs.twig b/templates/dotnet/src/Appwrite/Exception.cs.twig similarity index 51% rename from templates/dotnet/src/Appwrite/Models/Exception.cs.twig rename to templates/dotnet/src/Appwrite/Exception.cs.twig index c53875da9..eb967f973 100644 --- a/templates/dotnet/src/Appwrite/Models/Exception.cs.twig +++ b/templates/dotnet/src/Appwrite/Exception.cs.twig @@ -1,13 +1,16 @@ using System; -namespace Appwrite +namespace {{spec.title | caseUcfirst}} { public class {{spec.title | caseUcfirst}}Exception : Exception { - public int? Code; - public string Response = null; - public {{spec.title | caseUcfirst}}Exception(string message = null, int? code = null, string response = null) - : base(message) + public int? Code { get; set; } + public string? Response { get; set; } = null; + + public {{spec.title | caseUcfirst}}Exception( + string? message = null, + int? code = null, + string? response = null) : base(message) { this.Code = code; this.Response = response; diff --git a/templates/dotnet/src/Appwrite/Extensions/Extensions.cs.twig b/templates/dotnet/src/Appwrite/Extensions/Extensions.cs.twig new file mode 100644 index 000000000..10b2b5035 --- /dev/null +++ b/templates/dotnet/src/Appwrite/Extensions/Extensions.cs.twig @@ -0,0 +1,627 @@ +using Newtonsoft.Json; +using System; +using System.Collections; +using System.Collections.Generic; + +namespace {{ spec.title | caseUcfirst }}.Extensions +{ + public static class Extensions + { + public static string ToJson(this Dictionary dict) + { + return JsonConvert.SerializeObject(dict, Client.SerializerSettings); + } + + public static string ToQueryString(this Dictionary parameters) + { + var query = new List(); + + foreach (var kvp in parameters) + { + switch (kvp.Value) + { + case null: + continue; + case IList list: + foreach (var item in list) + { + query.Add($"{kvp.Key}[]={item}"); + } + break; + default: + query.Add($"{kvp.Key}={kvp.Value.ToString()}"); + break; + } + } + + return Uri.EscapeUriString(string.Join("&", query)); + } + + private static IDictionary _mappings = new Dictionary(StringComparer.InvariantCultureIgnoreCase) { + + #region Mime Types + {".323", "text/h323"}, + {".3g2", "video/3gpp2"}, + {".3gp", "video/3gpp"}, + {".3gp2", "video/3gpp2"}, + {".3gpp", "video/3gpp"}, + {".7z", "application/x-7z-compressed"}, + {".aa", "audio/audible"}, + {".AAC", "audio/aac"}, + {".aaf", "application/octet-stream"}, + {".aax", "audio/vnd.audible.aax"}, + {".ac3", "audio/ac3"}, + {".aca", "application/octet-stream"}, + {".accda", "application/msaccess.addin"}, + {".accdb", "application/msaccess"}, + {".accdc", "application/msaccess.cab"}, + {".accde", "application/msaccess"}, + {".accdr", "application/msaccess.runtime"}, + {".accdt", "application/msaccess"}, + {".accdw", "application/msaccess.webapplication"}, + {".accft", "application/msaccess.ftemplate"}, + {".acx", "application/internet-property-stream"}, + {".AddIn", "text/xml"}, + {".ade", "application/msaccess"}, + {".adobebridge", "application/x-bridge-url"}, + {".adp", "application/msaccess"}, + {".ADT", "audio/vnd.dlna.adts"}, + {".ADTS", "audio/aac"}, + {".afm", "application/octet-stream"}, + {".ai", "application/postscript"}, + {".aif", "audio/x-aiff"}, + {".aifc", "audio/aiff"}, + {".aiff", "audio/aiff"}, + {".air", "application/vnd.adobe.air-application-installer-package+zip"}, + {".amc", "application/x-mpeg"}, + {".application", "application/x-ms-application"}, + {".art", "image/x-jg"}, + {".asa", "application/xml"}, + {".asax", "application/xml"}, + {".ascx", "application/xml"}, + {".asd", "application/octet-stream"}, + {".asf", "video/x-ms-asf"}, + {".ashx", "application/xml"}, + {".asi", "application/octet-stream"}, + {".asm", "text/plain"}, + {".asmx", "application/xml"}, + {".aspx", "application/xml"}, + {".asr", "video/x-ms-asf"}, + {".asx", "video/x-ms-asf"}, + {".atom", "application/atom+xml"}, + {".au", "audio/basic"}, + {".avi", "video/x-msvideo"}, + {".axs", "application/olescript"}, + {".bas", "text/plain"}, + {".bcpio", "application/x-bcpio"}, + {".bin", "application/octet-stream"}, + {".bmp", "image/bmp"}, + {".c", "text/plain"}, + {".cab", "application/octet-stream"}, + {".caf", "audio/x-caf"}, + {".calx", "application/vnd.ms-office.calx"}, + {".cat", "application/vnd.ms-pki.seccat"}, + {".cc", "text/plain"}, + {".cd", "text/plain"}, + {".cdda", "audio/aiff"}, + {".cdf", "application/x-cdf"}, + {".cer", "application/x-x509-ca-cert"}, + {".chm", "application/octet-stream"}, + {".class", "application/x-java-applet"}, + {".clp", "application/x-msclip"}, + {".cmx", "image/x-cmx"}, + {".cnf", "text/plain"}, + {".cod", "image/cis-cod"}, + {".config", "application/xml"}, + {".contact", "text/x-ms-contact"}, + {".coverage", "application/xml"}, + {".cpio", "application/x-cpio"}, + {".cpp", "text/plain"}, + {".crd", "application/x-mscardfile"}, + {".crl", "application/pkix-crl"}, + {".crt", "application/x-x509-ca-cert"}, + {".cs", "text/plain"}, + {".csdproj", "text/plain"}, + {".csh", "application/x-csh"}, + {".csproj", "text/plain"}, + {".css", "text/css"}, + {".csv", "text/csv"}, + {".cur", "application/octet-stream"}, + {".cxx", "text/plain"}, + {".dat", "application/octet-stream"}, + {".datasource", "application/xml"}, + {".dbproj", "text/plain"}, + {".dcr", "application/x-director"}, + {".def", "text/plain"}, + {".deploy", "application/octet-stream"}, + {".der", "application/x-x509-ca-cert"}, + {".dgml", "application/xml"}, + {".dib", "image/bmp"}, + {".dif", "video/x-dv"}, + {".dir", "application/x-director"}, + {".disco", "text/xml"}, + {".dll", "application/x-msdownload"}, + {".dll.config", "text/xml"}, + {".dlm", "text/dlm"}, + {".doc", "application/msword"}, + {".docm", "application/vnd.ms-word.document.macroEnabled.12"}, + {".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, + {".dot", "application/msword"}, + {".dotm", "application/vnd.ms-word.template.macroEnabled.12"}, + {".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"}, + {".dsp", "application/octet-stream"}, + {".dsw", "text/plain"}, + {".dtd", "text/xml"}, + {".dtsConfig", "text/xml"}, + {".dv", "video/x-dv"}, + {".dvi", "application/x-dvi"}, + {".dwf", "drawing/x-dwf"}, + {".dwp", "application/octet-stream"}, + {".dxr", "application/x-director"}, + {".eml", "message/rfc822"}, + {".emz", "application/octet-stream"}, + {".eot", "application/octet-stream"}, + {".eps", "application/postscript"}, + {".etl", "application/etl"}, + {".etx", "text/x-setext"}, + {".evy", "application/envoy"}, + {".exe", "application/octet-stream"}, + {".exe.config", "text/xml"}, + {".fdf", "application/vnd.fdf"}, + {".fif", "application/fractals"}, + {".filters", "Application/xml"}, + {".fla", "application/octet-stream"}, + {".flr", "x-world/x-vrml"}, + {".flv", "video/x-flv"}, + {".fsscript", "application/fsharp-script"}, + {".fsx", "application/fsharp-script"}, + {".generictest", "application/xml"}, + {".gif", "image/gif"}, + {".group", "text/x-ms-group"}, + {".gsm", "audio/x-gsm"}, + {".gtar", "application/x-gtar"}, + {".gz", "application/x-gzip"}, + {".h", "text/plain"}, + {".hdf", "application/x-hdf"}, + {".hdml", "text/x-hdml"}, + {".hhc", "application/x-oleobject"}, + {".hhk", "application/octet-stream"}, + {".hhp", "application/octet-stream"}, + {".hlp", "application/winhlp"}, + {".hpp", "text/plain"}, + {".hqx", "application/mac-binhex40"}, + {".hta", "application/hta"}, + {".htc", "text/x-component"}, + {".htm", "text/html"}, + {".html", "text/html"}, + {".htt", "text/webviewhtml"}, + {".hxa", "application/xml"}, + {".hxc", "application/xml"}, + {".hxd", "application/octet-stream"}, + {".hxe", "application/xml"}, + {".hxf", "application/xml"}, + {".hxh", "application/octet-stream"}, + {".hxi", "application/octet-stream"}, + {".hxk", "application/xml"}, + {".hxq", "application/octet-stream"}, + {".hxr", "application/octet-stream"}, + {".hxs", "application/octet-stream"}, + {".hxt", "text/html"}, + {".hxv", "application/xml"}, + {".hxw", "application/octet-stream"}, + {".hxx", "text/plain"}, + {".i", "text/plain"}, + {".ico", "image/x-icon"}, + {".ics", "application/octet-stream"}, + {".idl", "text/plain"}, + {".ief", "image/ief"}, + {".iii", "application/x-iphone"}, + {".inc", "text/plain"}, + {".inf", "application/octet-stream"}, + {".inl", "text/plain"}, + {".ins", "application/x-internet-signup"}, + {".ipa", "application/x-itunes-ipa"}, + {".ipg", "application/x-itunes-ipg"}, + {".ipproj", "text/plain"}, + {".ipsw", "application/x-itunes-ipsw"}, + {".iqy", "text/x-ms-iqy"}, + {".isp", "application/x-internet-signup"}, + {".ite", "application/x-itunes-ite"}, + {".itlp", "application/x-itunes-itlp"}, + {".itms", "application/x-itunes-itms"}, + {".itpc", "application/x-itunes-itpc"}, + {".IVF", "video/x-ivf"}, + {".jar", "application/java-archive"}, + {".java", "application/octet-stream"}, + {".jck", "application/liquidmotion"}, + {".jcz", "application/liquidmotion"}, + {".jfif", "image/pjpeg"}, + {".jnlp", "application/x-java-jnlp-file"}, + {".jpb", "application/octet-stream"}, + {".jpe", "image/jpeg"}, + {".jpeg", "image/jpeg"}, + {".jpg", "image/jpeg"}, + {".js", "application/x-javascript"}, + {".json", "application/json"}, + {".jsx", "text/jscript"}, + {".jsxbin", "text/plain"}, + {".latex", "application/x-latex"}, + {".library-ms", "application/windows-library+xml"}, + {".lit", "application/x-ms-reader"}, + {".loadtest", "application/xml"}, + {".lpk", "application/octet-stream"}, + {".lsf", "video/x-la-asf"}, + {".lst", "text/plain"}, + {".lsx", "video/x-la-asf"}, + {".lzh", "application/octet-stream"}, + {".m13", "application/x-msmediaview"}, + {".m14", "application/x-msmediaview"}, + {".m1v", "video/mpeg"}, + {".m2t", "video/vnd.dlna.mpeg-tts"}, + {".m2ts", "video/vnd.dlna.mpeg-tts"}, + {".m2v", "video/mpeg"}, + {".m3u", "audio/x-mpegurl"}, + {".m3u8", "audio/x-mpegurl"}, + {".m4a", "audio/m4a"}, + {".m4b", "audio/m4b"}, + {".m4p", "audio/m4p"}, + {".m4r", "audio/x-m4r"}, + {".m4v", "video/x-m4v"}, + {".mac", "image/x-macpaint"}, + {".mak", "text/plain"}, + {".man", "application/x-troff-man"}, + {".manifest", "application/x-ms-manifest"}, + {".map", "text/plain"}, + {".master", "application/xml"}, + {".mda", "application/msaccess"}, + {".mdb", "application/x-msaccess"}, + {".mde", "application/msaccess"}, + {".mdp", "application/octet-stream"}, + {".me", "application/x-troff-me"}, + {".mfp", "application/x-shockwave-flash"}, + {".mht", "message/rfc822"}, + {".mhtml", "message/rfc822"}, + {".mid", "audio/mid"}, + {".midi", "audio/mid"}, + {".mix", "application/octet-stream"}, + {".mk", "text/plain"}, + {".mmf", "application/x-smaf"}, + {".mno", "text/xml"}, + {".mny", "application/x-msmoney"}, + {".mod", "video/mpeg"}, + {".mov", "video/quicktime"}, + {".movie", "video/x-sgi-movie"}, + {".mp2", "video/mpeg"}, + {".mp2v", "video/mpeg"}, + {".mp3", "audio/mpeg"}, + {".mp4", "video/mp4"}, + {".mp4v", "video/mp4"}, + {".mpa", "video/mpeg"}, + {".mpe", "video/mpeg"}, + {".mpeg", "video/mpeg"}, + {".mpf", "application/vnd.ms-mediapackage"}, + {".mpg", "video/mpeg"}, + {".mpp", "application/vnd.ms-project"}, + {".mpv2", "video/mpeg"}, + {".mqv", "video/quicktime"}, + {".ms", "application/x-troff-ms"}, + {".msi", "application/octet-stream"}, + {".mso", "application/octet-stream"}, + {".mts", "video/vnd.dlna.mpeg-tts"}, + {".mtx", "application/xml"}, + {".mvb", "application/x-msmediaview"}, + {".mvc", "application/x-miva-compiled"}, + {".mxp", "application/x-mmxp"}, + {".nc", "application/x-netcdf"}, + {".nsc", "video/x-ms-asf"}, + {".nws", "message/rfc822"}, + {".ocx", "application/octet-stream"}, + {".oda", "application/oda"}, + {".odc", "text/x-ms-odc"}, + {".odh", "text/plain"}, + {".odl", "text/plain"}, + {".odp", "application/vnd.oasis.opendocument.presentation"}, + {".ods", "application/oleobject"}, + {".odt", "application/vnd.oasis.opendocument.text"}, + {".one", "application/onenote"}, + {".onea", "application/onenote"}, + {".onepkg", "application/onenote"}, + {".onetmp", "application/onenote"}, + {".onetoc", "application/onenote"}, + {".onetoc2", "application/onenote"}, + {".orderedtest", "application/xml"}, + {".osdx", "application/opensearchdescription+xml"}, + {".p10", "application/pkcs10"}, + {".p12", "application/x-pkcs12"}, + {".p7b", "application/x-pkcs7-certificates"}, + {".p7c", "application/pkcs7-mime"}, + {".p7m", "application/pkcs7-mime"}, + {".p7r", "application/x-pkcs7-certreqresp"}, + {".p7s", "application/pkcs7-signature"}, + {".pbm", "image/x-portable-bitmap"}, + {".pcast", "application/x-podcast"}, + {".pct", "image/pict"}, + {".pcx", "application/octet-stream"}, + {".pcz", "application/octet-stream"}, + {".pdf", "application/pdf"}, + {".pfb", "application/octet-stream"}, + {".pfm", "application/octet-stream"}, + {".pfx", "application/x-pkcs12"}, + {".pgm", "image/x-portable-graymap"}, + {".pic", "image/pict"}, + {".pict", "image/pict"}, + {".pkgdef", "text/plain"}, + {".pkgundef", "text/plain"}, + {".pko", "application/vnd.ms-pki.pko"}, + {".pls", "audio/scpls"}, + {".pma", "application/x-perfmon"}, + {".pmc", "application/x-perfmon"}, + {".pml", "application/x-perfmon"}, + {".pmr", "application/x-perfmon"}, + {".pmw", "application/x-perfmon"}, + {".png", "image/png"}, + {".pnm", "image/x-portable-anymap"}, + {".pnt", "image/x-macpaint"}, + {".pntg", "image/x-macpaint"}, + {".pnz", "image/png"}, + {".pot", "application/vnd.ms-powerpoint"}, + {".potm", "application/vnd.ms-powerpoint.template.macroEnabled.12"}, + {".potx", "application/vnd.openxmlformats-officedocument.presentationml.template"}, + {".ppa", "application/vnd.ms-powerpoint"}, + {".ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12"}, + {".ppm", "image/x-portable-pixmap"}, + {".pps", "application/vnd.ms-powerpoint"}, + {".ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12"}, + {".ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"}, + {".ppt", "application/vnd.ms-powerpoint"}, + {".pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12"}, + {".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, + {".prf", "application/pics-rules"}, + {".prm", "application/octet-stream"}, + {".prx", "application/octet-stream"}, + {".ps", "application/postscript"}, + {".psc1", "application/PowerShell"}, + {".psd", "application/octet-stream"}, + {".psess", "application/xml"}, + {".psm", "application/octet-stream"}, + {".psp", "application/octet-stream"}, + {".pub", "application/x-mspublisher"}, + {".pwz", "application/vnd.ms-powerpoint"}, + {".qht", "text/x-html-insertion"}, + {".qhtm", "text/x-html-insertion"}, + {".qt", "video/quicktime"}, + {".qti", "image/x-quicktime"}, + {".qtif", "image/x-quicktime"}, + {".qtl", "application/x-quicktimeplayer"}, + {".qxd", "application/octet-stream"}, + {".ra", "audio/x-pn-realaudio"}, + {".ram", "audio/x-pn-realaudio"}, + {".rar", "application/octet-stream"}, + {".ras", "image/x-cmu-raster"}, + {".rat", "application/rat-file"}, + {".rc", "text/plain"}, + {".rc2", "text/plain"}, + {".rct", "text/plain"}, + {".rdlc", "application/xml"}, + {".resx", "application/xml"}, + {".rf", "image/vnd.rn-realflash"}, + {".rgb", "image/x-rgb"}, + {".rgs", "text/plain"}, + {".rm", "application/vnd.rn-realmedia"}, + {".rmi", "audio/mid"}, + {".rmp", "application/vnd.rn-rn_music_package"}, + {".roff", "application/x-troff"}, + {".rpm", "audio/x-pn-realaudio-plugin"}, + {".rqy", "text/x-ms-rqy"}, + {".rtf", "application/rtf"}, + {".rtx", "text/richtext"}, + {".ruleset", "application/xml"}, + {".s", "text/plain"}, + {".safariextz", "application/x-safari-safariextz"}, + {".scd", "application/x-msschedule"}, + {".sct", "text/scriptlet"}, + {".sd2", "audio/x-sd2"}, + {".sdp", "application/sdp"}, + {".sea", "application/octet-stream"}, + {".searchConnector-ms", "application/windows-search-connector+xml"}, + {".setpay", "application/set-payment-initiation"}, + {".setreg", "application/set-registration-initiation"}, + {".settings", "application/xml"}, + {".sgimb", "application/x-sgimb"}, + {".sgml", "text/sgml"}, + {".sh", "application/x-sh"}, + {".shar", "application/x-shar"}, + {".shtml", "text/html"}, + {".sit", "application/x-stuffit"}, + {".sitemap", "application/xml"}, + {".skin", "application/xml"}, + {".sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12"}, + {".sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"}, + {".slk", "application/vnd.ms-excel"}, + {".sln", "text/plain"}, + {".slupkg-ms", "application/x-ms-license"}, + {".smd", "audio/x-smd"}, + {".smi", "application/octet-stream"}, + {".smx", "audio/x-smd"}, + {".smz", "audio/x-smd"}, + {".snd", "audio/basic"}, + {".snippet", "application/xml"}, + {".snp", "application/octet-stream"}, + {".sol", "text/plain"}, + {".sor", "text/plain"}, + {".spc", "application/x-pkcs7-certificates"}, + {".spl", "application/futuresplash"}, + {".src", "application/x-wais-source"}, + {".srf", "text/plain"}, + {".SSISDeploymentManifest", "text/xml"}, + {".ssm", "application/streamingmedia"}, + {".sst", "application/vnd.ms-pki.certstore"}, + {".stl", "application/vnd.ms-pki.stl"}, + {".sv4cpio", "application/x-sv4cpio"}, + {".sv4crc", "application/x-sv4crc"}, + {".svc", "application/xml"}, + {".swf", "application/x-shockwave-flash"}, + {".t", "application/x-troff"}, + {".tar", "application/x-tar"}, + {".tcl", "application/x-tcl"}, + {".testrunconfig", "application/xml"}, + {".testsettings", "application/xml"}, + {".tex", "application/x-tex"}, + {".texi", "application/x-texinfo"}, + {".texinfo", "application/x-texinfo"}, + {".tgz", "application/x-compressed"}, + {".thmx", "application/vnd.ms-officetheme"}, + {".thn", "application/octet-stream"}, + {".tif", "image/tiff"}, + {".tiff", "image/tiff"}, + {".tlh", "text/plain"}, + {".tli", "text/plain"}, + {".toc", "application/octet-stream"}, + {".tr", "application/x-troff"}, + {".trm", "application/x-msterminal"}, + {".trx", "application/xml"}, + {".ts", "video/vnd.dlna.mpeg-tts"}, + {".tsv", "text/tab-separated-values"}, + {".ttf", "application/octet-stream"}, + {".tts", "video/vnd.dlna.mpeg-tts"}, + {".txt", "text/plain"}, + {".u32", "application/octet-stream"}, + {".uls", "text/iuls"}, + {".user", "text/plain"}, + {".ustar", "application/x-ustar"}, + {".vb", "text/plain"}, + {".vbdproj", "text/plain"}, + {".vbk", "video/mpeg"}, + {".vbproj", "text/plain"}, + {".vbs", "text/vbscript"}, + {".vcf", "text/x-vcard"}, + {".vcproj", "Application/xml"}, + {".vcs", "text/plain"}, + {".vcxproj", "Application/xml"}, + {".vddproj", "text/plain"}, + {".vdp", "text/plain"}, + {".vdproj", "text/plain"}, + {".vdx", "application/vnd.ms-visio.viewer"}, + {".vml", "text/xml"}, + {".vscontent", "application/xml"}, + {".vsct", "text/xml"}, + {".vsd", "application/vnd.visio"}, + {".vsi", "application/ms-vsi"}, + {".vsix", "application/vsix"}, + {".vsixlangpack", "text/xml"}, + {".vsixmanifest", "text/xml"}, + {".vsmdi", "application/xml"}, + {".vspscc", "text/plain"}, + {".vss", "application/vnd.visio"}, + {".vsscc", "text/plain"}, + {".vssettings", "text/xml"}, + {".vssscc", "text/plain"}, + {".vst", "application/vnd.visio"}, + {".vstemplate", "text/xml"}, + {".vsto", "application/x-ms-vsto"}, + {".vsw", "application/vnd.visio"}, + {".vsx", "application/vnd.visio"}, + {".vtx", "application/vnd.visio"}, + {".wav", "audio/wav"}, + {".wave", "audio/wav"}, + {".wax", "audio/x-ms-wax"}, + {".wbk", "application/msword"}, + {".wbmp", "image/vnd.wap.wbmp"}, + {".wcm", "application/vnd.ms-works"}, + {".wdb", "application/vnd.ms-works"}, + {".wdp", "image/vnd.ms-photo"}, + {".webarchive", "application/x-safari-webarchive"}, + {".webtest", "application/xml"}, + {".wiq", "application/xml"}, + {".wiz", "application/msword"}, + {".wks", "application/vnd.ms-works"}, + {".WLMP", "application/wlmoviemaker"}, + {".wlpginstall", "application/x-wlpg-detect"}, + {".wlpginstall3", "application/x-wlpg3-detect"}, + {".wm", "video/x-ms-wm"}, + {".wma", "audio/x-ms-wma"}, + {".wmd", "application/x-ms-wmd"}, + {".wmf", "application/x-msmetafile"}, + {".wml", "text/vnd.wap.wml"}, + {".wmlc", "application/vnd.wap.wmlc"}, + {".wmls", "text/vnd.wap.wmlscript"}, + {".wmlsc", "application/vnd.wap.wmlscriptc"}, + {".wmp", "video/x-ms-wmp"}, + {".wmv", "video/x-ms-wmv"}, + {".wmx", "video/x-ms-wmx"}, + {".wmz", "application/x-ms-wmz"}, + {".wpl", "application/vnd.ms-wpl"}, + {".wps", "application/vnd.ms-works"}, + {".wri", "application/x-mswrite"}, + {".wrl", "x-world/x-vrml"}, + {".wrz", "x-world/x-vrml"}, + {".wsc", "text/scriptlet"}, + {".wsdl", "text/xml"}, + {".wvx", "video/x-ms-wvx"}, + {".x", "application/directx"}, + {".xaf", "x-world/x-vrml"}, + {".xaml", "application/xaml+xml"}, + {".xap", "application/x-silverlight-app"}, + {".xbap", "application/x-ms-xbap"}, + {".xbm", "image/x-xbitmap"}, + {".xdr", "text/plain"}, + {".xht", "application/xhtml+xml"}, + {".xhtml", "application/xhtml+xml"}, + {".xla", "application/vnd.ms-excel"}, + {".xlam", "application/vnd.ms-excel.addin.macroEnabled.12"}, + {".xlc", "application/vnd.ms-excel"}, + {".xld", "application/vnd.ms-excel"}, + {".xlk", "application/vnd.ms-excel"}, + {".xll", "application/vnd.ms-excel"}, + {".xlm", "application/vnd.ms-excel"}, + {".xls", "application/vnd.ms-excel"}, + {".xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12"}, + {".xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12"}, + {".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, + {".xlt", "application/vnd.ms-excel"}, + {".xltm", "application/vnd.ms-excel.template.macroEnabled.12"}, + {".xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"}, + {".xlw", "application/vnd.ms-excel"}, + {".xml", "text/xml"}, + {".xmta", "application/xml"}, + {".xof", "x-world/x-vrml"}, + {".XOML", "text/plain"}, + {".xpm", "image/x-xpixmap"}, + {".xps", "application/vnd.ms-xpsdocument"}, + {".xrm-ms", "text/xml"}, + {".xsc", "application/xml"}, + {".xsd", "text/xml"}, + {".xsf", "text/xml"}, + {".xsl", "text/xml"}, + {".xslt", "text/xml"}, + {".xsn", "application/octet-stream"}, + {".xss", "application/xml"}, + {".xtp", "application/octet-stream"}, + {".xwd", "image/x-xwindowdump"}, + {".z", "application/x-compress"}, + {".zip", "application/x-zip-compressed"}, + #endregion + + }; + + public static string GetMimeTypeFromExtension(string extension) + { + if (extension == null) + { + throw new ArgumentNullException("extension"); + } + + if (!extension.StartsWith(".")) + { + extension = "." + extension; + } + + return _mappings.TryGetValue(extension, out var mime) ? mime : "application/octet-stream"; + } + + public static string GetMimeType(this string path) + { + return GetMimeTypeFromExtension(System.IO.Path.GetExtension(path)); + } + } +} \ No newline at end of file diff --git a/templates/dotnet/src/Appwrite/Helpers/ExtensionMethods.cs b/templates/dotnet/src/Appwrite/Helpers/ExtensionMethods.cs deleted file mode 100644 index 323137a7c..000000000 --- a/templates/dotnet/src/Appwrite/Helpers/ExtensionMethods.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; -using System; -using System.Collections.Generic; - -namespace {{ spec.title | caseUcfirst }} -{ - public static class ExtensionMethods - { - public static string ToJson(this Dictionary dict) - { - var settings = new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver(), - Converters = new List { new StringEnumConverter() } - }; - - return JsonConvert.SerializeObject(dict, settings); - } - - public static string ToQueryString(this Dictionary parameters) - { - List query = new List(); - - foreach (KeyValuePair parameter in parameters) - { - if (parameter.Value != null) - { - if (parameter.Value is List) - { - foreach(object entry in (dynamic) parameter.Value) - { - query.Add(parameter.Key + "[]=" + Uri.EscapeUriString(entry.ToString())); - } - } - else - { - query.Add(parameter.Key + "=" + Uri.EscapeUriString(parameter.Value.ToString())); - } - } - } - return string.Join("&", query); - } - } -} \ No newline at end of file diff --git a/templates/dotnet/src/Appwrite/ID.cs.twig b/templates/dotnet/src/Appwrite/ID.cs.twig new file mode 100644 index 000000000..c58310e42 --- /dev/null +++ b/templates/dotnet/src/Appwrite/ID.cs.twig @@ -0,0 +1,15 @@ +namespace {{ spec.title | caseUcfirst }} +{ + public static class ID + { + public static string Unique() + { + return "unique()"; + } + + public static string Custom(string id) + { + return id; + } + } +} \ No newline at end of file diff --git a/templates/dotnet/src/Appwrite/Models/InputFile.cs.twig b/templates/dotnet/src/Appwrite/Models/InputFile.cs.twig new file mode 100644 index 000000000..5d98b2167 --- /dev/null +++ b/templates/dotnet/src/Appwrite/Models/InputFile.cs.twig @@ -0,0 +1,41 @@ +using System.IO; +using Appwrite.Extensions; + +namespace {{ spec.title | caseUcfirst }}.Models +{ + public class InputFile + { + public string Path { get; set; } + public string Filename { get; set; } + public string MimeType { get; set; } + public string SourceType { get; set; } + public object Data { get; set; } + + public static InputFile FromPath(string path) => new InputFile + { + Path = path, + Filename = System.IO.Path.GetFileName(path), + MimeType = path.GetMimeType(), + SourceType = "path" + }; + + public static InputFile FromFileInfo(FileInfo fileInfo) => + InputFile.FromPath(fileInfo.FullName); + + public static InputFile FromStream(Stream stream, string filename, string mimeType) => new InputFile + { + Data = stream, + Filename = filename, + MimeType = mimeType, + SourceType = "stream" + }; + + public static InputFile FromBytes(byte[] bytes, string filename, string mimeType) => new InputFile + { + Data = bytes, + Filename = filename, + MimeType = mimeType, + SourceType = "bytes" + }; + } +} \ No newline at end of file diff --git a/templates/dotnet/src/Appwrite/Models/Model.cs.twig b/templates/dotnet/src/Appwrite/Models/Model.cs.twig new file mode 100644 index 000000000..d30396036 --- /dev/null +++ b/templates/dotnet/src/Appwrite/Models/Model.cs.twig @@ -0,0 +1,79 @@ +{% macro sub_schema(property) %}{% if property.sub_schema %}{% if property.type == 'array' %}List<{{property.sub_schema | caseUcfirst | overrideIdentifier}}>{% else %}{{property.sub_schema | caseUcfirst | overrideIdentifier}}{% endif %}{% else %}{{property | typeName}}{% endif %}{% if not property.required %}?{% endif %}{% endmacro %} +{% macro property_name(definition, property) %}{{ property.name | caseUcfirst | removeDollarSign | escapeKeyword}}{% endmacro %} + +using System; +using System.Linq; +using System.Collections.Generic; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace {{ spec.title | caseUcfirst }}.Models +{ + public class {{ definition.name | caseUcfirst | overrideIdentifier }} + { + {%~ for property in definition.properties %} + [JsonProperty("{{ property.name }}")] + public {{ _self.sub_schema(property) }} {{ _self.property_name(definition, property) }} { get; private set; } + + {%~ endfor %} + {%~ if definition.additionalProperties %} + public Dictionary Data { get; private set; } + + {%~ endif %} + public {{ definition.name | caseUcfirst | overrideIdentifier }}( + {%~ for property in definition.properties %} + {{ _self.sub_schema(property) }} {{ property.name | caseCamel | escapeKeyword }}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} + + {%~ endfor %} + {%~ if definition.additionalProperties %} + Dictionary data + {%~ endif %} + ) { + {%~ for property in definition.properties %} + {{ _self.property_name(definition, property) }} = {{ property.name | caseCamel | escapeKeyword }}; + {%~ endfor %} + {%~ if definition.additionalProperties %} + Data = data; + {%~ endif %} + } + + public static {{ definition.name | caseUcfirst | overrideIdentifier}} From(Dictionary map) => new {{ definition.name | caseUcfirst | overrideIdentifier}}( + {%~ for property in definition.properties %} + {{ property.name | caseCamel | escapeKeyword | removeDollarSign }}: {% if property.sub_schema %}{% if property.type == 'array' %}((JArray)map["{{ property.name }}"]).ToObject>>().Select(it => {{property.sub_schema | caseUcfirst | overrideIdentifier}}.From(map: it)).ToList(){% else %}{{property.sub_schema | caseUcfirst | overrideIdentifier}}.From(map: ((JObject)map["{{ property.name }}"]).ToObject>()!){% endif %}{% else %}{% if property.type == 'array' %}((JArray)map["{{ property.name }}"]).ToObject<{{ property | typeName }}>(){% 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 %}({{ property | typeName }}{% if not property.required %}?{% endif %})map["{{ property.name }}"]{% endif %}{% endif %}{% endif %}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} + + {%~ endfor %} + {%~ if definition.additionalProperties %} + data: map + {%~ endif %} + ); + + public Dictionary ToMap() => new Dictionary() + { + {%~ for property in definition.properties %} + { "{{ property.name }}", {% if property.sub_schema %}{% if property.type == 'array' %}{{ _self.property_name(definition, property) }}.Select(it => it.ToMap()){% else %}{{ _self.property_name(definition, property) }}.ToMap(){% endif %}{% else %}{{ _self.property_name(definition, property) }}{% endif %}{{ ' }' }}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} + + {%~ endfor %} + {%~ if definition.additionalProperties %} + { "data", Data } + {%~ endif %} + }; + {%~ if definition.additionalProperties %} + + public T ConvertTo(Func, T> fromJson) => + fromJson.Invoke(Data); + {%~ endif %} + {%~ for property in definition.properties %} + {%~ if property.sub_schema %} + {%~ for def in spec.definitions %} + {%~ if def.name == property.sub_schema and def.additionalProperties and property.type == 'array' %} + + public T ConvertTo(Func, T> fromJson) => + (T){{ property.name | caseUcfirst | escapeKeyword }}.Select(it => it.ConvertTo(fromJson)); + + {%~ endif %} + {%~ endfor %} + {%~ endif %} + {%~ endfor %} + } +} \ No newline at end of file diff --git a/templates/dotnet/src/Appwrite/Models/Rule.cs.twig b/templates/dotnet/src/Appwrite/Models/Rule.cs.twig deleted file mode 100644 index 71234033c..000000000 --- a/templates/dotnet/src/Appwrite/Models/Rule.cs.twig +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace {{ spec.title | caseUcfirst }} -{ - public class Rule - { - public string Label { get; set; } - public string Key { get; set; } - public string Type { get; set; } - public string Default { get; set; } - public bool Required { get; set; } - public bool Array { get; set; } - } -} diff --git a/templates/dotnet/src/Appwrite/Models/UploadProgress.cs.twig b/templates/dotnet/src/Appwrite/Models/UploadProgress.cs.twig new file mode 100644 index 000000000..47c78391c --- /dev/null +++ b/templates/dotnet/src/Appwrite/Models/UploadProgress.cs.twig @@ -0,0 +1,26 @@ +namespace {{ spec.title | caseUcfirst }} +{ + public class UploadProgress + { + public string Id { get; private set; } + public double Progress { get; private set; } + public long SizeUploaded { get; private set; } + public long ChunksTotal { get; private set; } + public long ChunksUploaded { get; private set; } + + public UploadProgress( + string id, + double progress, + long sizeUploaded, + long chunksTotal, + long chunksUploaded + ) + { + Id = id; + Progress = progress; + SizeUploaded = sizeUploaded; + ChunksTotal = chunksTotal; + ChunksUploaded = chunksUploaded; + } + } +} \ No newline at end of file diff --git a/templates/dotnet/src/Appwrite/Permission.cs.twig b/templates/dotnet/src/Appwrite/Permission.cs.twig new file mode 100644 index 000000000..5bde420f1 --- /dev/null +++ b/templates/dotnet/src/Appwrite/Permission.cs.twig @@ -0,0 +1,30 @@ +namespace {{ spec.title | caseUcfirst }} +{ + public static class Permission + { + public static string Read(string role) + { + return $"read(\"{role}\")"; + } + + public static string Write(string role) + { + return $"write(\"{role}\")"; + } + + public static string Create(string role) + { + return $"create(\"{role}\")"; + } + + public static string Update(string role) + { + return $"update(\"{role}\")"; + } + + public static string Delete(string role) + { + return $"delete(\"{role}\")"; + } + } +} diff --git a/templates/dotnet/src/Appwrite/Query.cs.twig b/templates/dotnet/src/Appwrite/Query.cs.twig new file mode 100644 index 000000000..c940f4d6b --- /dev/null +++ b/templates/dotnet/src/Appwrite/Query.cs.twig @@ -0,0 +1,139 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Appwrite +{ + public static class Query + { + public static string Equal(string attribute, object value) + { + return AddQuery(attribute, "equal", value); + } + + public static string NotEqual(string attribute, object value) + { + return AddQuery(attribute, "notEqual", value); + } + + public static string LessThan(string attribute, object value) + { + return AddQuery(attribute, "lessThan", value); + } + + public static string LessThanEqual(string attribute, object value) + { + return AddQuery(attribute, "lessThanEqual", value); + } + + public static string GreaterThan(string attribute, object value) + { + return AddQuery(attribute, "greaterThan", value); + } + + public static string GreaterThanEqual(string attribute, object value) + { + return AddQuery(attribute, "greaterThanEqual", value); + } + + public static string Search(string attribute, string value) + { + return AddQuery(attribute, "search", value); + } + + public static string IsNull(string attribute) + { + return $"isNull(\"{attribute}\")"; + } + + public static string IsNotNull(string attribute) + { + return $"isNotNull(\"{attribute}\")"; + } + + public static string StartsWith(string attribute, string value) + { + return AddQuery(attribute, "startsWith", value); + } + + public static string EndsWith(string attribute, string value) + { + return AddQuery(attribute, "endsWith", value); + } + + public static string Between(string attribute, string start, string end) + { + return AddQuery(attribute, "between", new List { start, end }); + } + + public static string Between(string attribute, int start, int end) + { + return AddQuery(attribute, "between", new List { start, end }); + } + + public static string Between(string attribute, double start, double end) + { + return AddQuery(attribute, "between", new List { start, end }); + } + + public static string Select(List attributes) + { + return $"select([{string.Join(",", attributes.Select(attribute => $"\"{attribute}\""))}])"; + } + + public static string CursorAfter(string documentId) + { + return $"cursorAfter(\"{documentId}\")"; + } + + public static string CursorBefore(string documentId) { + return $"cursorBefore(\"{documentId}\")"; + } + + public static string OrderAsc(string attribute) { + return $"orderAsc(\"{attribute}\")"; + } + + public static string OrderDesc(string attribute) { + return $"orderDesc(\"{attribute}\")"; + } + + public static string Limit(int limit) { + return $"limit({limit})"; + } + + public static string Offset(int offset) { + return $"offset({offset})"; + } + + private static string AddQuery(string attribute, string method, object value) + { + if (value is IList list) + { + var parsed = new List(); + foreach (var item in list) + { + parsed.Add(ParseValues(item)); + } + return $"{method}(\"{attribute}\", [{string.Join(",", parsed)}])"; + } + else + { + return $"{method}(\"{attribute}\", [{ParseValues(value)}])"; + } + } + + private static string ParseValues(object value) + { + switch (value) + { + case string str: + return $"\"{str}\""; + case bool boolean: + return boolean.ToString().ToLower(); + default: + return value.ToString(); + } + } + } +} \ No newline at end of file diff --git a/templates/dotnet/src/Appwrite/Role.cs.twig b/templates/dotnet/src/Appwrite/Role.cs.twig new file mode 100644 index 000000000..7fc1fdbcd --- /dev/null +++ b/templates/dotnet/src/Appwrite/Role.cs.twig @@ -0,0 +1,41 @@ +namespace Appwrite +{ + public static class Role + { + public static string Any() + { + return "any"; + } + + public static string User(string id, string status = "") + { + return status == string.Empty + ? $"user:{id}" + : $"user:{id}/{status}"; + } + + public static string Users(string status = "") + { + return status == string.Empty + ? "users" : + $"users/{status}"; + } + + public static string Guests() + { + return "guests"; + } + + public static string Team(string id, string role = "") + { + return role == string.Empty + ? $"team:{id}" + : $"team:{id}/{role}"; + } + + public static string Member(string id) + { + return $"member:{id}"; + } + } +} \ No newline at end of file diff --git a/templates/dotnet/src/Appwrite/Services/ServiceTemplate.cs.twig b/templates/dotnet/src/Appwrite/Services/ServiceTemplate.cs.twig index 95ff6dcd2..a7711ec93 100644 --- a/templates/dotnet/src/Appwrite/Services/ServiceTemplate.cs.twig +++ b/templates/dotnet/src/Appwrite/Services/ServiceTemplate.cs.twig @@ -1,73 +1,58 @@ -{% macro parameter(parameter) %} -{% if parameter.name == 'orderType' %}{{ 'OrderType orderType = OrderType.ASC' }}{% else %} -{{ parameter | typeName }}{% if not parameter.required and (parameter.type == 'boolean' or parameter.type == 'integer') %}?{% endif %} {{ parameter.name | caseCamel | escapeKeyword }}{{ parameter | paramDefault }}{% endif %} -{% endmacro %} -{% macro method_parameters(parameters) %} -{% if parameters.all|length > 0 %}{% for parameter in parameters.all %}{{ _self.parameter(parameter) }}{% if not loop.last %}{{ ', ' }}{% endif %}{% endfor %}{% endif %} -{% endmacro %} -{% macro map_parameter(parameter) %} -{% if parameter.name == 'orderType' %}{{ parameter.name | caseCamel ~ '.ToString()'}}{% else %} -{{ parameter.name | caseCamel | escapeKeyword }}{% endif %} -{% endmacro %} -{% macro methodNeedsSecurityParameters(method) %} -{% if (method.type == "webAuth" or method.type == "location") and method.auth|length > 0 %}{{ true }}{% else %}{{false}}{% endif %} -{% endmacro %} +{% import 'dotnet/base/utils.twig' as utils %} +using System; using System.Collections.Generic; -using System.IO; -using System.Net.Http; +using System.Linq; using System.Threading.Tasks; +using {{ spec.title | caseUcfirst }}.Models; -namespace {{ spec.title | caseUcfirst }} +namespace {{ spec.title | caseUcfirst }}.Services { public class {{ service.name | caseUcfirst }} : Service { - public {{ service.name | caseUcfirst }}(Client client) : base(client) { } + public {{ service.name | caseUcfirst }}(Client client) : base(client) + { + } -{% for method in service.methods %} -{% if method.title %} + {%~ for method in service.methods %} + {%~ if method.title %} /// /// {{ method.title }} -{% endif %} -{% if method.description %} + {%~ endif %} + {%~ if method.description %} /// -{{method.description|dotnetComment}} + {{~ method.description | dotnetComment }} /// -{% endif %} + {%~ endif %} /// - public {% if method.type == "location" %}string{% else %}async Task{% endif %} {{ method.name | caseUcfirst }}({{ _self.method_parameters(method.parameters) }}) + public Task{% if method.type != "webAuth" %}<{{ utils.resultType(spec.title, method) }}>{% endif %} {{ method.name | caseUcfirst }}({{ utils.method_parameters(method.parameters, method.consumes) }}) { - string path = "{{ method.path }}"{% for parameter in method.parameters.path %}.Replace("{{ '{' ~ parameter.name | caseCamel ~ '}' }}", {{ parameter.name | caseCamel | escapeKeyword }}){% endfor %}; - - Dictionary parameters = new Dictionary() - { -{% for parameter in method.parameters.query | merge(method.parameters.body) %} - { "{{ parameter.name }}", {{ _self.map_parameter(parameter) }} }{% if not loop.last or _self.methodNeedsSecurityParameters(method) %},{% endif %} - -{% endfor %} - }; -{% if _self.methodNeedsSecurityParameters(method) %}{% for node in method.auth %} -{% for key,header in node|keys %} - // { "{{header|caseLower}}", _client.GetConfig().get("{{header|caseLower}}") }{% if not loop.last %},{% endif %} - -{% endfor %} -{% endfor %} -{% endif %} -{% if method.type == 'location' %} - return _client.GetEndPoint() + path + "?" + parameters.ToQueryString(); -{% else %} - - Dictionary headers = new Dictionary() - { -{{ method.headers|map((header, key) => " { \"#{key}\", \"#{header}\" }")|join(',\n')|raw }} - }; - - return await _client.Call("{{ method.method | caseUpper }}", path, headers, parameters); -{% endif %} + var path = "{{ method.path }}"{% if method.parameters.path | length == 0 %};{% endif %} + + {{~ include('dotnet/base/params.twig') }} + + {%~ if method.type == 'location' %} + {{~ include('dotnet/base/requests/location.twig') }} + {%~ else %} + + {%~ if method.responseModel %} + static {{ utils.resultType(spec.title, method) }} Convert(Dictionary it) => + {%~ if method.responseModel == 'any' %} + it; + {%~ else %} + {{ utils.resultType(spec.title, method) }}.From(map: it); + {%~ endif %} + {%~ endif %} + + {%~ if 'multipart/form-data' in method.consumes %} + {{~ include('dotnet/base/requests/file.twig') }} + {%~ else %} + + {{~ include('dotnet/base/requests/api.twig')}} + {%~ endif %} + {%~ endif %} } -{% if not loop.last %} -{% endif %} -{% endfor %} - }; -} \ No newline at end of file + {%~ endfor %} + } +} diff --git a/tests/DotNet31Test.php b/tests/DotNet31Test.php new file mode 100644 index 000000000..9d0b6733a --- /dev/null +++ b/tests/DotNet31Test.php @@ -0,0 +1,34 @@ +() { "string in array" }); + TestContext.WriteLine(mock.Result); + + mock = await foo.Post("string", 123, new List() { "string in array" }); + TestContext.WriteLine(mock.Result); + + mock = await foo.Put("string", 123, new List() { "string in array" }); + TestContext.WriteLine(mock.Result); + + mock = await foo.Patch("string", 123, new List() { "string in array" }); + TestContext.WriteLine(mock.Result); + + mock = await foo.Delete("string", 123, new List() { "string in array" }); + TestContext.WriteLine(mock.Result); + + // Bar Tests + mock = await bar.Get("string", 123, new List() { "string in array" }); + TestContext.WriteLine(mock.Result); + + mock = await bar.Post("string", 123, new List() { "string in array" }); + TestContext.WriteLine(mock.Result); + + mock = await bar.Put("string", 123, new List() { "string in array" }); + TestContext.WriteLine(mock.Result); + + mock = await bar.Patch("string", 123, new List() { "string in array" }); + TestContext.WriteLine(mock.Result); + + mock = await bar.Delete("string", 123, new List() { "string in array" }); + TestContext.WriteLine(mock.Result); + + // General Tests + var result = await general.Redirect(); + TestContext.WriteLine((result as Dictionary)["result"]); + + mock = await general.Upload("string", 123, new List() { "string in array" }, InputFile.FromPath("../../../../../../../resources/file.png")); + TestContext.WriteLine(mock.Result); + + mock = await general.Upload("string", 123, new List() { "string in array" }, InputFile.FromPath("../../../../../../../resources/large_file.mp4")); + TestContext.WriteLine(mock.Result); + + var info = new FileInfo("../../../../../../../resources/file.png"); + mock = await general.Upload("string", 123, new List() { "string in array" }, InputFile.FromStream(info.OpenRead(), "file.png", "image/png")); + TestContext.WriteLine(mock.Result); + + info = new FileInfo("../../../../../../../resources/large_file.mp4"); + mock = await general.Upload("string", 123, new List() { "string in array" }, InputFile.FromStream(info.OpenRead(), "large_file.mp4", "video/mp4")); + TestContext.WriteLine(mock.Result); + + try + { + await general.Error400(); + } + catch (AppwriteException e) + { + TestContext.WriteLine(e.Message); + } + + try + { + await general.Error500(); + } + catch (AppwriteException e) + { + TestContext.WriteLine(e.Message); + } + + try + { + await general.Error502(); + } + catch (AppwriteException e) + { + TestContext.WriteLine(e.Message); + } + + await general.Empty(); + + // Query helper tests + TestContext.WriteLine(Query.Equal("released", new List { true })); + TestContext.WriteLine(Query.Equal("title", new List { "Spiderman", "Dr. Strange" })); + TestContext.WriteLine(Query.NotEqual("title", "Spiderman")); + TestContext.WriteLine(Query.LessThan("releasedYear", 1990)); + TestContext.WriteLine(Query.GreaterThan("releasedYear", 1990)); + TestContext.WriteLine(Query.Search("name", "john")); + TestContext.WriteLine(Query.IsNull("name")); + TestContext.WriteLine(Query.IsNotNull("name")); + TestContext.WriteLine(Query.Between("age", 50, 100)); + TestContext.WriteLine(Query.Between("age", 50.5, 100.5)); + TestContext.WriteLine(Query.Between("name", "Anna", "Brad")); + TestContext.WriteLine(Query.StartsWith("name", "Ann")); + TestContext.WriteLine(Query.EndsWith("name", "nne")); + TestContext.WriteLine(Query.Select(new List { "name", "age" })); + TestContext.WriteLine(Query.OrderAsc("title")); + TestContext.WriteLine(Query.OrderDesc("title")); + TestContext.WriteLine(Query.CursorAfter("my_movie_id")); + TestContext.WriteLine(Query.CursorBefore("my_movie_id")); + TestContext.WriteLine(Query.Limit(50)); + TestContext.WriteLine(Query.Offset(20)); + + // Permission & Roles helper tests + TestContext.WriteLine(Permission.Read(Role.Any())); + TestContext.WriteLine(Permission.Write(Role.User(ID.Custom("userid")))); + TestContext.WriteLine(Permission.Create(Role.Users())); + TestContext.WriteLine(Permission.Update(Role.Guests())); + TestContext.WriteLine(Permission.Delete(Role.Team("teamId", "owner"))); + TestContext.WriteLine(Permission.Delete(Role.Team("teamId"))); + TestContext.WriteLine(Permission.Create(Role.Member("memberId"))); + TestContext.WriteLine(Permission.Update(Role.Users("verified")));; + TestContext.WriteLine(Permission.Update(Role.User(ID.Custom("userid"), "unverified")));; + + // ID helper tests + TestContext.WriteLine(ID.Unique()); + TestContext.WriteLine(ID.Custom("custom_id")); + + mock = await general.Headers(); + TestContext.WriteLine(mock.Result); + } + } +} \ No newline at end of file diff --git a/tests/languages/dotnet/Tests60.csproj b/tests/languages/dotnet/Tests60.csproj new file mode 100644 index 000000000..15ffbc6b4 --- /dev/null +++ b/tests/languages/dotnet/Tests60.csproj @@ -0,0 +1,14 @@ + + + net6.0 + latest + false + + + + + + + + + diff --git a/tests/languages/dotnet/Tests70.csproj b/tests/languages/dotnet/Tests70.csproj new file mode 100644 index 000000000..392e78b36 --- /dev/null +++ b/tests/languages/dotnet/Tests70.csproj @@ -0,0 +1,14 @@ + + + net7.0 + latest + false + + + + + + + + + diff --git a/tests/languages/dotnet/tests.ps1 b/tests/languages/dotnet/tests.ps1 deleted file mode 100644 index bf8b5c808..000000000 --- a/tests/languages/dotnet/tests.ps1 +++ /dev/null @@ -1,96 +0,0 @@ -function Await-Task { - param ( - [Parameter(ValueFromPipeline=$true, Mandatory=$true)] - $task - ) - - process { - while (-not $task.AsyncWaitHandle.WaitOne(200)) { } - $task.GetAwaiter() - } -} - -function Print-Response { - param ( - $response - ) - Write-Host ($response.Content.ReadAsStringAsync().Result | ConvertFrom-Json).result -} - -Add-Type -Path "/app/tests/sdks/dotnet/src/test/Appwrite.dll" | Out-Null - -$client = New-Object Appwrite.Client -$foo = New-Object Appwrite.Foo -ArgumentList $client -$bar = New-Object Appwrite.Bar -ArgumentList $client -$general = New-Object Appwrite.General -ArgumentList $client - -Write-Host -Write-Host "Test Started" - -$list = $("string in array") -$response = $foo.Get("string", 123, $list) | Await-Task -Print-Response $response.GetResult() - -$response = $foo.Post("string", 123, $list) | Await-Task -Print-Response $response.GetResult() - -$response = $foo.Put("string", 123, $list) | Await-Task -Print-Response $response.GetResult() - -$response = $foo.Patch("string", 123, $list) | Await-Task -Print-Response $response.GetResult() - -$response = $foo.Delete("string", 123, $list) | Await-Task -Print-Response $response.GetResult() - -$response = $bar.Get("string", 123, $list) | Await-Task -Print-Response $response.GetResult() - -$response = $bar.Post("string", 123, $list) | Await-Task -Print-Response $response.GetResult() - -$response = $bar.Put("string", 123, $list) | Await-Task -Print-Response $response.GetResult() - -$response = $bar.Patch("string", 123, $list) | Await-Task -Print-Response $response.GetResult() - -$response = $bar.Delete("string", 123, $list) | Await-Task -Print-Response $response.GetResult() - -$response = $general.Redirect() | Await-Task -Print-Response $response.GetResult() - -$response = $general.Upload("string", 123, $list, (Get-Item "../../../../resources/file.png")) | Await-Task -Print-Response $response.GetResult() - -try { - $response = $general.Empty() | Await-Task - $response.GetResult() | Out-Null -} catch [Appwrite.AppwriteException] { - Write-Host $_.Exception.Message -} - -try { - $response = $general.Error400() | Await-Task - $response.GetResult() -} catch [Appwrite.AppwriteException] { - Write-Host $_.Exception.Message -} - -try { - $response = $general.Error500() | Await-Task - $response.GetResult() -} catch [Appwrite.AppwriteException] { - Write-Host $_.Exception.Message -} - -try { - $response = $general.Error502() | Await-Task - $response.GetResult() -} catch [Appwrite.AppwriteException] { - Write-Host $_.Exception.Message -} - -$response = $general.headers() | Await-Task -Print-Response $response.GetResult() \ No newline at end of file