diff --git a/src/SDK/Language/Flutter.php b/src/SDK/Language/Flutter.php index 4e0a877cd..cbdc4ad9d 100644 --- a/src/SDK/Language/Flutter.php +++ b/src/SDK/Language/Flutter.php @@ -235,12 +235,24 @@ public function getFiles() 'template' => 'flutter/lib/src/exception.dart.twig', 'minify' => false, ], + [ + 'scope' => 'default', + 'destination' => '/lib/models.dart', + 'template' => 'flutter/lib/models.dart.twig', + 'minify' => false, + ], [ 'scope' => 'service', 'destination' => '/lib/services/{{service.name | caseDash}}.dart', 'template' => 'flutter/lib/services/service.dart.twig', 'minify' => false, ], + [ + 'scope' => 'definition', + 'destination' => '/lib/src/models/{{definition.name | caseSnake }}.dart', + 'template' => '/flutter/lib/src/models/model.dart.twig', + 'minify' => false, + ], [ 'scope' => 'method', 'destination' => 'docs/examples/{{service.name | caseLower}}/{{method.name | caseDash}}.md', diff --git a/src/SDK/SDK.php b/src/SDK/SDK.php index 6a4bb111c..dea9b17ed 100644 --- a/src/SDK/SDK.php +++ b/src/SDK/SDK.php @@ -193,6 +193,9 @@ public function __construct(Language $language, Spec $spec) $this->twig->addFilter(new TwigFilter('caseHTML', function ($value) { return $value; }, ['is_safe' => ['html']])); + $this->twig->addFilter(new TwigFilter('removeDollarSign', function ($value) { + return str_replace('$','',$value); + })); } /** @@ -524,6 +527,7 @@ public function generate($target) 'contactURL' => $this->spec->getContactURL(), 'contactEmail' => $this->spec->getContactEmail(), 'services' => $this->spec->getServices(), + 'definitions' => $this->spec->getDefinitions(), 'global' => [ 'headers' => $this->spec->getGlobalHeaders(), 'defaultHeaders' => $this->defaultHeaders, @@ -570,6 +574,14 @@ public function generate($target) $this->render($template, $destination, $block, $params, $minify); } break; + case 'definition': + foreach ($this->spec->getDefinitions() as $key => $definition) { + + $params['definition'] = $definition; + + $this->render($template, $destination, $block, $params, $minify); + } + break; case 'method': foreach ($this->spec->getServices() as $key => $service) { $methods = $this->spec->getMethods($key); diff --git a/src/Spec/Spec.php b/src/Spec/Spec.php index fcfc13f36..137b789d7 100644 --- a/src/Spec/Spec.php +++ b/src/Spec/Spec.php @@ -109,6 +109,11 @@ abstract public function getMethods($service); */ abstract public function getGlobalHeaders(); + /** + * @return array + */ + abstract public function getDefinitions(); + /** * Get Attribute * diff --git a/src/Spec/Swagger2.php b/src/Spec/Swagger2.php index bac7b1cd8..9e7910dd8 100644 --- a/src/Spec/Swagger2.php +++ b/src/Spec/Swagger2.php @@ -146,6 +146,17 @@ public function getMethods($service) } } + $responses = $method['responses']; + $responseModel = ''; + foreach($responses as $code => $desc) { + if(isset($desc['schema']) && isset($desc['schema']['$ref'])) { + $responseModel = $desc['schema']['$ref']; + if(!empty($responseModel)) { + $responseModel = str_replace('#/definitions/', '', $responseModel); + } + } + } + $output = [ 'method' => $methodName, 'path' => $pathName, @@ -165,7 +176,8 @@ public function getMethods($service) 'path' => [], 'query' => [], 'body' => [], - ] + ], + 'responseModel' => $responseModel, ]; if(isset($method['consumes']) && is_array($method['consumes'])) { @@ -274,4 +286,30 @@ public function getGlobalHeaders() return $list; } + + public function getDefinitions() { + $list = []; + $definition = $this->getAttribute('definitions',[]); + foreach ($definition as $key => $schema) { + if($key == 'any') continue; + $sch = [ + "name" => $key, + "properties"=> $schema['properties'] ?? [], + "required" => $schema['required'] ?? [], + "additionalProperties" => $schema['additionalProperties'] ?? [] + ]; + if(isset($sch['properties'])) { + foreach($sch['properties'] as $name => $def) { + $sch['properties'][$name]['name'] = $name; + $sch['properties'][$name]['required'] = in_array($name,$sch['required']); + if(isset($def['items']['$ref'])) { + //nested model + $sch['properties'][$name]['sub_schema'] = str_replace('#/definitions/', '', $def['items']['$ref']); + } + } + } + $list[$key] = $sch; + } + return $list; + } } diff --git a/templates/flutter/lib/models.dart.twig b/templates/flutter/lib/models.dart.twig new file mode 100644 index 000000000..b617be3e0 --- /dev/null +++ b/templates/flutter/lib/models.dart.twig @@ -0,0 +1,5 @@ +library {{ language.params.packageName }}.models; + +{% for definition in spec.definitions %} +part 'src/models/{{definition.name | caseSnake}}.dart'; +{% endfor %} \ No newline at end of file diff --git a/templates/flutter/lib/package.dart.twig b/templates/flutter/lib/package.dart.twig index bfcdde521..78d2cad12 100644 --- a/templates/flutter/lib/package.dart.twig +++ b/templates/flutter/lib/package.dart.twig @@ -1,6 +1,7 @@ library {{ language.params.packageName }}; import 'dart:async'; +import 'dart:typed_data'; import 'package:http/http.dart' as http; import 'package:flutter/foundation.dart'; import 'src/redirect_stub.dart' @@ -9,6 +10,7 @@ import 'src/enums.dart'; import 'src/client.dart'; import 'src/response.dart'; import 'src/service.dart'; +import 'models.dart' as models; export 'src/response.dart'; export 'src/client.dart'; diff --git a/templates/flutter/lib/services/service.dart.twig b/templates/flutter/lib/services/service.dart.twig index f4cba41b7..889ff289e 100644 --- a/templates/flutter/lib/services/service.dart.twig +++ b/templates/flutter/lib/services/service.dart.twig @@ -6,7 +6,6 @@ part of {{ language.params.packageName }}; {% 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) %}'{{ parameter.name }}': {{ parameter.name | caseCamel | escapeKeyword }},{% endmacro %} - class {{ service.name | caseUcfirst }} extends Service { {{ service.name | caseUcfirst }}(Client client): super(client); {% for method in service.methods %} @@ -17,7 +16,7 @@ class {{ service.name | caseUcfirst }} extends Service { {{ method.description|dartComment }} /// {% endif %} - {% if method.type == 'webAuth' %}Future{% else %}Future{% endif %} {{ method.name | caseCamel }}({{ _self.method_parameters(method.parameters) }}) { + {% if method.type == 'webAuth' %}Future{% elseif method.type == 'location' %} Future {% else %} {% if method.responseModel and method.responseModel != 'any' %}Future{% else %}Future{% endif %}{% endif %} {{ method.name | caseCamel }}({{ _self.method_parameters(method.parameters) }}) async { final String path = '{{ method.path }}'{% for parameter in method.parameters.path %}.replaceAll(RegExp('{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}'), {{ parameter.name | caseCamel | escapeKeyword }}){% endfor %}; final Map params = { @@ -72,7 +71,8 @@ class {{ service.name | caseUcfirst }} extends Service { params[key] = params[key].toString(); }}); - return client.call(HttpMethod.{{ method.method | caseLower }}, path: path, params: params, responseType: ResponseType.bytes); + final res = await client.call(HttpMethod.{{ method.method | caseLower }}, path: path, params: params, responseType: ResponseType.bytes); + return res.data; {% else %} final Map headers = { {% for key, header in method.headers %} @@ -80,7 +80,8 @@ class {{ service.name | caseUcfirst }} extends Service { {% endfor %} }; - return client.call(HttpMethod.{{ method.method | caseLower }}, path: path, params: params, headers: headers); + final res = await client.call(HttpMethod.{{ method.method | caseLower }}, path: path, params: params, headers: headers); + return {% if method.responseModel and method.responseModel != 'any' %}models.{{method.responseModel | caseUcfirst}}.fromMap(res.data){% else %} res.data{% endif %}; {% endif %} } {% endfor %} diff --git a/templates/flutter/lib/src/models/model.dart.twig b/templates/flutter/lib/src/models/model.dart.twig new file mode 100644 index 000000000..fc59ef133 --- /dev/null +++ b/templates/flutter/lib/src/models/model.dart.twig @@ -0,0 +1,57 @@ +{% macro sub_schema(property) %}{% if property.sub_schema %}{% if property.type == 'array' %}List<{{property.sub_schema | caseUcfirst}}>{% else %}{{property.sub_schema | caseUcfirst}}{% endif %}{% else %}{{property.type | typeName}}{% endif %}{% endmacro %} +part of {{ language.params.packageName }}.models; + +class {{ definition.name | caseUcfirst }} { +{% for property in definition.properties %} + final {% if not property.required %}{{_self.sub_schema(property)}}? {{ property.name | escapeKeyword }}{% else %}{{_self.sub_schema(property)}} {{ property.name | escapeKeyword }}{% endif %}; +{% endfor %} +{% if definition.additionalProperties %} + final Map data; +{% endif %} + + {{ definition.name | caseUcfirst}}({ +{% for property in definition.properties %}{% if property.required %} + required {% endif %}this.{{ property.name | escapeKeyword }}, +{% endfor %} +{% if definition.additionalProperties %} + required this.data, +{% endif %} + }); + + factory {{ definition.name | caseUcfirst}}.fromMap(Map map) { + return {{ definition.name | caseUcfirst }}( +{% for property in definition.properties %} + {{ property.name | escapeKeyword }}: {% if property.sub_schema %}{% if property.type == 'array' %}List<{{property.sub_schema | caseUcfirst}}>.from(map['{{property.name | escapeDollarSign }}'].map((p) => {{property.sub_schema | caseUcfirst}}.fromMap(p))){% else %}{{property.sub_schema | caseUcfirst}}.fromMap(map['{{property.name | escapeDollarSign }}']){% endif %}{% else %}map['{{property.name | escapeDollarSign }}']{% if property.type == "number" %}.toDouble(){% endif %}{% endif %}, +{% endfor %} +{% if definition.additionalProperties %} + data: map, +{% endif %} + ); + } + + Map toMap() { + return { +{% for property in definition.properties %} + "{{ property.name | escapeDollarSign }}": {% if property.sub_schema %}{% if property.type == 'array' %}{{property.name | escapeKeyword}}.map((p) => p.toMap()){% else %}{{property.name | escapeKeyword}}.toMap(){% endif %}{% else %}{{property.name | escapeKeyword }}{% endif %}, +{% endfor %} +{% if definition.additionalProperties %} + "data": data, +{% endif %} + }; + } +{% if definition.additionalProperties %} + + T convertTo(T Function(Map) fromJson) => fromJson(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' %} + + List convertTo(T Function(Map) fromJson) => + {{property.name}}.map((d) => d.convertTo(fromJson)).toList(); +{% endif %} +{% endfor %} +{% endif %} +{% endfor %} +} diff --git a/templates/flutter/pubspec.yaml.twig b/templates/flutter/pubspec.yaml.twig index 9d2dd2e27..da6f27d8f 100644 --- a/templates/flutter/pubspec.yaml.twig +++ b/templates/flutter/pubspec.yaml.twig @@ -14,8 +14,8 @@ dependencies: device_info_plus: ^2.1.0 flutter_web_auth: ^0.3.1 http: ^0.13.3 - package_info_plus: ^1.0.4 - path_provider: ^2.0.2 + package_info_plus: ^1.0.6 + path_provider: ^2.0.3 web_socket_channel: ^2.1.0 dev_dependencies: diff --git a/tests/languages/flutter/tests.dart b/tests/languages/flutter/tests.dart index 1b0c35022..75cd1760d 100644 --- a/tests/languages/flutter/tests.dart +++ b/tests/languages/flutter/tests.dart @@ -1,4 +1,5 @@ import '../lib/packageName.dart'; +import '../lib/models.dart'; void main() async { Client client = Client(); @@ -8,64 +9,65 @@ void main() async { client.setSelfSigned(); client.setProject('console'); - client.setEndPointRealtime("wss://demo.appwrite.io/v1"); // change this later to appwrite.io + client.setEndPointRealtime( + "wss://demo.appwrite.io/v1"); // change this later to appwrite.io Realtime realtime = Realtime(client); final rtsub = realtime.subscribe(["tests"]); - await Future.delayed(Duration(seconds: 4)); + await Future.delayed(Duration(seconds: 5)); client.addHeader('Origin', 'http://localhost'); // Foo Tests print('\nTest Started'); - Response response; + Mock response; response = await foo.get(x: 'string', y: 123, z: ['string in array']); - print(response.data['result']); + print(response.result); response = await foo.post(x: 'string', y: 123, z: ['string in array']); - print(response.data['result']); + print(response.result); response = await foo.put(x: 'string', y: 123, z: ['string in array']); - print(response.data['result']); + print(response.result); response = await foo.patch(x: 'string', y: 123, z: ['string in array']); - print(response.data['result']); + print(response.result); response = await foo.delete(x: 'string', y: 123, z: ['string in array']); - print(response.data['result']); + print(response.result); // Bar Tests response = await bar.get(xrequired: 'string', xdefault: 123, z: ['string in array']); - print(response.data['result']); + print(response.result); response = await bar .post(xrequired: 'string', xdefault: 123, z: ['string in array']); - print(response.data['result']); + print(response.result); response = await bar.put(xrequired: 'string', xdefault: 123, z: ['string in array']); - print(response.data['result']); + print(response.result); response = await bar .patch(xrequired: 'string', xdefault: 123, z: ['string in array']); - print(response.data['result']); + print(response.result); response = await bar .delete(xrequired: 'string', xdefault: 123, z: ['string in array']); - print(response.data['result']); + print(response.result); // General Tests - response = await general.redirect(); - print(response.data['result']); + final res = await general.redirect(); + print(res['result']); final file = await MultipartFile.fromPath('file', '../../resources/file.png', filename: 'file.png'); response = await general.upload( x: 'string', y: 123, z: ['string in array'], file: file); - print(response.data['result']); + print(response.result); try { await general.error400(); @@ -93,8 +95,8 @@ void main() async { await Future.delayed(Duration(seconds: 5)); // response = await general.setCookie(); - // print(response.data['result']); + // print(response.result); // response = await general.getCookie(); - // print(response.data['result']); + // print(response.result); }