params.kt (5805B)
1 /* 2 * This file is part of LibEuFin. 3 * Copyright (C) 2024-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.http.* 23 import kotlin.math.min 24 import java.util.* 25 26 fun Parameters.expect(name: String): String 27 = get(name) ?: throw badRequest("Missing '$name' parameter", TalerErrorCode.GENERIC_PARAMETER_MISSING) 28 fun Parameters.int(name: String): Int? 29 = get(name)?.run { toIntOrNull() ?: throw paramsMalformed("Param '$name' not a number") } 30 fun Parameters.expectInt(name: String): Int 31 = int(name) ?: throw badRequest("Missing '$name' number parameter", TalerErrorCode.GENERIC_PARAMETER_MISSING) 32 fun Parameters.long(name: String): Long? 33 = get(name)?.run { toLongOrNull() ?: throw paramsMalformed("Param '$name' not a number") } 34 fun Parameters.expectLong(name: String): Long 35 = long(name) ?: throw badRequest("Missing '$name' number parameter", TalerErrorCode.GENERIC_PARAMETER_MISSING) 36 fun Parameters.uuid(name: String): UUID? 37 = get(name)?.run { 38 try { 39 UUID.fromString(this) 40 } catch (e: Exception) { 41 throw paramsMalformed("Param '$name' not an UUID") 42 } 43 } 44 fun Parameters.expectUuid(name: String): UUID 45 = uuid(name) ?: throw badRequest("Missing '$name' UUID parameter", TalerErrorCode.GENERIC_PARAMETER_MISSING) 46 fun Parameters.payto(name: String): Payto? 47 = get(name)?.run { 48 try { 49 Payto.parse(this) 50 } catch (e: Exception) { 51 throw paramsMalformed("Param '$name' not a valid payto") 52 } 53 } 54 fun Parameters.expectPayto(name: String): Payto 55 = payto(name) ?: throw badRequest("Missing '$name' payto parameter", TalerErrorCode.GENERIC_PARAMETER_MISSING) 56 fun Parameters.amount(name: String): TalerAmount? 57 = get(name)?.run { 58 try { 59 TalerAmount(this) 60 } catch (e: Exception) { 61 throw paramsMalformed("Param '$name' not a taler amount") 62 } 63 } 64 65 data class PageParams( 66 val limit: Int, val offset: Long 67 ) { 68 companion object { 69 fun extract(params: Parameters): PageParams { 70 val legacy_limit_value = params.int("delta") 71 val new_limit_value = params.int("limit") 72 if (legacy_limit_value != null && new_limit_value != null && legacy_limit_value != new_limit_value) 73 throw paramsMalformed("Param 'limit' cannot be used with param 'delta'") 74 75 val legacy_offset_value = params.long("start") 76 val new_offset_value = params.long("offset") 77 if (legacy_offset_value != null && new_offset_value != null && legacy_offset_value != new_offset_value) 78 throw paramsMalformed("Param 'offset' cannot be used with param 'start'") 79 80 val limit: Int = new_limit_value ?: legacy_limit_value ?: -20 81 if (limit == 0) throw paramsMalformed("Param 'limit' must be non-zero") 82 else if (limit > MAX_PAGE_SIZE) throw paramsMalformed("Param 'limit' must be <= ${MAX_PAGE_SIZE}") 83 val offset: Long = new_offset_value ?: legacy_offset_value ?: if (limit >= 0) 0L else Long.MAX_VALUE 84 if (offset < 0) throw paramsMalformed("Param 'offset' must be a positive number") 85 86 return PageParams(limit, offset) 87 } 88 } 89 } 90 91 data class TransferParams( 92 val page: PageParams, val status: TransferStatusState? 93 ) { 94 companion object { 95 private val names = TransferStatusState.entries.map { it.name } 96 private val names_fmt = names.joinToString() 97 fun extract(params: Parameters): TransferParams { 98 val status = params["status"]?.let { 99 if (!names.contains(it)) { 100 throw paramsMalformed("Param 'status' must be one of $names_fmt") 101 } 102 TransferStatusState.valueOf(it) 103 } 104 return TransferParams(PageParams.extract(params), status) 105 } 106 } 107 } 108 109 data class PollingParams( 110 val timeout_ms: Long 111 ) { 112 companion object { 113 fun extract(params: Parameters): PollingParams { 114 val legacy_value = params.long("long_poll_ms") 115 val new_value = params.long("timeout_ms") 116 if (legacy_value != null && new_value != null && legacy_value != new_value) 117 throw paramsMalformed("Param 'timeout_ms' cannot be used with param 'long_poll_ms'") 118 val timeout_ms: Long = min(new_value ?: legacy_value ?: 0, MAX_TIMEOUT_MS) 119 if (timeout_ms < 0) throw paramsMalformed("Param 'timeout_ms' must be a positive number") 120 return PollingParams(timeout_ms) 121 } 122 } 123 } 124 125 data class HistoryParams( 126 val page: PageParams, val polling: PollingParams 127 ) { 128 companion object { 129 fun extract(params: Parameters): HistoryParams { 130 return HistoryParams(PageParams.extract(params), PollingParams.extract(params)) 131 } 132 } 133 } 134 135 data class AccountCheckParams( 136 val account: Payto 137 ) { 138 companion object { 139 fun extract(params: Parameters): AccountCheckParams { 140 val account = params.expectPayto("account") 141 return AccountCheckParams(account) 142 } 143 } 144 }