Skip to content

Commit 07a7d62

Browse files
authored
Merge pull request #1198 from appwrite/feat-enum-support
2 parents 14617d4 + be203d7 commit 07a7d62

File tree

31 files changed

+346
-69
lines changed

31 files changed

+346
-69
lines changed

.github/workflows/tests.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ jobs:
2020
CLINode18,
2121
DartBeta,
2222
DartStable,
23-
Deno1193,
24-
Deno1303,
2523
DotNet60,
2624
DotNet80,
2725
DotNet90,

example.php

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
use Appwrite\SDK\Language\Ruby;
1414
use Appwrite\SDK\Language\Dart;
1515
use Appwrite\SDK\Language\Go;
16-
use Appwrite\SDK\Language\Deno;
1716
use Appwrite\SDK\Language\REST;
1817
use Appwrite\SDK\Language\Swift;
1918
use Appwrite\SDK\Language\Apple;
@@ -101,30 +100,6 @@ function getSSLPage($url) {
101100

102101
$sdk->generate(__DIR__ . '/examples/web');
103102

104-
// Deno
105-
$sdk = new SDK(new Deno(), new Swagger2($spec));
106-
107-
$sdk
108-
->setName('NAME')
109-
->setDescription('Repo description goes here')
110-
->setShortDescription('Repo short description goes here')
111-
->setVersion('0.0.0')
112-
->setURL('https://example.com')
113-
->setLogo('https://appwrite.io/v1/images/console.png')
114-
->setLicenseContent('test test test')
115-
->setWarning('**WORK IN PROGRESS - NOT READY FOR USAGE**')
116-
->setChangelog('**CHANGELOG**')
117-
->setGitUserName('repoowner')
118-
->setGitRepoName('reponame')
119-
->setTwitter('appwrite_io')
120-
->setDiscord('564160730845151244', 'https://appwrite.io/discord')
121-
->setDefaultHeaders([
122-
'X-Appwrite-Response-Format' => '1.6.0',
123-
])
124-
;
125-
126-
$sdk->generate(__DIR__ . '/examples/deno');
127-
128103
// Node
129104
$sdk = new SDK(new Node(), new Swagger2($spec));
130105

src/SDK/Language/DotNet.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Appwrite\SDK\Language;
66
use Twig\TwigFilter;
7+
use Twig\TwigFunction;
78

89
class DotNet extends Language
910
{
@@ -465,6 +466,47 @@ public function getFilters(): array
465466
];
466467
}
467468

469+
/**
470+
* get sub_scheme and property_name functions
471+
* @return TwigFunction[]
472+
*/
473+
public function getFunctions(): array
474+
{
475+
return [
476+
new TwigFunction('sub_schema', function (array $property) {
477+
$result = '';
478+
479+
if (isset($property['sub_schema']) && !empty($property['sub_schema'])) {
480+
if ($property['type'] === 'array') {
481+
$result = 'List<' . \ucfirst($property['sub_schema']) . '>';
482+
} else {
483+
$result = \ucfirst($property['sub_schema']);
484+
}
485+
} elseif (isset($property['enum']) && !empty($property['enum'])) {
486+
$enumName = $property['enumName'] ?? $property['name'];
487+
$result = \ucfirst($enumName);
488+
} else {
489+
$result = $this->getTypeName($property);
490+
}
491+
492+
if (!($property['required'] ?? true)) {
493+
$result .= '?';
494+
}
495+
496+
return $result;
497+
}),
498+
new TwigFunction('property_name', function (array $definition, array $property) {
499+
$name = $property['name'];
500+
$name = \ucfirst($name);
501+
$name = \str_replace('$', '', $name);
502+
if (\in_array(\strtolower($name), $this->getKeywords())) {
503+
$name = '@' . $name;
504+
}
505+
return $name;
506+
}),
507+
];
508+
}
509+
468510
/**
469511
* Format a PHP array as a C# anonymous object
470512
*/

src/SDK/Language/Kotlin.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,9 @@ public function getFilters(): array
469469
}
470470
return $this->toUpperSnakeCase($value);
471471
}),
472+
new TwigFilter('propertyAssignment', function (array $property, array $spec) {
473+
return $this->getPropertyAssignment($property, $spec);
474+
}),
472475
];
473476
}
474477

