Skip to content

Adding C# to CLI type generation #1135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/SDK/Language/CLI.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
5 changes: 4 additions & 1 deletion templates/cli/lib/commands/types.js.twig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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`);
}
Expand All @@ -55,7 +58,7 @@ const typesLanguageOption = new Option(
"-l, --language <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(
Expand Down
170 changes: 170 additions & 0 deletions templates/cli/lib/type-generation/languages/csharp.js.twig
Original file line number Diff line number Diff line change
@@ -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<string, object> 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<object>)map["<%- attribute.key %>"]).Select(e => Enum.Parse<Models.<%- toPascalCase(attribute.key) %>>(e.ToString()!, true)).ToList()<%
} else {
-%>Enum.Parse<Models.<%- toPascalCase(attribute.key) %>>(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<object>)map["<%- attribute.key %>"]).Select(it => Models.<%- relatedClass %>.From((Dictionary<string, object>)it)).ToList()<%
} else {
-%>Models.<%- relatedClass %>.From((Dictionary<string, object>)map["<%- attribute.key %>"])<%
}
// ARRAY TYPES
} else if (attribute.array) {
if (attribute.type === 'string' || attribute.type === 'datetime' || attribute.type === 'email') {
-%>((IEnumerable<object>)map["<%- attribute.key %>"]).Select(x => x?.ToString())<%- attribute.required ? '.Where(x => x != null)' : '' %>.ToList()!<%
} else if (attribute.type === 'integer') {
-%>((IEnumerable<object>)map["<%- attribute.key %>"]).Select(x => <%- !attribute.required ? 'x == null ? (long?)null : ' : '' %>Convert.ToInt64(x)).ToList()<%
} else if (attribute.type === 'double') {
-%>((IEnumerable<object>)map["<%- attribute.key %>"]).Select(x => <%- !attribute.required ? 'x == null ? (double?)null : ' : '' %>Convert.ToDouble(x)).ToList()<%
} else if (attribute.type === 'boolean') {
-%>((IEnumerable<object>)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<string, object?> ToMap() => new Dictionary<string, object?>()
{
<% 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 };