Skip to content

Commit 950363e

Browse files
committed
Implement response models in client and services
1 parent 99676bc commit 950363e

File tree

2 files changed

+93
-35
lines changed

2 files changed

+93
-35
lines changed

templates/android/library/src/main/java/io/appwrite/Client.kt.twig

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
1717
import okhttp3.MediaType.Companion.toMediaType
1818
import okhttp3.RequestBody.Companion.asRequestBody
1919
import okhttp3.RequestBody.Companion.toRequestBody
20+
import java.io.BufferedInputStream
2021
import java.io.BufferedReader
2122
import java.io.File
2223
import java.io.IOException
@@ -44,6 +45,8 @@ class Client @JvmOverloads constructor(
4445

4546
private val job = Job()
4647

48+
private val gson = Gson()
49+
4750
lateinit var http: OkHttpClient
4851

4952
private val headers: MutableMap<String, String>
@@ -203,13 +206,14 @@ class Client @JvmOverloads constructor(
203206
* @return [Response]
204207
*/
205208
@Throws({{ spec.title | caseUcfirst }}Exception::class)
206-
suspend fun call(
209+
suspend fun <T> call(
207210
method: String,
208211
path: String,
209212
headers: Map<String, String> = mapOf(),
210-
params: Map<String, Any?> = mapOf()
211-
): Response {
212-
213+
params: Map<String, Any?> = mapOf(),
214+
responseType: Class<T>,
215+
convert: ((Map<String, Any,>) -> T)? = null
216+
): T {
213217
val filteredParams = params.filterValues { it != null }
214218

215219
val requestHeaders = this.headers.toHeaders().newBuilder()
@@ -244,7 +248,7 @@ class Client @JvmOverloads constructor(
244248
.get()
245249
.build()
246250

247-
return awaitResponse(request)
251+
return awaitResponse(request, responseType, convert)
248252
}
249253

250254
val body = if (MultipartBody.FORM.toString() == headers["content-type"]) {
@@ -272,7 +276,7 @@ class Client @JvmOverloads constructor(
272276
}
273277
builder.build()
274278
} else {
275-
Gson().toJson(filteredParams)
279+
gson.toJson(filteredParams)
276280
.toRequestBody("application/json".toMediaType())
277281
}
278282

@@ -282,21 +286,25 @@ class Client @JvmOverloads constructor(
282286
.method(method, body)
283287
.build()
284288

285-
return awaitResponse(request)
289+
return awaitResponse(request, responseType, convert)
286290
}
287291

288292
/**
289293
* Await Response
290-
*
294+
*
291295
* @param method
292296
* @param path
293297
* @param headers
294298
* @param params
295299
*
296-
* @return [Response]
300+
* @return [T]
297301
*/
298302
@Throws({{ spec.title | caseUcfirst }}Exception::class)
299-
private suspend fun awaitResponse(request: Request) = suspendCancellableCoroutine<Response> {
303+
private suspend fun <T> awaitResponse(
304+
request: Request,
305+
responseType: Class<T>,
306+
convert: ((Map<String, Any,>) -> T)? = null
307+
) = suspendCancellableCoroutine<T> {
300308
http.newCall(request).enqueue(object : Callback {
301309
override fun onFailure(call: Call, e: IOException) {
302310
if (it.isCancelled) {
@@ -306,26 +314,52 @@ class Client @JvmOverloads constructor(
306314
}
307315

308316
override fun onResponse(call: Call, response: Response) {
309-
if (response.code >= 400) {
310-
val bodyString = response.body
311-
?.charStream()
312-
?.buffered()
313-
?.use(BufferedReader::readText) ?: ""
314-
315-
val contentType: String = response.headers["content-type"] ?: ""
316-
val error = if (contentType.contains("application/json", ignoreCase = true)) {
317-
bodyString.fromJson<{{ spec.title | caseUcfirst }}Exception>()
317+
if (!response.isSuccessful) {
318+
val body = response.body!!
319+
.charStream()
320+
.buffered()
321+
.use(BufferedReader::readText)
322+
val error = if (response.headers["content-type"]?.contains("application/json") == true) {
323+
body.fromJson()
318324
} else {
319-
{{ spec.title | caseUcfirst }}Exception(bodyString, response.code)
325+
{{ spec.title | caseUcfirst }}Exception(body, response.code)
320326
}
321-
322-
it.cancel(AppwriteException(
323-
error.message,
324-
error.code,
325-
bodyString
326-
))
327+
it.cancel(error)
328+
return
329+
}
330+
when {
331+
responseType == Boolean::class.java -> {
332+
it.resume(true as T)
333+
return
334+
}
335+
responseType == ByteArray::class.java -> {
336+
it.resume(response.body!!
337+
.byteStream()
338+
.buffered()
339+
.use(BufferedInputStream::readBytes) as T
340+
)
341+
return
342+
}
343+
response.body == null -> {
344+
it.resume(true as T)
345+
return
346+
}
347+
}
348+
val body = response.body!!
349+
.charStream()
350+
.buffered()
351+
.use(BufferedReader::readText)
352+
if (body.isEmpty()) {
353+
it.resume(true as T)
354+
return
327355
}
328-
it.resume(response)
356+
val map = gson.fromJson<Map<String, Any>>(
357+
body,
358+
HashMap::class.java
359+
)
360+
it.resume(
361+
convert?.invoke(map) ?: map as T
362+
)
329363
}
330364
})
331365
}

templates/android/library/src/main/java/io/appwrite/services/ServiceTemplate.kt.twig

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1-
package {{ sdk.namespace | caseDot }}.services
2-
31
{% macro parameter(parameter) %}{{ parameter.name | caseCamel }}: {{ parameter.type | typeName }}{% if not parameter.required %}? = null{% endif %}{% endmacro %}
42
{% macro method_parameters(parameters) %}
53
{% if parameters.all|length > 0 %}{% for parameter in parameters.all %}{{ '\n\t\t' }}{{ _self.parameter(parameter) }}{% if not loop.last %}{{ ',' }}{% endif %}{% endfor %}{% endif %}
64
{% endmacro %}
75
{% macro methodNeedsSecurityParameters(method) %}
86
{% if (method.type == "webAuth" or method.type == "location") and method.security|length > 0 %}{{ true }}{% else %}{{false}}{% endif %}
97
{% endmacro %}
8+
{% macro resultType(namespace, method) %}
9+
{% if method.type == "webAuth" %}Bool{% elseif method.type == "location" %}ByteArray{% elseif not method.responseModel or method.responseModel == 'any' %}Any{% else %}{{ namespace | caseDot}}.models.{{method.responseModel | caseUcfirst}}{% endif %}
10+
{% endmacro %}
11+
package {{ sdk.namespace | caseDot }}.services
12+
1013
import android.net.Uri
1114
import {{ sdk.namespace | caseDot }}.Client
15+
import {{ sdk.namespace | caseDot }}.models.*
1216
{% if service.features.webAuth %}
1317
import {{ sdk.namespace | caseDot }}.WebAuthComponent
1418
import androidx.activity.ComponentActivity
@@ -40,7 +44,7 @@ class {{ service.name | caseUcfirst }}(client: Client) : Service(client) {
4044
*/
4145
@JvmOverloads
4246
@Throws({{ spec.title | caseUcfirst }}Exception::class)
43-
suspend fun {{ method.name | caseCamel }}({% if method.type == "webAuth" %}{{ '\n\t\t' }}activity: ComponentActivity{% if method.parameters.all | length > 0 %}, {% endif %}{% endif %}{{ _self.method_parameters(method.parameters) }}{% if method.parameters.all|length > 0 %}{{ '\n\t' }}{% endif %}){% if method.type != "webAuth" %}: Response{% endif %} {
47+
suspend fun {{ method.name | caseCamel }}({% if method.type == "webAuth" %}{{ '\n\t\t' }}activity: ComponentActivity{% if method.parameters.all | length > 0 %}, {% endif %}{% endif %}{{ _self.method_parameters(method.parameters) }}{% if method.parameters.all|length > 0 %}{{ '\n\t' }}{% endif %}){% if method.type != "webAuth" %}: {{ _self.resultType(sdk.namespace, method) }}{% endif %} {
4448
val path = "{{ method.path }}"{% for parameter in method.parameters.path %}.replace("{{ '{' ~ parameter.name | caseCamel ~ '}' }}", {{ parameter.name | caseCamel }}){% endfor %}
4549

4650
val params = mapOf<String, Any?>(
@@ -55,7 +59,6 @@ class {{ service.name | caseUcfirst }}(client: Client) : Service(client) {
5559

5660
{% endfor %}{% endfor %}{% endif %}
5761
)
58-
5962
{% if method.type == 'webAuth' %}
6063
val query = mutableListOf<String>()
6164
params.forEach {
@@ -99,15 +102,36 @@ class {{ service.name | caseUcfirst }}(client: Client) : Service(client) {
99102
listOf(cookie)
100103
)
101104
}
102-
103105
{% elseif method.type == 'location' %}
104-
return client.call("{{ method.method | caseUpper }}", path, params = params)
106+
return client.call(
107+
"{{ method.method | caseUpper }}",
108+
path,
109+
params = params,
110+
responseType = {{ _self.resultType(sdk.namespace, method) }}::class.java
111+
)
105112
{% else %}
106113
val headers = mapOf(
107114
{{ method.headers|map((header, key) => " \"#{key}\" to \"#{header}\"")|join(',\n')|raw }}
108115
)
109-
110-
return client.call("{{ method.method | caseUpper }}", path, headers, params)
116+
{% if method.responseModel %}
117+
val convert: (Map<String, Any>) -> {{ _self.resultType(sdk.namespace, method) }} = {
118+
{% if method.responseModel == 'any' %}
119+
it
120+
{% else %}
121+
{{ _self.resultType(sdk.namespace, method) }}.from(map = it)
122+
{% endif %}
123+
}
124+
{% endif %}
125+
return client.call(
126+
"{{ method.method | caseUpper }}",
127+
path,
128+
headers,
129+
params,
130+
responseType = {{ _self.resultType(sdk.namespace, method) }}::class.java,
131+
{% if method.responseModel %}
132+
convert = convert
133+
{% endif %}
134+
)
111135
{% endif %}
112136
}
113137

0 commit comments

Comments
 (0)