Skip to content
12 changes: 12 additions & 0 deletions src/SDK/Language/Flutter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
12 changes: 12 additions & 0 deletions src/SDK/SDK.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}));
}

/**
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down
5 changes: 5 additions & 0 deletions src/Spec/Spec.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ abstract public function getMethods($service);
*/
abstract public function getGlobalHeaders();

/**
* @return array
*/
abstract public function getDefinitions();

/**
* Get Attribute
*
Expand Down
40 changes: 39 additions & 1 deletion src/Spec/Swagger2.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -165,7 +176,8 @@ public function getMethods($service)
'path' => [],
'query' => [],
'body' => [],
]
],
'responseModel' => $responseModel,
];

if(isset($method['consumes']) && is_array($method['consumes'])) {
Expand Down Expand Up @@ -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;
}
}
5 changes: 5 additions & 0 deletions templates/flutter/lib/models.dart.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
library {{ language.params.packageName }}.models;

{% for definition in spec.definitions %}
part 'src/models/{{definition.name | caseSnake}}.dart';
{% endfor %}
2 changes: 2 additions & 0 deletions templates/flutter/lib/package.dart.twig
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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';
Expand Down
9 changes: 5 additions & 4 deletions templates/flutter/lib/services/service.dart.twig
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
Expand All @@ -17,7 +16,7 @@ class {{ service.name | caseUcfirst }} extends Service {
{{ method.description|dartComment }}
///
{% endif %}
{% if method.type == 'webAuth' %}Future{% else %}Future<Response>{% endif %} {{ method.name | caseCamel }}({{ _self.method_parameters(method.parameters) }}) {
{% if method.type == 'webAuth' %}Future{% elseif method.type == 'location' %} Future<Uint8List> {% else %} {% if method.responseModel and method.responseModel != 'any' %}Future<models.{{method.responseModel | caseUcfirst}}>{% 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<String, dynamic> params = {
Expand Down Expand Up @@ -72,15 +71,17 @@ 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<String, String> headers = {
{% for key, header in method.headers %}
'{{ key }}': '{{ header }}',
{% 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 %}
Expand Down
57 changes: 57 additions & 0 deletions templates/flutter/lib/src/models/model.dart.twig
Original file line number Diff line number Diff line change
@@ -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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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>(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<T> convertTo<T>(T Function(Map) fromJson) =>
{{property.name}}.map((d) => d.convertTo<T>(fromJson)).toList();
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
}
4 changes: 2 additions & 2 deletions templates/flutter/pubspec.yaml.twig
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
38 changes: 20 additions & 18 deletions tests/languages/flutter/tests.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import '../lib/packageName.dart';
import '../lib/models.dart';

void main() async {
Client client = Client();
Expand All @@ -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));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to delay at both the start and end of the test?

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();
Expand Down Expand Up @@ -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);
}