Skip to content

Commit 8ade1e3

Browse files
committed
Add Test Generation,
1 parent 21d70cc commit 8ade1e3

File tree

8 files changed

+313
-187
lines changed

8 files changed

+313
-187
lines changed

composer.lock

Lines changed: 188 additions & 132 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
include_once 'vendor/autoload.php';
44

55
use Appwrite\SDK\Language\GraphQL;
6+
use Appwrite\SDK\Language\KMP;
67
use Appwrite\Spec\Swagger2;
78
use Appwrite\SDK\SDK;
89
use Appwrite\SDK\Language\Web;
@@ -464,6 +465,31 @@ function getSSLPage($url) {
464465
;
465466
$sdk->generate(__DIR__ . '/examples/android');
466467

468+
// KMP
469+
470+
$sdk = new SDK(new KMP(), new Swagger2($spec));
471+
472+
$sdk
473+
->setName('KMP')
474+
->setNamespace('io appwrite')
475+
->setDescription('Appwrite is an open-source backend as a service server that abstract and simplify complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way. Use the Flutter SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools. For full API documentation and tutorials go to https://appwrite.io/docs')
476+
->setShortDescription('Appwrite KMP SDK')
477+
->setURL('https://example.com')
478+
->setGitUserName('appwrite')
479+
->setGitRepoName('sdk-for-kmp')
480+
->setLogo('https://appwrite.io/v1/images/console.png')
481+
->setLicenseContent('test test test')
482+
->setWarning('**This SDK is compatible with Appwrite server version 0.7.x. For older versions, please check previous releases.**')
483+
->setChangelog('**CHANGELOG**')
484+
->setVersion('0.0.0-SNAPSHOT')
485+
->setTwitter('appwrite_io')
486+
->setDiscord('564160730845151244', 'https://appwrite.io/discord')
487+
->setDefaultHeaders([
488+
'x-appwrite-response-format' => '0.7.0',
489+
])
490+
;
491+
$sdk->generate(__DIR__ . '/examples/kmp');
492+
467493
// Kotlin
468494
$sdk = new SDK(new Kotlin(), new Swagger2($spec));
469495

src/SDK/Language/Android.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,11 +339,11 @@ public function getFiles(): array
339339
];
340340
}
341341

342-
protected function getReturnType(array $method, array $spec, string $namespace, string $generic = 'T'): string
342+
protected function getReturnType(array $method, array $spec, string $namespace, bool $withGeneric = true, string $generic = 'T'): string
343343
{
344344
if ($method['type'] === 'webAuth') {
345345
return 'Bool';
346346
}
347-
return parent::getReturnType($method, $spec, $namespace, $generic);
347+
return parent::getReturnType($method, $spec, $namespace, $withGeneric, $generic);
348348
}
349349
}

src/SDK/Language/KMP.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ public function getName(): string
1212
return 'KMP';
1313
}
1414

15-
protected function getReturnType(array $method, array $spec, string $namespace, string $generic = 'T'): string
15+
protected function getReturnType(array $method, array $spec, string $namespace, bool $withGeneric = true, string $generic = 'T'): string
1616
{
1717
if ($method['type'] === 'webAuth') {
1818
return 'Bool';
1919
}
20-
return parent::getReturnType($method, $spec, $namespace, $generic);
20+
return parent::getReturnType($method, $spec, $namespace, $withGeneric, $generic);
2121
}
2222

2323
public function getFiles(): array
@@ -196,8 +196,8 @@ public function getFiles(): array
196196
'template' => '/kmp/shared/src/commonMain/kotlin/io/package/models/InputFile.kt.twig',
197197
],
198198
[
199-
'scope' => 'default',
200-
'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/models/Model.kt',
199+
'scope' => 'definition',
200+
'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/models//models/{{ definition.name | caseUcfirst }}.kt',
201201
'template' => '/kmp/shared/src/commonMain/kotlin/io/package/models/Model.kt.twig',
202202
],
203203
[
@@ -235,8 +235,8 @@ public function getFiles(): array
235235
'template' => '/kmp/shared/src/commonMain/kotlin/io/package/services/Realtime.kt.twig',
236236
],
237237
[
238-
'scope' => 'default',
239-
'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/services/Service.kt',
238+
'scope' => 'service',
239+
'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/services/{{service.name | caseUcfirst}}.kt',
240240
'template' => '/kmp/shared/src/commonMain/kotlin/io/package/services/Service.kt.twig',
241241
],
242242

