diff --git a/src/SDK/Language/CLI.php b/src/SDK/Language/CLI.php index aa922619b..a072f3b56 100644 --- a/src/SDK/Language/CLI.php +++ b/src/SDK/Language/CLI.php @@ -231,6 +231,11 @@ public function getFiles(): array 'destination' => 'lib/type-generation/languages/dart.js', 'template' => 'cli/lib/type-generation/languages/dart.js.twig', ], + [ + 'scope' => 'default', + 'destination' => 'lib/type-generation/languages/csharp.js', + 'template' => 'cli/lib/type-generation/languages/csharp.js.twig', + ], [ 'scope' => 'default', 'destination' => 'lib/questions.js', diff --git a/templates/cli/lib/commands/types.js.twig b/templates/cli/lib/commands/types.js.twig index 16beecbbc..07a85a896 100644 --- a/templates/cli/lib/commands/types.js.twig +++ b/templates/cli/lib/commands/types.js.twig @@ -12,6 +12,7 @@ const { Swift } = require("../type-generation/languages/swift"); const { Java } = require("../type-generation/languages/java"); const { Dart } = require("../type-generation/languages/dart"); const { JavaScript } = require("../type-generation/languages/javascript"); +const { CSharp } = require("../type-generation/languages/csharp"); /** * @param {string} language @@ -33,6 +34,8 @@ function createLanguageMeta(language) { return new Java(); case "dart": return new Dart(); + case "cs": + return new CSharp(); default: throw new Error(`Language '${language}' is not supported`); } @@ -55,7 +58,7 @@ const typesLanguageOption = new Option( "-l, --language ", "The language of the types" ) - .choices(["auto", "ts", "js", "php", "kotlin", "swift", "java", "dart"]) + .choices(["auto", "ts", "js", "php", "kotlin", "swift", "java", "dart", "cs"]) .default("auto"); const typesStrictOption = new Option( diff --git a/templates/cli/lib/type-generation/languages/csharp.js.twig b/templates/cli/lib/type-generation/languages/csharp.js.twig new file mode 100644 index 000000000..1df7c7cdb --- /dev/null +++ b/templates/cli/lib/type-generation/languages/csharp.js.twig @@ -0,0 +1,170 @@ +/** @typedef {import('../attribute').Attribute} Attribute */ +const { AttributeType } = require('../attribute'); +const { LanguageMeta } = require("./language"); + +class CSharp extends LanguageMeta { + getType(attribute, collections) { + let type = ""; + switch (attribute.type) { + case AttributeType.STRING: + case AttributeType.EMAIL: + case AttributeType.DATETIME: + type = "string"; + if (attribute.format === AttributeType.ENUM) { + type = LanguageMeta.toPascalCase(attribute.key); + } + break; + case AttributeType.INTEGER: + type = "long"; + break; + case AttributeType.FLOAT: + type = "double"; + break; + case AttributeType.BOOLEAN: + type = "bool"; + break; + case AttributeType.RELATIONSHIP: + const relatedCollection = collections.find(c => c.$id === attribute.relatedCollection); + if (!relatedCollection) { + throw new Error(`Related collection with ID '${attribute.relatedCollection}' not found.`); + } + type = LanguageMeta.toPascalCase(relatedCollection.name); + if ((attribute.relationType === 'oneToMany' && attribute.side === 'parent') || (attribute.relationType === 'manyToOne' && attribute.side === 'child') || attribute.relationType === 'manyToMany') { + type = `List<${type}>`; + } + break; + default: + throw new Error(`Unknown attribute type: ${attribute.type}`); + } + if (attribute.array) { + type = `List<${type}>`; + } + if (!attribute.required) { + type += "?"; + } + return type; + } + + getTemplate() { + return `/// This file is auto-generated by the Appwrite CLI. +/// You can regenerate it by running \`appwrite ${process.argv.slice(2).join(' ')}\`. + +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; + +namespace Appwrite.Models +{ +<% for (const attribute of collection.attributes) { -%> +<% if (attribute.format === 'enum') { -%> + +public enum <%- toPascalCase(attribute.key) %> { +<% for (const [index, element] of Object.entries(attribute.elements) ) { -%> + [JsonPropertyName("<%- element %>")] + <%- toPascalCase(element) %><% if (index < attribute.elements.length - 1) { %>,<% } %> +<% } -%> +} +<% } -%> +<% } %> +public class <%= toPascalCase(collection.name) %> +{ +<% for (const [index, attribute] of Object.entries(collection.attributes)) { -%> + [JsonPropertyName("<%- attribute.key %>")] + public <%- getType(attribute, collections) %> <%= toPascalCase(attribute.key) %> { get; private set; } + +<% } -%> + + public <%= toPascalCase(collection.name) %>( +<% for (const [index, attribute] of Object.entries(collection.attributes)) { -%> + <%- getType(attribute, collections) %> <%= toCamelCase(attribute.key) %><% if (index < collection.attributes.length - 1) { %>,<% } %> +<% } -%> + ) + { +<% for (const [index, attribute] of Object.entries(collection.attributes)) { -%> + <%= toPascalCase(attribute.key) %> = <%= toCamelCase(attribute.key) %>; +<% } -%> + } + + public static <%= toPascalCase(collection.name) %> From(Dictionary map) => new <%= toPascalCase(collection.name) %>( +<% for (const [index, attribute] of Object.entries(collection.attributes)) { -%> + <%- toCamelCase(attribute.key) %>: <% + // ENUM + if (attribute.format === 'enum') { + if (attribute.array) { + -%>((IEnumerable)map["<%- attribute.key %>"]).Select(e => Enum.Parse>(e.ToString()!, true)).ToList()<% + } else { + -%>Enum.Parse>(map["<%- attribute.key %>"].ToString()!, true)<% + } + // RELATIONSHIP + } else if (attribute.type === 'relationship') { + const relatedClass = toPascalCase(collections.find(c => c.$id === attribute.relatedCollection).name); + if ((attribute.relationType === 'oneToMany' && attribute.side === 'parent') || (attribute.relationType === 'manyToOne' && attribute.side === 'child') || attribute.relationType === 'manyToMany' || attribute.array) { + -%>((IEnumerable)map["<%- attribute.key %>"]).Select(it => Models.<%- relatedClass %>.From((Dictionary)it)).ToList()<% + } else { + -%>Models.<%- relatedClass %>.From((Dictionary)map["<%- attribute.key %>"])<% + } + // ARRAY TYPES + } else if (attribute.array) { + if (attribute.type === 'string' || attribute.type === 'datetime' || attribute.type === 'email') { + -%>((IEnumerable)map["<%- attribute.key %>"]).Select(x => x?.ToString())<%- attribute.required ? '.Where(x => x != null)' : '' %>.ToList()!<% + } else if (attribute.type === 'integer') { + -%>((IEnumerable)map["<%- attribute.key %>"]).Select(x => <%- !attribute.required ? 'x == null ? (long?)null : ' : '' %>Convert.ToInt64(x)).ToList()<% + } else if (attribute.type === 'double') { + -%>((IEnumerable)map["<%- attribute.key %>"]).Select(x => <%- !attribute.required ? 'x == null ? (double?)null : ' : '' %>Convert.ToDouble(x)).ToList()<% + } else if (attribute.type === 'boolean') { + -%>((IEnumerable)map["<%- attribute.key %>"]).Select(x => <%- !attribute.required ? 'x == null ? (bool?)null : ' : '' %>(bool)x).ToList()<% + } + // SINGLE VALUE TYPES + } else if (attribute.type === 'integer') { + -%><%- !attribute.required ? 'map["' + attribute.key + '"] == null ? null : ' : '' %>Convert.ToInt64(map["<%- attribute.key %>"])<% + } else if (attribute.type === 'double') { + -%><%- !attribute.required ? 'map["' + attribute.key + '"] == null ? null : ' : '' %>Convert.ToDouble(map["<%- attribute.key %>"])<% + } else if (attribute.type === 'boolean') { + -%>(<%- getType(attribute, collections) %>)map["<%- attribute.key %>"]<% + } else if (attribute.type === 'string' || attribute.type === 'datetime' || attribute.type === 'email') { + -%>map["<%- attribute.key %>"]<%- !attribute.required ? '?' : '' %>.ToString()<%- attribute.required ? '!' : ''%><% + } else { + -%>default<% + } + -%><% if (index < collection.attributes.length - 1) { %>,<% } %> +<% } -%> + ); + + public Dictionary ToMap() => new Dictionary() + { +<% for (const [index, attribute] of Object.entries(collection.attributes)) { -%> + { "<%- attribute.key %>", <% + // ENUM + if (attribute.format === 'enum') { + if (attribute.array) { + -%><%= toPascalCase(attribute.key) %>?.Select(e => e.ToString()).ToList()<% + } else { + -%><%= toPascalCase(attribute.key) %>?.ToString()<% + } + // RELATIONSHIP + } else if (attribute.type === 'relationship') { + if ((attribute.relationType === 'oneToMany' && attribute.side === 'parent') || (attribute.relationType === 'manyToOne' && attribute.side === 'child') || attribute.relationType === 'manyToMany' || attribute.array) { + -%><%= toPascalCase(attribute.key) %>?.Select(e => e.ToMap()).ToList()<% + } else { + -%><%= toPascalCase(attribute.key) %>?.ToMap()<% + } + // OTHER + } else { + -%><%= toPascalCase(attribute.key) %><% + } + -%> }<% if (index < collection.attributes.length - 1) { %>,<% } %> +<% } -%> + }; +} +} +`; + } + + getFileName(collection) { + return LanguageMeta.toPascalCase(collection.name) + ".cs"; + } +} + +module.exports = { CSharp };