@@ -518,6 +521,9 @@ protected function getPropertyType(array $property, array $spec, string $generic
518521
if ($property['type'] === 'array') {
519522
$type = 'List<' . $type . '>';
520523
}
524+
} elseif (isset($property['enum'])) {
525+
$enumName = $property['enumName'] ?? $property['name'];
526+
$type = \ucfirst($enumName);
521527
} else {
522528
$type = $this->getTypeName($property);
523529
}
@@ -551,4 +557,71 @@ protected function hasGenericType(?string $model, array $spec): string
551557

552558
return false;
553559
}
560+
561+
/**
562+
* Generate property assignment logic for model deserialization
563+
*
564+
* @param array $property
565+
* @param array $spec
566+
* @return string
567+
*/
568+
protected function getPropertyAssignment(array $property, array $spec): string
569+
{
570+
$propertyName = $property['name'];
571+
$escapedPropertyName = str_replace('$', '\$', $propertyName);
572+
$mapKey = "map[\"$escapedPropertyName\"]";
573+
574+
// Handle sub-schema (nested objects)
575+
if (isset($property['sub_schema']) && !empty($property['sub_schema'])) {
576+
$subSchemaClass = $this->toPascalCase($property['sub_schema']);
577+
$hasGenericType = $this->hasGenericType($property['sub_schema'], $spec);
578+
$nestedTypeParam = $hasGenericType ? ', nestedType' : '';
579+
580+
if ($property['type'] === 'array') {
581+
return "($mapKey as List<Map<String, Any>>).map { " .
582+
"$subSchemaClass.from(map = it$nestedTypeParam) }";
583+
} else {
584+
return "$subSchemaClass.from(" .
585+
"map = $mapKey as Map<String, Any>$nestedTypeParam" .
586+
")";
587+
}
588+
}
589+
590+
// Handle enum properties
591+
if (isset($property['enum']) && !empty($property['enum'])) {
592+
$enumName = $property['enumName'] ?? $property['name'];
593+
$enumClass = $this->toPascalCase($enumName);
594+
$nullCheck = $property['required'] ? '!!' : ' ?: null';
595+
596+
if ($property['required']) {
597+
return "$enumClass.values().find { " .
598+
"it.value == $mapKey as String " .
599+
"}$nullCheck";
600+
}
601+
602+
return "$enumClass.values().find { " .
603+
"it.value == ($mapKey as? String) " .
604+
"}$nullCheck";
605+
}
606+
607+
// Handle primitive types
608+
$nullableModifier = $property['required'] ? '' : '?';
609+
610+
if ($property['type'] === 'integer') {
611+
return "($mapKey as$nullableModifier Number)" .
612+
($nullableModifier ? '?' : '') . '.toLong()';
613+
}
614+
615+
if ($property['type'] === 'number') {
616+
return "($mapKey as$nullableModifier Number)" .
617+
($nullableModifier ? '?' : '') . '.toDouble()';
618+
}
619+
620+
// Handle other types (string, boolean, etc.)
621+
$kotlinType = $this->getPropertyType($property, $spec);
622+
// Remove nullable modifier from type since we handle it in the cast
623+
$kotlinType = str_replace('?', '', $kotlinType);
624+
625+
return "$mapKey as$nullableModifier $kotlinType";
626+
}
554627
}

src/SDK/Language/Swift.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ protected function getPropertyType(array $property, array $spec, string $generic
577577
$type = '[' . $type . ']';
578578
}
579579
} else {
580-
$type = $this->getTypeName($property, isProperty: true);
580+
$type = $this->getTypeName($property, $spec, true);
581581
}
582582

583583
return $type;

src/SDK/Language/Web.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ public function getReturn(array $method, array $spec): string
316316
return 'Promise<{}>';
317317
}
318318

319-
public function getSubSchema(array $property, array $spec): string
319+
public function getSubSchema(array $property, array $spec, string $methodName = ''): string
320320
{
321321
if (array_key_exists('sub_schema', $property)) {
322322
$ret = '';
@@ -336,6 +336,14 @@ public function getSubSchema(array $property, array $spec): string
336336
return $ret;
337337
}
338338

339+
if (array_key_exists('enum', $property) && !empty($methodName)) {
340+
if (isset($property['enumName'])) {
341+
return $this->toPascalCase($property['enumName']);
342+
}
343+
344+
return $this->toPascalCase($methodName) . $this->toPascalCase($property['name']);
345+
}
346+
339347
return $this->getTypeName($property);
340348
}
341349

