Skip to content

Commit dd24d9f

Browse files
Merge branch 'master' into feat-refactor-generator
2 parents 4804025 + c089b58 commit dd24d9f

30 files changed

+779
-472
lines changed

specs/swagger-appwrite-0.12.0.json

Lines changed: 0 additions & 1 deletion
This file was deleted.

specs/swagger-appwrite.0.12.0.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

specs/swagger2-latest-console.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

src/SDK/Language.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,11 @@ public function getParams()
7676
}
7777

7878
/**
79-
* @return array
79+
* Language specific filters.
80+
* @return array
8081
*/
81-
public function getTwigFilters(){
82+
public function getFilters(): array
83+
{
8284
return [];
8385
}
8486
}

src/SDK/Language/Web.php

Lines changed: 197 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
use Twig\TwigFilter;
66

7-
class Web extends JS {
7+
8+
class Web extends JS
9+
{
810

911
/**
1012
* @return string
@@ -22,8 +24,38 @@ public function getFiles()
2224
return [
2325
[
2426
'scope' => 'default',
25-
'destination' => 'src/sdk.ts',
26-
'template' => '/web/src/sdk.ts.twig',
27+
'destination' => 'src/index.ts',
28+
'template' => 'web/src/index.ts.twig',
29+
'minify' => false,
30+
],
31+
[
32+
'scope' => 'default',
33+
'destination' => 'src/client.ts',
34+
'template' => 'web/src/client.ts.twig',
35+
'minify' => false,
36+
],
37+
[
38+
'scope' => 'default',
39+
'destination' => 'src/service.ts',
40+
'template' => 'web/src/service.ts.twig',
41+
'minify' => false,
42+
],
43+
[
44+
'scope' => 'service',
45+
'destination' => 'src/services/{{service.name | caseDash}}.ts',
46+
'template' => 'web/src/services/template.ts.twig',
47+
'minify' => false,
48+
],
49+
[
50+
'scope' => 'default',
51+
'destination' => 'src/models.ts',
52+
'template' => 'web/src/models.ts.twig',
53+
'minify' => false,
54+
],
55+
[
56+
'scope' => 'default',
57+
'destination' => 'src/query.ts',
58+
'template' => 'web/src/query.ts.twig',
2759
'minify' => false,
2860
],
2961
[
@@ -100,7 +132,7 @@ public function getParamExample(array $param)
100132

101133
$output = '';
102134

103-
if(empty($example) && $example !== 0 && $example !== false) {
135+
if (empty($example) && $example !== 0 && $example !== false) {
104136
switch ($type) {
105137
case self::TYPE_NUMBER:
106138
case self::TYPE_INTEGER:
@@ -120,8 +152,7 @@ public function getParamExample(array $param)
120152
$output .= "document.getElementById('uploader').files[0]";
121153
break;
122154
}
123-
}
124-
else {
155+
} else {
125156
switch ($type) {
126157
case self::TYPE_NUMBER:
127158
case self::TYPE_INTEGER:
@@ -144,23 +175,169 @@ public function getParamExample(array $param)
144175
return $output;
145176
}
146177

147-
public function getTwigFilters()
178+
public function getTypeName($type, $method = []): string
148179
{
149-
return [
150-
new TwigFilter('comment2', function ($value) {
151-
$value = explode("\n", $value);
152-
foreach ($value as $key => $line) {
153-
$value[$key] = " * " . wordwrap($value[$key], 75, "\n * ");
180+
switch ($type) {
181+
case self::TYPE_INTEGER:
182+
case self::TYPE_NUMBER:
183+
return 'number';
184+
break;
185+
case self::TYPE_ARRAY:
186+
return 'string[]';
187+
case self::TYPE_FILE:
188+
return 'File';
189+
case self::TYPE_OBJECT:
190+
if (empty($method)) {
191+
return $type;
154192
}
155-
return implode("\n", $value);
156-
}, ['is_safe' => ['html']]),
157-
new TwigFilter('comment3', function ($value) {
158-
$value = explode("\n", $value);
159-
foreach ($value as $key => $line) {
160-
$value[$key] = " * " . wordwrap($value[$key], 75, "\n * ");
193+
194+
switch ($method['responseModel']) {
195+
case 'user':
196+
return "Partial<Preferences>";
197+
break;
198+
case 'document':
199+
if ($method['method'] === 'post') {
200+
return "Omit<Document, keyof Models.Document>";
201+
}
202+
if ($method['method'] === 'patch') {
203+
return "Partial<Omit<Document, keyof Models.Document>>";
204+
}
161205
}
162-
return implode("\n", $value);
163-
}, ['is_safe' => ['html']]),
206+
break;
207+
}
208+
209+
return $type;
210+
}
211+
212+
protected function populateGenerics(string $model, array $spec, array &$generics, bool $skipFirst = false)
213+
{
214+
if (!$skipFirst && $spec['definitions'][$model]['additionalProperties']) {
215+
$generics[] = $this->toUpperCase($model);
216+
}
217+
218+
$properties = $spec['definitions'][$model]['properties'];
219+
220+
foreach ($properties as $property) {
221+
if (array_key_exists('sub_schema', $property) && $property['sub_schema']) {
222+
$this->populateGenerics($property['sub_schema'], $spec, $generics, false);
223+
}
224+
}
225+
}
226+
227+
public function getGenerics(string $model, array $spec, bool $skipFirst = false): string
228+
{
229+
$generics = [];
230+
231+
if (array_key_exists($model, $spec['definitions'])) {
232+
$this->populateGenerics($model, $spec, $generics, $skipFirst);
233+
}
234+
235+
if (empty($generics)) return '';
236+
237+
$generics = array_unique($generics);
238+
$generics = array_map(fn ($type) => "{$type} extends Models.{$type}", $generics);
239+
240+
return '<' . implode(', ', $generics) . '>';
241+
}
242+
243+
public function getReturn(array $method, array $spec): string
244+
{
245+
if ($method['type'] === 'webAuth') {
246+
return 'void | URL';
247+
} elseif ($method['type'] === 'location') {
248+
return 'URL';
249+
}
250+
251+
if (array_key_exists('responseModel', $method) && !empty($method['responseModel']) && $method['responseModel'] !== 'any') {
252+
$ret = 'Promise<';
253+
254+
if (
255+
array_key_exists($method['responseModel'], $spec['definitions']) &&
256+
array_key_exists('additionalProperties', $spec['definitions'][$method['responseModel']]) &&
257+
!$spec['definitions'][$method['responseModel']]['additionalProperties']
258+
) {
259+
$ret .= 'Models.';
260+
}
261+
262+
$ret .= $this->toUpperCase($method['responseModel']);
263+
264+
$models = [];
265+
266+
if ($method['responseModel']) {
267+
$this->populateGenerics($method['responseModel'], $spec, $models);
268+
}
269+
270+
$models = array_unique($models);
271+
$models = array_filter($models, fn ($model) => $model != $this->toUpperCase($method['responseModel']));
272+
273+
if (!empty($models)) {
274+
$ret .= '<' . implode(', ', $models) . '>';
275+
}
276+
277+
$ret .= '>';
278+
279+
return $ret;
280+
} else {
281+
return 'Promise<{}>';
282+
}
283+
284+
return "";
285+
}
286+
287+
public function toUpperCase(string $value): string
288+
{
289+
return ucfirst((string)$this->helperCamelCase($value));
290+
}
291+
292+
protected function helperCamelCase($str)
293+
{
294+
$str = preg_replace('/[^a-z0-9' . implode("", []) . ']+/i', ' ', $str);
295+
$str = trim($str);
296+
$str = ucwords($str);
297+
$str = str_replace(" ", "", $str);
298+
$str = lcfirst($str);
299+
300+
return $str;
301+
}
302+
303+
public function getSubSchema(array $property, array $spec): string
304+
{
305+
if (array_key_exists('sub_schema', $property)) {
306+
$ret = '';
307+
$generics = [];
308+
$this->populateGenerics($property['sub_schema'], $spec, $generics);
309+
310+
$generics = array_filter($generics, fn ($model) => $model != $this->toUpperCase($property['sub_schema']));
311+
312+
$ret .= $this->toUpperCase($property['sub_schema']);
313+
if (!empty($generics)) {
314+
$ret .= '<' . implode(', ', $generics) . '>';
315+
}
316+
if ($property['type'] === 'array') {
317+
$ret .= '[]';
318+
}
319+
320+
return $ret;
321+
}
322+
323+
return $this->getTypeName($property['type']);
324+
}
325+
326+
public function getFilters(): array
327+
{
328+
return [
329+
new TwigFilter('getPropertyType', function ($value, $method = []) {
330+
return $this->getTypeName($value, $method);
331+
}),
332+
new TwigFilter('getSubSchema', function (array $property, array $spec) {
333+
return $this->getSubSchema($property, $spec);
334+
}),
335+
new TwigFilter('getGenerics', function (string $model, array $spec, bool $skipAdditional = false) {
336+
return $this->getGenerics($model, $spec, $skipAdditional);
337+
}),
338+
new TwigFilter('getReturn', function (array $method, array $spec) {
339+
return $this->getReturn($method, $spec);
340+
}),
164341
];
165342
}
166343
}

src/SDK/SDK.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,14 @@ public function __construct(Language $language, Spec $spec)
7575
$this->twig = new Environment(new FilesystemLoader(__DIR__ . '/../../templates'), [
7676
'debug' => true
7777
] );
78+
79+
/**
80+
* Add language specific filters
81+
*/
82+
foreach ($this->language->getFilters() as $filter) {
83+
$this->twig->addFilter($filter);
84+
}
85+
7886
$this->twig->addExtension(new \Twig\Extension\DebugExtension());
7987

8088
$this->twig->addFilter(new TwigFilter('caseLower', function ($value) {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{% import 'flutter/base/utils.twig' as utils %}
2+
final Map<String, dynamic> params = {
3+
{{ utils.map_parameter(method.parameters.query) }}
4+
{{ utils.map_parameter(method.parameters.body) }}
5+
};
6+
7+
final Map<String, String> headers = {
8+
{{ utils.map_headers(method.headers) }}
9+
};
10+
11+
final res = await client.call(HttpMethod.{{ method.method | caseLower }}, path: path, params: params, headers: headers);
12+
13+
return {% if method.responseModel and method.responseModel != 'any' %}models.{{method.responseModel | caseUcfirst}}.fromMap(res.data){% else %} res.data{% endif %};
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{% import 'flutter/base/utils.twig' as utils %}
2+
final Map<String, dynamic> params = {
3+
{{ utils.map_parameter(method.parameters.query) }}
4+
{{ utils.map_parameter(method.parameters.body) }}
5+
};
6+
7+
final Map<String, String> headers = {
8+
{{ utils.map_headers(method.headers) }}
9+
};
10+
11+
{% if 'multipart/form-data' in method.consumes %}
12+
dynamic res;
13+
if(identical(0, 0.0)) {
14+
{% for parameter in method.parameters.all %}
15+
{% if parameter.type == 'file' %}
16+
params['{{ parameter.name }}'] = {{ parameter.name }}.file;
17+
{% endif %}
18+
{% endfor %}
19+
res = await client.call(HttpMethod.{{ method.method | caseLower }}, path: path, params: params, headers: headers);
20+
} else {
21+
String idParamName = '';
22+
{% for parameter in method.parameters.all %}
23+
{% if parameter.type == 'file' %}
24+
final paramName = '{{ parameter.name }}';
25+
{% endif %}
26+
{% if parameter.isUploadID %}
27+
idParamName = '{{ parameter.name }}';
28+
{% endif %}
29+
{% endfor %}
30+
res = await chunkedUpload(
31+
client: client,
32+
path: path,
33+
params: params,
34+
paramName: paramName,
35+
idParamName: idParamName,
36+
headers: headers,
37+
onProgress: onProgress,
38+
);
39+
}
40+
41+
return {% if method.responseModel and method.responseModel != 'any' %}models.{{method.responseModel | caseUcfirst}}.fromMap(res.data){% else %} res.data{% endif %};
42+
{% endif %}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{% import 'flutter/base/utils.twig' as utils %}
2+
final Map<String, dynamic> params = {
3+
{{ utils.map_parameter(method.parameters.query) }}
4+
{{ utils.map_parameter(method.parameters.body) }}
5+
{% if method.security|length > 0 %}{% for node in method.security %}
6+
{% for key,header in node|keys %}
7+
'{{header|caseLower}}': client.config['{{header|caseLower}}'],
8+
{% endfor %}
9+
{% endfor %}
10+
{% endif %}
11+
};
12+
13+
final res = await client.call(HttpMethod.{{ method.method | caseLower }}, path: path, params: params, responseType: ResponseType.bytes);
14+
return res.data;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{% import 'flutter/base/utils.twig' as utils %}
2+
final Map<String, dynamic> params = {
3+
{{ utils.map_parameter(method.parameters.query) }}
4+
{{ utils.map_parameter(method.parameters.body) }}
5+
{% if method.security|length > 0 %}
6+
{% for node in method.security %}
7+
{% for key,header in node|keys %}
8+
'{{header|caseLower}}': client.config['{{header|caseLower}}'],
9+
{% endfor %}
10+
{% endfor %}
11+
{% endif %}
12+
};
13+
14+
final List query = [];
15+
16+
params.forEach((key, value) {
17+
if (value is List) {
18+
for (var item in value) {
19+
query.add(Uri.encodeComponent(key + '[]') + '=' + Uri.encodeComponent(item));
20+
}
21+
} else if(value != null) {
22+
query.add(Uri.encodeComponent(key) + '=' + Uri.encodeComponent(value));
23+
}
24+
});
25+
26+
Uri endpoint = Uri.parse(client.endPoint);
27+
Uri url = Uri(scheme: endpoint.scheme,
28+
host: endpoint.host,
29+
port: endpoint.port,
30+
path: endpoint.path + path,
31+
query: query.join('&')
32+
);
33+
34+
return client.webAuth(url);

0 commit comments

Comments
 (0)