Skip to content

Commit bc821b9

Browse files
committed
Evaluate Ktorfit (WIP)
1 parent cce4008 commit bc821b9

File tree

10 files changed

+211
-328
lines changed

10 files changed

+211
-328
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ plugins {
3838
alias(libs.plugins.firebase.crashlytics) apply false
3939
alias(libs.plugins.about.libraries) apply false
4040
alias(libs.plugins.kover)
41+
alias(libs.plugins.ktorfit) apply false
4142
}
4243

4344
val koverProjects = listOf(

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 {
@@ -34,6 +36,8 @@ kotlin {
3436
commonMain.dependencies {
3537
implementation(libs.kotlinx.datetime)
3638
implementation(libs.bundles.ktor.client)
39+
40+
implementation(libs.ktorfit)
3741
}
3842

3943
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: 37 additions & 107 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,20 +39,16 @@ 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 default task list](https://developers.google.com/tasks/reference/rest/v1/tasklists/get).
5747
* It uses the special `taskListId` value `@default`.
5848
*
5949
* @return the instance of [TaskList] of the default task list.
6050
*/
61-
suspend fun default(): TaskList
51+
suspend fun default() = get("@default")
6252

6353
/**
6454
* [Returns the authenticated user's specified task list](https://developers.google.com/tasks/reference/rest/v1/tasklists/get).
@@ -67,7 +57,8 @@ interface TaskListsApi {
6757
*
6858
* @return an instance of [TaskList].
6959
*/
70-
suspend fun get(taskListId: String): TaskList
60+
@GET("tasks/v1/users/@me/lists/{taskListId}")
61+
suspend fun get(@Path("taskListId") taskListId: String): TaskList
7162

7263
/**
7364
* [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.
@@ -76,7 +67,12 @@ interface TaskListsApi {
7667
*
7768
* @return a newly created instance of [TaskList].
7869
*/
79-
suspend fun insert(taskList: TaskList): TaskList
70+
@Headers(
71+
"Content-Type: application/json",
72+
"Content-Encoding: gzip",
73+
)
74+
@POST("tasks/v1/users/@me/lists")
75+
suspend fun insert(@Body taskList: TaskList): TaskList
8076

8177
/**
8278
* [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.
@@ -86,7 +82,11 @@ interface TaskListsApi {
8682
*
8783
* @return an instance of [ResourceListResponse] of type [TaskList], whose type is always [ResourceType.TaskLists].
8884
*/
89-
suspend fun list(maxResults: Int = 20, pageToken: String? = null): ResourceListResponse<TaskList>
85+
@GET("tasks/v1/users/@me/lists")
86+
suspend fun list(
87+
@Query("maxResults") maxResults: Int = 20,
88+
@Query("pageToken") pageToken: String? = null
89+
): ResourceListResponse<TaskList>
9090

9191
/**
9292
* [Updates the authenticated user's specified task list](https://developers.google.com/tasks/reference/rest/v1/tasklists/patch). This method supports patch semantics.
@@ -96,7 +96,12 @@ interface TaskListsApi {
9696
*
9797
* @return an instance of [TaskList].
9898
*/
99-
suspend fun patch(taskListId: String, taskList: TaskList): TaskList
99+
@Headers("Content-Type: application/json")
100+
@PATCH("tasks/v1/users/@me/lists/{taskListId}")
101+
suspend fun patch(
102+
@Path("taskListId") taskListId: String,
103+
@Body taskList: TaskList
104+
): TaskList
100105

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

192122
/**

0 commit comments

Comments
 (0)