src/SDK/Language/Kotlin.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ public function getFilters(): array
447447
];
448448
}
449449

450-
protected function getReturnType(array $method, array $spec, string $namespace, string $generic = 'T'): string
450+
protected function getReturnType(array $method, array $spec, string $namespace, bool $withGeneric = true, string $generic = 'T'): string
451451
{
452452
if ($method['type'] === 'webAuth') {
453453
return 'String';
@@ -466,7 +466,7 @@ protected function getReturnType(array $method, array $spec, string $namespace,
466466

467467
$ret = $this->toPascalCase($method['responseModel']);
468468

469-
if ($this->hasGenericType($method['responseModel'], $spec)) {
469+
if ($this->hasGenericType($method['responseModel'], $spec) && $withGeneric) {
470470
$ret .= '<' . $generic . '>';
471471
}
472472

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package {{ sdk.namespace | caseDot }}.models
22

3-
import com.google.gson.annotations.SerializedName
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
45
import io.appwrite.extensions.jsonCast
56

67
/**
78
* {{ definition.description }}
89
*/
9-
@Serializable
10-
{% if definition.properties | length != 0 or definition.additionalProperties %}data {% endif %}class {{ definition | modelType(spec) | raw }}(
10+
{% if definition.properties | length != 0 or definition.additionalProperties %}
11+
@Serializable
12+
data {% endif %}class {{ definition | modelType(spec) | raw }}(
13+
{%~ if definition.properties is defined %}
1114
{%~ for property in definition.properties %}
1215
/**
1316
* {{ property.description }}
@@ -18,11 +21,12 @@ import io.appwrite.extensions.jsonCast
1821
{%- endif %} {{ property.name | escapeKeyword | removeDollarSign }}: {{ property | propertyType(spec) | raw }},
1922

2023
{%~ endfor %}
24+
{%~ endif %}
2125
{%~ if definition.additionalProperties %}
2226
/**
2327
* Additional properties
2428
*/
2529
@SerialName("data")
2630
val data: T
2731
{%~ endif %}
28-
)
32+
)

templates/kmp/shared/src/commonMain/kotlin/io/package/services/Service.kt.twig

Lines changed: 46 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package {{ sdk.namespace | caseDot }}.services
22

3-
import android.net.Uri
43
import {{ sdk.namespace | caseDot }}.Client
54
import {{ sdk.namespace | caseDot }}.Service
65
{% if spec.definitions is not empty %}
@@ -38,32 +37,38 @@ import java.io.File
3837

3938
/**
4039
* {{ service.description | raw | replace({"\n": "", "\r": ""}) }}
41-
**/
40+
**/
4241
class {{ service.name | caseUcfirst }}(client: Client) : Service(client) {
42+
{% for method in service.methods %}
4343

44-
{% for method in service.methods %}
4544
/**
4645
* {{ method.title }}
4746
*
4847
* {{ method.description | raw | replace({"\n": "", "\r": ""}) }}
4948
*
49+
{%~ if method.parameters.all | reduce((carry, param) => carry or not param.required) %}
50+
@JvmOverloads
51+
{%~ endif %}
52+
@Throws(Throwable::class)
5053
{%~ for parameter in method.parameters.all %}
5154
* @param {{ parameter.name | caseCamel }} {{ parameter.description | raw }}
5255
{%~ endfor %}
5356
{%~ if method.type != "webAuth" %}
5457
* @return [{{ method | returnType(spec, sdk.namespace | caseDot) | raw }}]
5558
{%~ endif %}
5659
*/
60+
@Throws(Throwable::class)
5761
{%~ if method.parameters.all | reduce((carry, param) => carry or not param.required) %}
5862
@JvmOverloads
5963
{%~ endif %}
6064
{% if method.responseModel | hasGenericType(spec) %}
61-
suspend inline fun {{ 'reified <T> : Any' | raw }} {{ method.name | caseCamel }}(
65+
suspend inline fun {{ '<reified T : Any>' | raw }} {{ method.name | caseCamel }}(
6266
{% else %}
6367
suspend fun {{ method.name | caseCamel }}(
6468
{% endif %}
6569
{%~ if method.type == "webAuth" %}
6670
webAuth: WebAuthComponent,
71+
{%~ endif %}
6772
{%~ for parameter in method.parameters.all %}
6873
{{ parameter.name | caseCamel }}: {{ parameter | typeName }}{%~ if not parameter.required or parameter.nullable %}? = null{% endif %},
6974
{%~ endfor %}
@@ -80,21 +85,21 @@ class {{ service.name | caseUcfirst }}(client: Client) : Service(client) {
8085
{%~ endfor %}
8186

8287
val apiParams = mutableMapOf<String, Any?>(
83-
{%~ for parameter in method.parameters.query | merge(method.parameters.body) %}
88+
{%~ for parameter in method.parameters.query | merge(method.parameters.body) %}
8489
"{{ parameter.name }}" to {{ parameter.name | caseCamel }},
85-
{%~ endfor %}
86-
{%~ if method.type == 'location' or method.type == 'webAuth' %}
87-
{%~ if method.auth | length > 0 %}
88-
{%~ for node in method.auth %}
89-
{%~ for key,header in node | keys %}
90+
{%~ endfor %}
91+
{%~ if method.type == 'location' or method.type == 'webAuth' %}
92+
{%~ if method.auth | length > 0 %}
93+
{%~ for node in method.auth %}
94+
{%~ for key,header in node | keys %}
9095
"{{ header | caseLower }}" to client.config["{{ header | caseLower }}"],
91-
{%~ endfor %}
92-
{%~ endfor %}
93-
{%~ endif %}
94-
{%~ endif %}
96+
{%~ endfor %}
97+
{%~ endfor %}
98+
{%~ endif %}
99+
{%~ endif %}
95100
)
96-
{%~ if method.type == 'webAuth' %}
97101

102+
{% if method.type == 'webAuth' %}
98103
val apiQuery = mutableListOf<String>()
99104
apiParams.forEach {
100105
when (it.value) {
@@ -138,43 +143,44 @@ class {{ service.name | caseUcfirst }}(client: Client) : Service(client) {
138143
}
139144
}
140145
}
141-
{%~ elseif method.type == 'location' %}
146+
{% elseif method.type == 'location' %}
142147
return client.call(
143148
"{{ method.method | caseUpper }}",
144149
apiPath,
145150
params = apiParams,
146151
responseType = {{ method | returnType(spec, sdk.namespace | caseDot) | raw }}::class
147152
)
148-
{%~ else %}
153+
{% else %}
149154
val apiHeaders = mutableMapOf(
150-
{%~ for key, header in method.headers %}
155+
{%~ for key, header in method.headers %}
151156
"{{ key }}" to "{{ header }}",
152-
{%~ endfor %}
157+
{%~ endfor %}
153158
)
159+
154160
{%~ if method.responseModel | hasGenericType(spec) %}
155161
val actualSerializer = genericSerializer ?: getSerializer(T::class)
156162
{%~ endif %}
157-
}
158-
{%~ endif %}
163+
159164
{%~ if 'multipart/form-data' in method.consumes %}
160165
val idParamName: String? = {% if method.parameters.all | filter(p => p.isUploadID) | length > 0 %}{% for parameter in method.parameters.all | filter(parameter => parameter.isUploadID) %}"{{ parameter.name }}"{% endfor %}{% else %}null{% endif %}
161-
166+
162167
{%~ for parameter in method.parameters.all %}
163168
{%~ if parameter.type == 'file' %}
164169
val paramName = "{{ parameter.name }}"
165170
{%~ endif %}
166171
{%~ endfor %}
172+
167173
return client.chunkedUpload(
168174
apiPath,
169175
apiHeaders,
170176
apiParams,
171177
responseType = {{ method | returnType(spec, sdk.namespace | caseDot) | raw }}::class,
172178
{%~ if method.responseModel %}
173-
{{ method | returnType(spec, sdk.namespace | caseDot) | raw }}.serializer(),
179+
{{ method | returnType(spec, sdk.namespace | caseDot, false) | raw }}.serializer(),
174180
{%~ endif %}
175181
paramName,
176182
idParamName,
177-
onProgress,
183+
onProgress
178184
)
179185
{%~ else %}
180186
return client.call(
@@ -188,15 +194,15 @@ class {{ service.name | caseUcfirst }}(client: Client) : Service(client) {
188194
responseType = {{ method | returnType(spec, sdk.namespace | caseDot) | raw }}::class,
189195
{%~ endif %}
190196
{%~ if method.responseModel | hasGenericType(spec) %}
191-
serializer = {{ method | returnType(spec, sdk.namespace | caseDot) | raw }}.serializer(actualSerializer),
197+
serializer = {{ method | returnType(spec, sdk.namespace | caseDot, false) | raw }}.serializer(actualSerializer)
192198
{%~ elseif method.responseModel == 'any' %}
193199
serializer = DynamicLookupSerializer
194-
{%~ else method.responseModel %}
195-
serializer = {{ method | returnType(spec, sdk.namespace | caseDot) | raw }}.serializer()
200+
{%~ else %}
201+
serializer = {{ method | returnType(spec, sdk.namespace | caseDot, false) | raw }}.serializer()
196202
{%~ endif %}
197203
)
198204
{%~ endif %}
199-
{%~ endif %}
205+
{% endif %}
200206
}
201207

202208
{%~ if method.responseModel | hasGenericType(spec) %}
@@ -212,10 +218,10 @@ class {{ service.name | caseUcfirst }}(client: Client) : Service(client) {
212218
* @return [{{ method | returnType(spec, sdk.namespace | caseDot) | raw }}]
213219
{%~ endif %}
214220
*/
221+
@Throws(Throwable::class)
215222
{%~ if method.parameters.all | reduce((carry, param) => carry or not param.required) %}
216223
@JvmOverloads
217224
{%~ endif %}
218-
@Throws(Throwable::class)
219225
suspend fun {{ method.name | caseCamel }}(
220226
{%~ if method.type == "webAuth" %}
221227
webAuth: WebAuthComponent,
@@ -227,20 +233,19 @@ class {{ service.name | caseUcfirst }}(client: Client) : Service(client) {
227233
onProgress: ((UploadProgress) -> Unit)? = null
228234
{%~ endif %}
229235
): {{ method | returnType(spec, sdk.namespace | caseDot, 'Map<String, Any>') | raw }} = {{ method.name | caseCamel }}(
230-
{%~ if method.type == "webAuth" %}
231-
webAuth,
232-
{%~ endif %}
233-
{%~ for parameter in method.parameters.all %}
236+
{%~ if method.type == "webAuth" %}
237+
webAuth,
238+
{%~ endif %}
239+
{%~ for parameter in method.parameters.all %}
234240
{{ parameter.name | caseCamel }},
235-
{%~ endfor %}
236-
{%~ if method.responseModel | hasGenericType(spec) %}
241+
{%~ endfor %}
242+
{%~ if method.responseModel | hasGenericType(spec) %}
237243
nestedType = classOf(),
238-
{%~ endif %}
239-
{%~ if 'multipart/form-data' in method.consumes %}
244+
{%~ endif %}
245+
{%~ if 'multipart/form-data' in method.consumes %}
240246
onProgress = onProgress
241-
{%~ endif %}
247+
{%~ endif %}
242248
)
243249
{%~ endif %}
244-
245-
{%~ endfor %}
246-
}
250+
{% endfor %}
251+
}

tests/KMPAndroid14Java17Test.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace Tests;
4+
5+
class KMPAndroid14Java17Test extends Base
6+
{
7+
protected string $sdkName = 'kmp';
8+
protected string $sdkPlatform = 'client';
9+
protected string $sdkLanguage = 'kmp';
10+
protected string $version = '0.0.1';
11+
12+
protected string $language = 'kmp';
13+
protected string $class = 'Appwrite\SDK\Language\KMP';
14+
protected array $build = [
15+
'mkdir -p tests/sdks/kmp/library/src/test/java',
16+
'cp tests/languages/kmp/Tests.kt tests/sdks/kmp/library/src/test/java/Tests.kt',
17+
'chmod +x tests/sdks/kmp/gradlew',
18+
];
19+
protected string $command =
20+
'docker run --rm --network="mockapi" -v $(pwd):/app -w /app/tests/sdks/kmp alvrme/alpine-android:android-34-jdk17 sh -c "./gradlew :library:testReleaseUnitTest --stacktrace -q && cat library/result.txt"';
21+
22+
protected array $expectedOutput = [
23+
...Base::FOO_RESPONSES,
24+
...Base::BAR_RESPONSES,
25+
...Base::GENERAL_RESPONSES,
26+
...Base::UPLOAD_RESPONSES,
27+
...Base::ENUM_RESPONSES,
28+
...Base::EXCEPTION_RESPONSES,
29+
...Base::REALTIME_RESPONSES,
30+
// ...Base::COOKIE_RESPONSES,
31+
...Base::QUERY_HELPER_RESPONSES,
32+
...Base::PERMISSION_HELPER_RESPONSES,
33+
...Base::ID_HELPER_RESPONSES
34+
];
35+
}

0 commit comments

Comments
 (0)