client.kt (4920B)
1 /* 2 * This file is part of LibEuFin. 3 * Copyright (C) 2023-2025, 2026 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 kotlinx.serialization.json.encodeToJsonElement 29 import kotlinx.serialization.json.jsonObject 30 import kotlin.test.assertEquals 31 32 /* ----- Json DSL ----- */ 33 34 inline fun obj(from: JsonObject = JsonObject(emptyMap()), builderAction: JsonBuilder.() -> Unit): JsonObject { 35 val builder = JsonBuilder(from) 36 builder.apply(builderAction) 37 return JsonObject(builder.content) 38 } 39 40 class JsonBuilder(from: JsonObject) { 41 val content: MutableMap<String, JsonElement> = from.toMutableMap() 42 43 inline infix fun <reified T> String.to(v: T) { 44 val json = Json.encodeToJsonElement(kotlinx.serialization.serializer<T>(), v) 45 content[this] = json 46 } 47 } 48 49 /* ----- Json body helper ----- */ 50 51 inline fun <reified B> HttpRequestBuilder.json(b: B) { 52 val json = Json.encodeToString(kotlinx.serialization.serializer<B>(), b) 53 contentType(ContentType.Application.Json) 54 setBody(json) 55 } 56 57 inline fun <reified B> HttpRequestBuilder.json( 58 from: B, 59 builderAction: JsonBuilder.() -> Unit 60 ) { 61 json(obj(Json.encodeToJsonElement(kotlinx.serialization.serializer<B>(), from).jsonObject, builderAction)) 62 } 63 64 inline fun HttpRequestBuilder.json( 65 from: JsonObject = JsonObject(emptyMap()), 66 builderAction: JsonBuilder.() -> Unit 67 ) { 68 json(obj(from, builderAction)) 69 } 70 71 suspend inline fun <reified B> HttpResponse.json(): B = 72 Json.decodeFromString(kotlinx.serialization.serializer<B>(), bodyAsText()) 73 74 suspend inline fun <reified B> HttpResponse.assertOkJson(lambda: (B) -> Unit = {}): B { 75 assertOk() 76 val body = json<B>() 77 lambda(body) 78 return body 79 } 80 81 suspend inline fun <reified B> HttpResponse.assertAcceptedJson(lambda: (B) -> Unit = {}): B { 82 assertAccepted() 83 val body = json<B>() 84 lambda(body) 85 return body 86 } 87 88 /* ----- Assert ----- */ 89 90 suspend fun HttpResponse.isStatus(status: HttpStatusCode, err: TalerErrorCode?): Boolean { 91 if (status != this.status) return false 92 if (err != null) { 93 val body = json<TalerError>() 94 return err.code == body.code 95 } 96 return true 97 } 98 99 suspend fun HttpResponse.assertStatus(status: HttpStatusCode, err: TalerErrorCode?): HttpResponse { 100 assertEquals(status, this.status, if (err != null) "$err" else err) 101 if (err != null) { 102 val body = json<TalerError>() 103 assertEquals(err.code, body.code) 104 } 105 return this 106 } 107 suspend fun HttpResponse.assertOk(): HttpResponse 108 = assertStatus(HttpStatusCode.OK, null) 109 suspend fun HttpResponse.assertNoContent(err: TalerErrorCode? = null): HttpResponse 110 = assertStatus(HttpStatusCode.NoContent, err) 111 suspend fun HttpResponse.assertAccepted(): HttpResponse 112 = assertStatus(HttpStatusCode.Accepted, null) 113 suspend fun HttpResponse.assertNotFound(err: TalerErrorCode): HttpResponse 114 = assertStatus(HttpStatusCode.NotFound, err) 115 suspend fun HttpResponse.assertUnauthorized(err: TalerErrorCode = TalerErrorCode.GENERIC_UNAUTHORIZED): HttpResponse 116 = assertStatus(HttpStatusCode.Unauthorized, err) 117 suspend fun HttpResponse.assertConflict(err: TalerErrorCode): HttpResponse 118 = assertStatus(HttpStatusCode.Conflict, err) 119 suspend fun HttpResponse.assertBadRequest(err: TalerErrorCode = TalerErrorCode.GENERIC_JSON_INVALID): HttpResponse 120 = assertStatus(HttpStatusCode.BadRequest, err) 121 suspend fun HttpResponse.assertForbidden(err: TalerErrorCode = TalerErrorCode.GENERIC_FORBIDDEN): HttpResponse 122 = assertStatus(HttpStatusCode.Forbidden, err) 123 suspend fun HttpResponse.assertNotImplemented(err: TalerErrorCode = TalerErrorCode.END): HttpResponse 124 = assertStatus(HttpStatusCode.NotImplemented, err) 125 suspend fun HttpResponse.assertTooManyRequests(err: TalerErrorCode): HttpResponse 126 = assertStatus(HttpStatusCode.TooManyRequests, err) 127 suspend fun HttpResponse.assertPayloadTooLarge( 128 err: TalerErrorCode = TalerErrorCode.GENERIC_UPLOAD_EXCEEDS_LIMIT 129 ): HttpResponse = assertStatus(HttpStatusCode.PayloadTooLarge, err)