client.kt (4594B)
1 /* 2 * This file is part of LibEuFin. 3 * Copyright (C) 2023-2025 Taler Systems S.A. 4 5 * LibEuFin is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation; either version 3, or 8 * (at your option) any later version. 9 10 * LibEuFin is distributed in the hope that it will be useful, but 11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General 13 * Public License for more details. 14 15 * You should have received a copy of the GNU Affero General Public 16 * License along with LibEuFin; see the file COPYING. If not, see 17 * <http://www.gnu.org/licenses/> 18 */ 19 20 package tech.libeufin.common 21 22 import io.ktor.client.request.* 23 import io.ktor.client.statement.* 24 import io.ktor.http.* 25 import kotlinx.serialization.json.Json 26 import kotlinx.serialization.json.JsonElement 27 import kotlinx.serialization.json.JsonObject 28 import kotlin.test.assertEquals 29 30 /* ----- Json DSL ----- */ 31 32 inline fun obj(from: JsonObject = JsonObject(emptyMap()), builderAction: JsonBuilder.() -> Unit): JsonObject { 33 val builder = JsonBuilder(from) 34 builder.apply(builderAction) 35 return JsonObject(builder.content) 36 } 37 38 class JsonBuilder(from: JsonObject) { 39 val content: MutableMap<String, JsonElement> = from.toMutableMap() 40 41 inline infix fun <reified T> String.to(v: T) { 42 val json = Json.encodeToJsonElement(kotlinx.serialization.serializer<T>(), v) 43 content[this] = json 44 } 45 } 46 47 /* ----- Json body helper ----- */ 48 49 inline fun <reified B> HttpRequestBuilder.json(b: B) { 50 val json = Json.encodeToString(kotlinx.serialization.serializer<B>(), b) 51 contentType(ContentType.Application.Json) 52 setBody(json) 53 } 54 55 inline fun HttpRequestBuilder.json( 56 from: JsonObject = JsonObject(emptyMap()), 57 builderAction: JsonBuilder.() -> Unit 58 ) { 59 json(obj(from, builderAction)) 60 } 61 62 suspend inline fun <reified B> HttpResponse.json(): B = 63 Json.decodeFromString(kotlinx.serialization.serializer<B>(), bodyAsText()) 64 65 suspend inline fun <reified B> HttpResponse.assertOkJson(lambda: (B) -> Unit = {}): B { 66 assertOk() 67 val body = json<B>() 68 lambda(body) 69 return body 70 } 71 72 suspend inline fun <reified B> HttpResponse.assertAcceptedJson(lambda: (B) -> Unit = {}): B { 73 assertAccepted() 74 val body = json<B>() 75 lambda(body) 76 return body 77 } 78 79 /* ----- Assert ----- */ 80 81 suspend fun HttpResponse.isStatus(status: HttpStatusCode, err: TalerErrorCode?): Boolean { 82 if (status != this.status) return false 83 if (err != null) { 84 val body = json<TalerError>() 85 return err.code == body.code 86 } 87 return true 88 } 89 90 suspend fun HttpResponse.assertStatus(status: HttpStatusCode, err: TalerErrorCode?): HttpResponse { 91 assertEquals(status, this.status, if (err != null) "$err" else err) 92 if (err != null) { 93 val body = json<TalerError>() 94 assertEquals(err.code, body.code) 95 } 96 return this 97 } 98 suspend fun HttpResponse.assertOk(): HttpResponse 99 = assertStatus(HttpStatusCode.OK, null) 100 suspend fun HttpResponse.assertNoContent(err: TalerErrorCode? = null): HttpResponse 101 = assertStatus(HttpStatusCode.NoContent, err) 102 suspend fun HttpResponse.assertAccepted(): HttpResponse 103 = assertStatus(HttpStatusCode.Accepted, null) 104 suspend fun HttpResponse.assertNotFound(err: TalerErrorCode): HttpResponse 105 = assertStatus(HttpStatusCode.NotFound, err) 106 suspend fun HttpResponse.assertUnauthorized(err: TalerErrorCode = TalerErrorCode.GENERIC_UNAUTHORIZED): HttpResponse 107 = assertStatus(HttpStatusCode.Unauthorized, err) 108 suspend fun HttpResponse.assertConflict(err: TalerErrorCode): HttpResponse 109 = assertStatus(HttpStatusCode.Conflict, err) 110 suspend fun HttpResponse.assertBadRequest(err: TalerErrorCode = TalerErrorCode.GENERIC_JSON_INVALID): HttpResponse 111 = assertStatus(HttpStatusCode.BadRequest, err) 112 suspend fun HttpResponse.assertForbidden(err: TalerErrorCode = TalerErrorCode.GENERIC_FORBIDDEN): HttpResponse 113 = assertStatus(HttpStatusCode.Forbidden, err) 114 suspend fun HttpResponse.assertNotImplemented(err: TalerErrorCode = TalerErrorCode.END): HttpResponse 115 = assertStatus(HttpStatusCode.NotImplemented, err) 116 suspend fun HttpResponse.assertTooManyRequests(err: TalerErrorCode): HttpResponse 117 = assertStatus(HttpStatusCode.TooManyRequests, err) 118 suspend fun HttpResponse.assertPayloadTooLarge( 119 err: TalerErrorCode = TalerErrorCode.GENERIC_UPLOAD_EXCEEDS_LIMIT 120 ): HttpResponse = assertStatus(HttpStatusCode.PayloadTooLarge, err)