@@ -348,8 +356,8 @@ public function getFilters(): array
348356
new TwigFilter('getReadOnlyProperties', function ($value, $responseModel, $spec = []) {
349357
return $this->getReadOnlyProperties($value, $responseModel, $spec);
350358
}),
351-
new TwigFilter('getSubSchema', function (array $property, array $spec) {
352-
return $this->getSubSchema($property, $spec);
359+
new TwigFilter('getSubSchema', function (array $property, array $spec, string $methodName = '') {
360+
return $this->getSubSchema($property, $spec, $methodName);
353361
}),
354362
new TwigFilter('getGenerics', function (string $model, array $spec, bool $skipAdditional = false) {
355363
return $this->getGenerics($model, $spec, $skipAdditional);

src/SDK/SDK.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,9 @@ public function generate(string $target): void
633633
'contactURL' => $this->spec->getContactURL(),
634634
'contactEmail' => $this->spec->getContactEmail(),
635635
'services' => $this->getFilteredServices(),
636-
'enums' => $this->spec->getEnums(),
636+
'requestEnums' => $this->spec->getRequestEnums(),
637+
'responseEnums' => $this->spec->getResponseEnums(),
638+
'allEnums' => $this->spec->getAllEnums(),
637639
'definitions' => $this->spec->getDefinitions(),
638640
'global' => [
639641
'headers' => $this->spec->getGlobalHeaders(),
@@ -724,7 +726,7 @@ public function generate(string $target): void
724726
}
725727
break;
726728
case 'enum':
727-
foreach ($this->spec->getEnums() as $key => $enum) {
729+
foreach ($this->spec->getAllEnums() as $key => $enum) {
728730
$params['enum'] = $enum;
729731

730732
$this->render($template, $destination, $block, $params, $minify);

src/Spec/Spec.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,16 @@ public function setAttribute($key, $value, $type = self::SET_TYPE_ASSIGN)
178178
}
179179

180180
/**
181-
* Get Enums
181+
* Get Request Enums
182182
*
183183
* @return array
184184
*/
185-
abstract public function getEnums();
185+
abstract public function getRequestEnums();
186+
187+
/**
188+
* Get Response Enums
189+
*
190+
* @return array
191+
*/
192+
abstract public function getResponseEnums();
186193
}

src/Spec/Swagger2.php

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,13 @@ public function getDefinitions()
489489
//nested model
490490
$sch['properties'][$name]['sub_schemas'] = \array_map(fn($schema) => str_replace('#/definitions/', '', $schema['$ref']), $def['items']['x-oneOf']);
491491
}
492+
493+
if (isset($def['enum'])) {
494+
// enum property
495+
$sch['properties'][$name]['enum'] = $def['enum'];
496+
$sch['properties'][$name]['enumName'] = $def['x-enum-name'] ?? ucfirst($key) . ucfirst($name);
497+
$sch['properties'][$name]['enumKeys'] = $def['x-enum-keys'] ?? [];
498+
}
492499
}
493500
}
494501
$list[$key] = $sch;
@@ -499,7 +506,7 @@ public function getDefinitions()
499506
/**
500507
* @return array
501508
*/
502-
public function getEnums(): array
509+
public function getRequestEnums(): array
503510
{
504511
$list = [];
505512

@@ -523,4 +530,62 @@ public function getEnums(): array
523530

524531
return \array_values($list);
525532
}
533+
534+
/**
535+
* @return array
536+
*/
537+
public function getResponseEnums(): array
538+
{
539+
$list = [];
540+
$definitions = $this->getDefinitions();
541+
542+
foreach ($definitions as $modelName => $model) {
543+
if (isset($model['properties']) && is_array($model['properties'])) {
544+
foreach ($model['properties'] as $propertyName => $property) {
545+
if (isset($property['enum'])) {
546+
$enumName = $property['x-enum-name'] ?? ucfirst($modelName) . ucfirst($propertyName);
547+
548+
if (!isset($list[$enumName])) {
549+
$list[$enumName] = [
550+
'name' => $enumName,
551+
'enum' => $property['enum'],
552+
'keys' => $property['x-enum-keys'] ?? [],
553+
];
554+
}
555+
}
556+
557+
// array of enums
558+
if ((($property['type'] ?? null) === 'array') && isset($property['items']['enum'])) {
559+
$enumName = $property['x-enum-name'] ?? ucfirst($modelName) . ucfirst($propertyName);
560+
561+
if (!isset($list[$enumName])) {
562+
$list[$enumName] = [
563+
'name' => $enumName,
564+
'enum' => $property['items']['enum'],
565+
'keys' => $property['items']['x-enum-keys'] ?? [],
566+
];
567+
}
568+
}
569+
}
570+
}
571+
}
572+
573+
return \array_values($list);
574+
}
575+
576+
/**
577+
* @return array
578+
*/
579+
public function getAllEnums(): array
580+
{
581+
$list = [];
582+
foreach ($this->getRequestEnums() as $enum) {
583+
$list[$enum['name']] = $enum;
584+
}
585+
foreach ($this->getResponseEnums() as $enum) {
586+
$list[$enum['name']] = $enum;
587+
}
588+
589+
return \array_values($list);
590+
}
526591
}

