Skip to content

Commit b689ae2

Browse files
committed
Evaluate Ktorfit (WIP)
1 parent 9fe9899 commit b689ae2

File tree

10 files changed

+210
-325
lines changed

10 files changed

+210
-325
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,5 @@ plugins {
3535
alias(libs.plugins.google.services) apply false
3636
alias(libs.plugins.firebase.crashlytics) apply false
3737
alias(libs.plugins.about.libraries) apply false
38+
alias(libs.plugins.ktorfit) apply false
3839
}

google/tasks/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
plugins {
2424
alias(libs.plugins.jetbrains.kotlin.multiplatform)
2525
alias(libs.plugins.jetbrains.kotlin.serialization)
26+
alias(libs.plugins.ksp)
27+
alias(libs.plugins.ktorfit)
2628
}
2729

2830
kotlin {
@@ -32,6 +34,8 @@ kotlin {
3234
commonMain.dependencies {
3335
implementation(libs.kotlinx.datetime)
3436
implementation(libs.bundles.ktor.client)
37+
38+
implementation(libs.ktorfit)
3539
}
3640

3741
commonTest.dependencies {
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright (c) 2024 Olivier Patry
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining
5+
* a copy of this software and associated documentation files (the "Software"),
6+
* to deal in the Software without restriction, including without limitation
7+
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
8+
* and/or sell copies of the Software, and to permit persons to whom the Software
9+
* is furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
16+
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17+
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18+
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19+
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
20+
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
*/
22+
23+
package net.opatry.google.tasks
24+
25+
import de.jensklingenberg.ktorfit.Ktorfit
26+
import de.jensklingenberg.ktorfit.converter.Converter
27+
import de.jensklingenberg.ktorfit.converter.KtorfitResult
28+
import de.jensklingenberg.ktorfit.converter.TypeData
29+
import io.ktor.client.call.body
30+
import io.ktor.client.plugins.ClientRequestException
31+
import io.ktor.client.statement.HttpResponse
32+
import io.ktor.client.statement.bodyAsText
33+
import io.ktor.http.isSuccess
34+
35+
object ClientRequestExceptionConverterFactory : Converter.Factory {
36+
37+
override fun suspendResponseConverter(
38+
typeData: TypeData,
39+
ktorfit: Ktorfit
40+
): Converter.SuspendResponseConverter<HttpResponse, *> {
41+
return object : Converter.SuspendResponseConverter<HttpResponse, Any> {
42+
override suspend fun convert(result: KtorfitResult): Any {
43+
when (result) {
44+
is KtorfitResult.Success -> {
45+
val response = result.response
46+
if (response.status.isSuccess()) {
47+
return response.body(typeData.typeInfo)
48+
} else {
49+
throw ClientRequestException(response, response.bodyAsText())
50+
}
51+
}
52+
is KtorfitResult.Failure -> {
53+
throw result.throwable
54+
}
55+
}
56+
}
57+
}
58+
}
59+
}

google/tasks/src/commonMain/kotlin/net/opatry/google/tasks/TaskListsApi.kt

Lines changed: 36 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,15 @@
2222

2323
package net.opatry.google.tasks
2424

25-
import io.ktor.client.HttpClient
26-
import io.ktor.client.call.body
27-
import io.ktor.client.plugins.ClientRequestException
28-
import io.ktor.client.plugins.compression.compress
29-
import io.ktor.client.request.delete
30-
import io.ktor.client.request.get
31-
import io.ktor.client.request.parameter
32-
import io.ktor.client.request.patch
33-
import io.ktor.client.request.post
34-
import io.ktor.client.request.put
35-
import io.ktor.client.request.setBody
36-
import io.ktor.client.statement.bodyAsText
37-
import io.ktor.http.ContentType
38-
import io.ktor.http.contentType
39-
import io.ktor.http.isSuccess
25+
import de.jensklingenberg.ktorfit.http.Body
26+
import de.jensklingenberg.ktorfit.http.DELETE
27+
import de.jensklingenberg.ktorfit.http.GET
28+
import de.jensklingenberg.ktorfit.http.Headers
29+
import de.jensklingenberg.ktorfit.http.PATCH
30+
import de.jensklingenberg.ktorfit.http.POST
31+
import de.jensklingenberg.ktorfit.http.PUT
32+
import de.jensklingenberg.ktorfit.http.Path
33+
import de.jensklingenberg.ktorfit.http.Query
4034
import net.opatry.google.tasks.model.ResourceListResponse
4135
import net.opatry.google.tasks.model.ResourceType
4236
import net.opatry.google.tasks.model.TaskList
@@ -45,12 +39,8 @@ import net.opatry.google.tasks.model.TaskList
4539
* Service for interacting with the [Google Task Lists REST API](https://developers.google.com/tasks/reference/rest/v1/tasklists).
4640
*/
4741
interface TaskListsApi {
48-
/**
49-
* [Deletes the authenticated user's specified task list](https://developers.google.com/tasks/reference/rest/v1/tasklists/delete). If the list contains assigned tasks, both the assigned tasks and the original tasks in the assignment surface (Docs, Chat Spaces) are deleted.
50-
*
51-
* @param taskListId Task list identifier.
52-
*/
53-
suspend fun delete(taskListId: String)
42+
@DELETE("tasks/v1/users/@me/lists/{taskListId}")
43+
suspend fun delete(@Path("taskListId") taskListId: String)
5444

5545
/**
5646
* [Returns the authenticated user's specified task list](https://developers.google.com/tasks/reference/rest/v1/tasklists/get).
@@ -59,7 +49,8 @@ interface TaskListsApi {
5949
*
6050
* @return an instance of [TaskList].
6151
*/
62-
suspend fun get(taskListId: String): TaskList
52+
@GET("tasks/v1/users/@me/lists/{taskListId}")
53+
suspend fun get(@Path("taskListId") taskListId: String): TaskList
6354

6455
/**
6556
* [Creates a new task list](https://developers.google.com/tasks/reference/rest/v1/tasklists/insert) and adds it to the authenticated user's task lists. A user can have up to 2000 lists at a time.
@@ -68,7 +59,12 @@ interface TaskListsApi {
6859
*
6960
* @return a newly created instance of [TaskList].
7061
*/
71-
suspend fun insert(taskList: TaskList): TaskList
62+
@Headers(
63+
"Content-Type: application/json",
64+
"Content-Encoding: gzip",
65+
)
66+
@POST("tasks/v1/users/@me/lists")
67+
suspend fun insert(@Body taskList: TaskList): TaskList
7268

7369
/**
7470
* [Returns all the authenticated user's task lists](https://developers.google.com/tasks/reference/rest/v1/tasklists/list). A user can have up to 2000 lists at a time.
@@ -78,7 +74,11 @@ interface TaskListsApi {
7874
*
7975
* @return an instance of [ResourceListResponse] of type [TaskList], whose type is always [ResourceType.TaskLists].
8076
*/
81-
suspend fun list(maxResults: Int = 20, pageToken: String? = null): ResourceListResponse<TaskList>
77+
@GET("tasks/v1/users/@me/lists")
78+
suspend fun list(
79+
@Query("maxResults") maxResults: Int = 20,
80+
@Query("pageToken") pageToken: String? = null
81+
): ResourceListResponse<TaskList>
8282

8383
/**
8484
* [Updates the authenticated user's specified task list](https://developers.google.com/tasks/reference/rest/v1/tasklists/patch). This method supports patch semantics.
@@ -88,7 +88,12 @@ interface TaskListsApi {
8888
*
8989
* @return an instance of [TaskList].
9090
*/
91-
suspend fun patch(taskListId: String, taskList: TaskList): TaskList
91+
@Headers("Content-Type: application/json")
92+
@PATCH("tasks/v1/users/@me/lists/{taskListId}")
93+
suspend fun patch(
94+
@Path("taskListId") taskListId: String,
95+
@Body taskList: TaskList
96+
): TaskList
9297

9398
/**
9499
* [Updates the authenticated user's specified task list](https://developers.google.com/tasks/reference/rest/v1/tasklists/update).
@@ -98,85 +103,12 @@ interface TaskListsApi {
98103
*
99104
* @return an instance of [TaskList].
100105
*/
101-
suspend fun update(taskListId: String, taskList: TaskList): TaskList
102-
}
103-
104-
class HttpTaskListsApi(
105-
private val httpClient: HttpClient
106-
) : TaskListsApi {
107-
override suspend fun delete(taskListId: String) {
108-
val response = httpClient.delete("tasks/v1/users/@me/lists/${taskListId}")
109-
110-
if (response.status.isSuccess()) {
111-
return response.body()
112-
} else {
113-
throw ClientRequestException(response, response.bodyAsText())
114-
}
115-
}
116-
117-
override suspend fun get(taskListId: String): TaskList {
118-
val response = httpClient.get("tasks/v1/users/@me/lists/${taskListId}")
119-
120-
if (response.status.isSuccess()) {
121-
return response.body()
122-
} else {
123-
throw ClientRequestException(response, response.bodyAsText())
124-
}
125-
}
126-
127-
override suspend fun insert(taskList: TaskList): TaskList {
128-
val response = httpClient.post("tasks/v1/users/@me/lists") {
129-
contentType(ContentType.Application.Json)
130-
compress("gzip")
131-
setBody(taskList)
132-
}
133-
134-
if (response.status.isSuccess()) {
135-
return response.body()
136-
} else {
137-
throw ClientRequestException(response, response.bodyAsText())
138-
}
139-
}
140-
141-
override suspend fun list(maxResults: Int, pageToken: String?): ResourceListResponse<TaskList> {
142-
val response = httpClient.get("tasks/v1/users/@me/lists") {
143-
parameter("maxResults", maxResults.coerceIn(0, 100))
144-
if (pageToken != null) {
145-
parameter("pageToken", pageToken)
146-
}
147-
}
148-
if (response.status.isSuccess()) {
149-
return response.body()
150-
} else {
151-
throw ClientRequestException(response, response.bodyAsText())
152-
}
153-
}
154-
155-
override suspend fun patch(taskListId: String, taskList: TaskList): TaskList {
156-
val response = httpClient.patch("tasks/v1/users/@me/lists/${taskListId}") {
157-
contentType(ContentType.Application.Json)
158-
setBody(taskList)
159-
}
160-
161-
if (response.status.isSuccess()) {
162-
return response.body()
163-
} else {
164-
throw ClientRequestException(response, response.bodyAsText())
165-
}
166-
}
167-
168-
override suspend fun update(taskListId: String, taskList: TaskList): TaskList {
169-
val response = httpClient.put("tasks/v1/users/@me/lists/${taskListId}") {
170-
contentType(ContentType.Application.Json)
171-
setBody(taskList)
172-
}
173-
174-
if (response.status.isSuccess()) {
175-
return response.body()
176-
} else {
177-
throw ClientRequestException(response, response.bodyAsText())
178-
}
179-
}
106+
@Headers("Content-Type: application/json")
107+
@PUT("tasks/v1/users/@me/lists/{taskListId}")
108+
suspend fun update(
109+
@Path("taskListId") taskListId: String,
110+
@Body taskList: TaskList
111+
): TaskList
180112
}
181113

182114
/**

0 commit comments

Comments
 (0)