libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

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 }