templates/android/library/src/main/java/io/package/models/Model.kt.twig

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ package {{ sdk.namespace | caseDot }}.models
22

33
import com.google.gson.annotations.SerializedName
44
import io.appwrite.extensions.jsonCast
5+
{%~ for property in definition.properties %}
6+
{%~ if property.enum %}
7+
import {{ sdk.namespace | caseDot }}.enums.{{ property.enumName | caseUcfirst }}
8+
{%~ endif %}
9+
{%~ endfor %}
510

611
/**
712
* {{ definition.description | replace({"\n": "\n * "}) | raw }}
@@ -27,7 +32,7 @@ import io.appwrite.extensions.jsonCast
2732
) {
2833
fun toMap(): Map<String, Any> = mapOf(
2934
{%~ for property in definition.properties %}
30-
"{{ property.name | escapeDollarSign }}" to {% if property.sub_schema %}{% if property.type == 'array' %}{{property.name | escapeKeyword | removeDollarSign}}.map { it.toMap() }{% else %}{{property.name | escapeKeyword | removeDollarSign}}.toMap(){% endif %}{% else %}{{property.name | escapeKeyword | removeDollarSign}}{% endif %} as Any,
35+
"{{ property.name | escapeDollarSign }}" to {% if property.sub_schema %}{% if property.type == 'array' %}{{property.name | escapeKeyword | removeDollarSign}}.map { it.toMap() }{% else %}{{property.name | escapeKeyword | removeDollarSign}}.toMap(){% endif %}{% elseif property.enum %}{{property.name | escapeKeyword | removeDollarSign}}{% if not property.required %}?{% endif %}.value{% else %}{{property.name | escapeKeyword | removeDollarSign}}{% endif %} as Any,
3136
{%~ endfor %}
3237
{%~ if definition.additionalProperties %}
3338
"data" to data!!.jsonCast(to = Map::class.java)
@@ -61,7 +66,7 @@ import io.appwrite.extensions.jsonCast
6166
{%~ endif %}
6267
) = {{ definition | modelType(spec) | raw }}(
6368
{%~ for property in definition.properties %}
64-
{{ property.name | escapeKeyword | removeDollarSign }} = {% if property.sub_schema %}{% if property.type == 'array' %}(map["{{ property.name | escapeDollarSign }}"] as List<Map<String, Any>>).map { {{ property.sub_schema | caseUcfirst }}.from(map = it{% if property.sub_schema | hasGenericType(spec) %}, nestedType{% endif %}) }{% else %}{{ property.sub_schema | caseUcfirst }}.from(map = map["{{property.name | escapeDollarSign }}"] as Map<String, Any>{% if property.sub_schema | hasGenericType(spec) %}, nestedType{% endif %}){% endif %}{% else %}{% if property.type == "integer" or property.type == "number" %}({% endif %}map["{{ property.name | escapeDollarSign }}"]{% if property.type == "integer" or property.type == "number" %} as{% if not property.required %}?{% endif %} Number){% endif %}{% if property.type == "integer" %}{% if not property.required %}?{% endif %}.toLong(){% elseif property.type == "number" %}{% if not property.required %}?{% endif %}.toDouble(){% else %} as{% if not property.required %}?{% endif %} {{ property | propertyType(spec) | raw }}{% endif %}{% endif %},
69+
{{ property.name | escapeKeyword | removeDollarSign }} = {{ property | propertyAssignment(spec) | raw }},
6570
{%~ endfor %}
6671
{%~ if definition.additionalProperties %}
6772
data = map["data"]?.jsonCast(to = nestedType) ?: map.jsonCast(to = nestedType)

0 commit comments

Comments
 (0)