gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libeufin] branch master updated: libeufin-bank: implement main()


From: gnunet
Subject: [libeufin] branch master updated: libeufin-bank: implement main()
Date: Fri, 22 Sep 2023 11:29:45 +0200

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository libeufin.

The following commit(s) were added to refs/heads/master by this push:
     new f52e5964 libeufin-bank: implement main()
f52e5964 is described below

commit f52e5964270a2509323c78803317dffe9dde3bc4
Author: Florian Dold <florian@dold.me>
AuthorDate: Fri Sep 22 11:29:35 2023 +0200

    libeufin-bank: implement main()
---
 bank/src/main/kotlin/tech/libeufin/bank/Main.kt    | 76 +++++++++++++++++++---
 .../tech/libeufin/bank/accountsMgmtHandlers.kt     |  4 +-
 .../kotlin/tech/libeufin/bank/talerWebHandlers.kt  |  8 +--
 .../tech/libeufin/bank/talerWireGatewayHandlers.kt |  6 +-
 .../kotlin/tech/libeufin/bank/tokenHandlers.kt     |  2 +-
 .../tech/libeufin/bank/transactionsHandlers.kt     |  6 +-
 bank/src/test/kotlin/LibeuFinApiTest.kt            | 20 ++++--
 bank/src/test/kotlin/TalerApiTest.kt               | 33 +++++++---
 util/src/main/kotlin/TalerConfig.kt                |  8 +++
 9 files changed, 126 insertions(+), 37 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
index 8183fefe..696b397f 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -20,12 +20,24 @@
 
 package tech.libeufin.bank
 
+import TalerConfig
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.context
+import com.github.ajalt.clikt.core.subcommands
+import com.github.ajalt.clikt.output.CliktHelpFormatter
+import com.github.ajalt.clikt.parameters.options.default
+import com.github.ajalt.clikt.parameters.options.flag
+import com.github.ajalt.clikt.parameters.options.option
+import com.github.ajalt.clikt.parameters.options.versionOption
+import com.github.ajalt.clikt.parameters.types.int
 import io.ktor.http.*
 import io.ktor.server.application.*
 import io.ktor.server.plugins.*
 import io.ktor.server.plugins.requestvalidation.*
 import io.ktor.server.plugins.contentnegotiation.*
 import io.ktor.serialization.kotlinx.json.*
+import io.ktor.server.engine.*
+import io.ktor.server.netty.*
 import io.ktor.server.plugins.callloging.*
 import kotlinx.serialization.*
 import io.ktor.server.plugins.cors.routing.*
@@ -33,6 +45,9 @@ import io.ktor.server.plugins.statuspages.*
 import io.ktor.server.request.*
 import io.ktor.server.response.*
 import io.ktor.server.routing.*
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
 import kotlinx.serialization.descriptors.*
 import kotlinx.serialization.encoding.Decoder
 import kotlinx.serialization.encoding.Encoder
@@ -44,10 +59,10 @@ import org.slf4j.LoggerFactory
 import org.slf4j.event.Level
 import tech.libeufin.util.*
 import java.time.Duration
+import kotlin.system.exitProcess
 
 // GLOBALS
 private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.bank.Main")
-private val db = Database(System.getProperty("BANK_DB_CONNECTION_STRING"))
 const val GENERIC_UNDEFINED = -1 // Filler for ECs that don't exist yet.
 val TOKEN_DEFAULT_DURATION_US = Duration.ofDays(1L).seconds * 1000000
 
@@ -62,6 +77,7 @@ object RelativeTimeSerializer : KSerializer<RelativeTime> {
     override fun serialize(encoder: Encoder, value: RelativeTime) {
         throw internalServerError("Encoding of RelativeTime not implemented.") 
// API doesn't require this.
     }
+
     override fun deserialize(decoder: Decoder): RelativeTime {
         val jsonInput = decoder as? JsonDecoder ?: throw 
internalServerError("RelativeTime had no JsonDecoder")
         val json = try {
@@ -77,7 +93,8 @@ object RelativeTimeSerializer : KSerializer<RelativeTime> {
             if (maybeDUs.content != "forever") throw badRequest("Only 
'forever' allowed for d_us as string, but '${maybeDUs.content}' was found")
             return RelativeTime(d_us = Long.MAX_VALUE)
         }
-        val dUs: Long = maybeDUs.longOrNull ?: throw badRequest("Could not 
convert d_us: '${maybeDUs.content}' to a number")
+        val dUs: Long =
+            maybeDUs.longOrNull ?: throw badRequest("Could not convert d_us: 
'${maybeDUs.content}' to a number")
         return RelativeTime(d_us = dUs)
     }
 
@@ -91,9 +108,11 @@ object TalerAmountSerializer : KSerializer<TalerAmount> {
 
     override val descriptor: SerialDescriptor =
         PrimitiveSerialDescriptor("TalerAmount", PrimitiveKind.STRING)
+
     override fun serialize(encoder: Encoder, value: TalerAmount) {
         throw internalServerError("Encoding of TalerAmount not implemented.") 
// API doesn't require this.
     }
+
     override fun deserialize(decoder: Decoder): TalerAmount {
         val maybeAmount = try {
             decoder.decodeString()
@@ -116,7 +135,7 @@ object TalerAmountSerializer : KSerializer<TalerAmount> {
  *
  * Returns the authenticated customer, or null if they failed.
  */
-fun ApplicationCall.myAuth(requiredScope: TokenScope): Customer? {
+fun ApplicationCall.myAuth(db: Database, requiredScope: TokenScope): Customer? 
{
     // Extracting the Authorization header.
     val header = getAuthorizationRawHeader(this.request) ?: throw badRequest(
         "Authorization header not found.",
@@ -139,7 +158,11 @@ fun ApplicationCall.myAuth(requiredScope: TokenScope): 
Customer? {
     }
 }
 
-val webApp: Application.() -> Unit = {
+
+/**
+ * Set up web server handlers for the Taler corebank API.
+ */
+fun Application.corebankWebApp(db: Database) {
     install(CallLogging) {
         this.level = Level.DEBUG
         this.logger = tech.libeufin.bank.logger
@@ -181,7 +204,7 @@ val webApp: Application.() -> Unit = {
          * (Ktor native) type doesn't easily map to the Taler error
          * format.
          */
-        exception<BadRequestException> {call, cause ->
+        exception<BadRequestException> { call, cause ->
             /**
              * NOTE: extracting the root cause helps with JSON error messages,
              * because they mention the particular way they are invalid, but 
OTOH
@@ -198,11 +221,13 @@ val webApp: Application.() -> Unit = {
             val errorMessage: String? = rootCause?.message ?: cause.message
             logger.error(errorMessage)
             // Telling apart invalid JSON vs missing parameter vs invalid 
parameter.
-            val talerErrorCode = when(cause) {
+            val talerErrorCode = when (cause) {
                 is MissingRequestParameterException ->
                     TalerErrorCode.TALER_EC_GENERIC_PARAMETER_MISSING
+
                 is ParameterConversionException ->
                     TalerErrorCode.TALER_EC_GENERIC_PARAMETER_MALFORMED
+
                 else -> TalerErrorCode.TALER_EC_GENERIC_JSON_INVALID
             }
             call.respond(
@@ -210,7 +235,8 @@ val webApp: Application.() -> Unit = {
                 message = TalerError(
                     code = talerErrorCode.code,
                     hint = errorMessage
-                ))
+                )
+            )
         }
         /**
          * This branch triggers when a bank handler throws it, and namely
@@ -218,7 +244,7 @@ val webApp: Application.() -> Unit = {
          * should be preferred to catch errors, as it allows to include the
          * Taler specific error detail.
          */
-        exception<LibeufinBankException> {call, cause ->
+        exception<LibeufinBankException> { call, cause ->
             logger.error(cause.talerError.hint)
             call.respond(
                 status = cause.httpStatus,
@@ -226,7 +252,7 @@ val webApp: Application.() -> Unit = {
             )
         }
         // Catch-all branch to mean that the bank wasn't able to manage one 
error.
-        exception<Exception> {call, cause ->
+        exception<Exception> { call, cause ->
             cause.printStackTrace()
             logger.error(cause.message)
             call.respond(
@@ -250,4 +276,34 @@ val webApp: Application.() -> Unit = {
         this.talerIntegrationHandlers(db)
         this.talerWireGatewayHandlers(db)
     }
-}
\ No newline at end of file
+}
+
+class LibeufinBankCommand : CliktCommand() {
+    init {
+        versionOption(getVersion())
+    }
+
+    override fun run() = Unit
+}
+
+class ServeBank : CliktCommand("Run libeufin-bank HTTP server", name = 
"serve") {
+    init {
+        context {
+            helpFormatter = CliktHelpFormatter(showDefaultValues = true)
+        }
+    }
+
+    override fun run() {
+        val config = TalerConfig.load()
+        val dbConnStr = config.requireValueString("libeufin-bank-db-postgres", 
"config")
+        logger.info("using database '$dbConnStr'")
+        val db = Database(dbConnStr)
+        embeddedServer(Netty, port = 8080) {
+            corebankWebApp(db)
+        }.start(wait = true)
+    }
+}
+
+fun main(args: Array<String>) {
+    LibeufinBankCommand().subcommands(ServeBank()).main(args)
+}
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/accountsMgmtHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/accountsMgmtHandlers.kt
index 77394248..f6b225f5 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/accountsMgmtHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/accountsMgmtHandlers.kt
@@ -23,7 +23,7 @@ fun Routing.accountsMgmtHandlers(db: Database) {
         // check if only admin.
         val maybeOnlyAdmin = db.configGet("only_admin_registrations")
         if (maybeOnlyAdmin?.lowercase() == "yes") {
-            val customer: Customer? = call.myAuth(TokenScope.readwrite)
+            val customer: Customer? = call.myAuth(db, TokenScope.readwrite)
             if (customer == null || customer.login != "admin")
                 throw LibeufinBankException(
                     httpStatus = HttpStatusCode.Unauthorized,
@@ -110,7 +110,7 @@ fun Routing.accountsMgmtHandlers(db: Database) {
         return@post
     }
     get("/accounts/{USERNAME}") {
-        val c = call.myAuth(TokenScope.readonly) ?: throw unauthorized("Login 
failed")
+        val c = call.myAuth(db, TokenScope.readonly) ?: throw 
unauthorized("Login failed")
         val resourceName = call.maybeUriComponent("USERNAME") ?: throw 
badRequest(
             hint = "No username found in the URI",
             talerErrorCode = TalerErrorCode.TALER_EC_GENERIC_PARAMETER_MISSING
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/talerWebHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/talerWebHandlers.kt
index 83d6adb3..258bc005 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/talerWebHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/talerWebHandlers.kt
@@ -38,7 +38,7 @@ import java.util.*
 
 fun Routing.talerWebHandlers(db: Database) {
     post("/accounts/{USERNAME}/withdrawals") {
-        val c = call.myAuth(TokenScope.readwrite) ?: throw unauthorized()
+        val c = call.myAuth(db, TokenScope.readwrite) ?: throw unauthorized()
         // Admin not allowed to withdraw in the name of customers:
         val accountName = call.expectUriComponent("USERNAME")
         if (c.login != accountName)
@@ -79,7 +79,7 @@ fun Routing.talerWebHandlers(db: Database) {
         return@post
     }
     get("/accounts/{USERNAME}/withdrawals/{withdrawal_id}") {
-        val c = call.myAuth(TokenScope.readonly) ?: throw unauthorized()
+        val c = call.myAuth(db, TokenScope.readonly) ?: throw unauthorized()
         val accountName = call.expectUriComponent("USERNAME")
         // Admin allowed to see the details
         if (c.login != accountName && c.login != "admin") throw forbidden()
@@ -96,7 +96,7 @@ fun Routing.talerWebHandlers(db: Database) {
         return@get
     }
     post("/accounts/{USERNAME}/withdrawals/{withdrawal_id}/abort") {
-        val c = call.myAuth(TokenScope.readonly) ?: throw unauthorized()
+        val c = call.myAuth(db, TokenScope.readonly) ?: throw unauthorized()
         // Admin allowed to abort.
         if (!call.getResourceName("USERNAME").canI(c)) throw forbidden()
         val op = getWithdrawal(db, call.expectUriComponent("withdrawal_id"))
@@ -114,7 +114,7 @@ fun Routing.talerWebHandlers(db: Database) {
         return@post
     }
     post("/accounts/{USERNAME}/withdrawals/{withdrawal_id}/confirm") {
-        val c = call.myAuth(TokenScope.readwrite) ?: throw unauthorized()
+        val c = call.myAuth(db, TokenScope.readwrite) ?: throw unauthorized()
         // No admin allowed.
         if(!call.getResourceName("USERNAME").canI(c, withAdmin = false)) throw 
forbidden()
         val op = getWithdrawal(db, call.expectUriComponent("withdrawal_id"))
diff --git 
a/bank/src/main/kotlin/tech/libeufin/bank/talerWireGatewayHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/talerWireGatewayHandlers.kt
index b223310d..85d2ca54 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/talerWireGatewayHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/talerWireGatewayHandlers.kt
@@ -37,7 +37,7 @@ fun Routing.talerWireGatewayHandlers(db: Database) {
         return@get
     }
     get("/accounts/{USERNAME}/taler-wire-gateway/history/incoming") {
-        val c = call.myAuth(TokenScope.readonly) ?: throw unauthorized()
+        val c = call.myAuth(db, TokenScope.readonly) ?: throw unauthorized()
         if (!call.getResourceName("USERNAME").canI(c, withAdmin = true)) throw 
forbidden()
         val params = getHistoryParams(call.request)
         val bankAccount = db.bankAccountGetFromOwnerId(c.expectRowId())
@@ -69,7 +69,7 @@ fun Routing.talerWireGatewayHandlers(db: Database) {
         return@get
     }
     post("/accounts/{USERNAME}/taler-wire-gateway/transfer") {
-        val c = call.myAuth(TokenScope.readwrite) ?: throw unauthorized()
+        val c = call.myAuth(db, TokenScope.readwrite) ?: throw unauthorized()
         if (!call.getResourceName("USERNAME").canI(c, withAdmin = false)) 
throw forbidden()
         val req = call.receive<TransferRequest>()
         // Checking for idempotency.
@@ -124,7 +124,7 @@ fun Routing.talerWireGatewayHandlers(db: Database) {
         return@post
     }
     post("/accounts/{USERNAME}/taler-wire-gateway/admin/add-incoming") {
-        val c = call.myAuth(TokenScope.readwrite) ?: throw unauthorized()
+        val c = call.myAuth(db, TokenScope.readwrite) ?: throw unauthorized()
         if (!call.getResourceName("USERNAME").canI(c, withAdmin = false)) 
throw forbidden()
         val req = call.receive<AddIncomingRequest>()
         val amount = parseTalerAmount(req.amount)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/tokenHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/tokenHandlers.kt
index 738adad9..98166116 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/tokenHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/tokenHandlers.kt
@@ -37,7 +37,7 @@ fun Routing.tokenHandlers(db: Database) {
         throw internalServerError("Token deletion not implemented.")
     }
     post("/accounts/{USERNAME}/token") {
-        val customer = call.myAuth(TokenScope.refreshable) ?: throw 
unauthorized("Authentication failed")
+        val customer = call.myAuth(db, TokenScope.refreshable) ?: throw 
unauthorized("Authentication failed")
         val endpointOwner = call.maybeUriComponent("USERNAME")
         if (customer.login != endpointOwner)
             throw forbidden(
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/transactionsHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/transactionsHandlers.kt
index dd1a2a1e..c6f67a61 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/transactionsHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/transactionsHandlers.kt
@@ -18,7 +18,7 @@ private val logger: Logger = 
LoggerFactory.getLogger("tech.libeufin.bank.transac
 
 fun Routing.transactionsHandlers(db: Database) {
     get("/accounts/{USERNAME}/transactions") {
-        val c = call.myAuth(TokenScope.readonly) ?: throw unauthorized()
+        val c = call.myAuth(db, TokenScope.readonly) ?: throw unauthorized()
         val resourceName = call.expectUriComponent("USERNAME")
         if (c.login != resourceName && c.login != "admin") throw forbidden()
         // Collecting params.
@@ -51,7 +51,7 @@ fun Routing.transactionsHandlers(db: Database) {
     }
     // Creates a bank transaction.
     post("/accounts/{USERNAME}/transactions") {
-        val c = call.myAuth(TokenScope.readwrite) ?: throw unauthorized()
+        val c = call.myAuth(db, TokenScope.readwrite) ?: throw unauthorized()
         val resourceName = call.expectUriComponent("USERNAME")
         // admin has no rights here.
         if ((c.login != resourceName) && (call.getAuthToken() == null))
@@ -98,7 +98,7 @@ fun Routing.transactionsHandlers(db: Database) {
         return@post
     }
     get("/accounts/{USERNAME}/transactions/{T_ID}") {
-        val c = call.myAuth(TokenScope.readonly) ?: throw unauthorized()
+        val c = call.myAuth(db, TokenScope.readonly) ?: throw unauthorized()
         val accountOwner = call.expectUriComponent("USERNAME")
         // auth ok, check rights.
         if (c.login != "admin" && c.login != accountOwner)
diff --git a/bank/src/test/kotlin/LibeuFinApiTest.kt 
b/bank/src/test/kotlin/LibeuFinApiTest.kt
index 9403c840..23256bd8 100644
--- a/bank/src/test/kotlin/LibeuFinApiTest.kt
+++ b/bank/src/test/kotlin/LibeuFinApiTest.kt
@@ -54,7 +54,9 @@ class LibeuFinApiTest {
         assert(db.bankAccountCreate(genBankAccount(barId!!)))
         for (i in 1..10) { db.bankTransactionCreate(genTx("test-$i")) }
         testApplication {
-            application(webApp)
+            application {
+                corebankWebApp(db)
+            }
             val asc = client.get("/accounts/foo/transactions?delta=2") {
                 basicAuth("foo", "pw")
                 expectSuccess = true
@@ -84,7 +86,9 @@ class LibeuFinApiTest {
         assert(db.bankAccountCreate(genBankAccount(barId!!)))
         // accounts exist, now create one transaction.
         testApplication {
-            application(webApp)
+            application {
+                corebankWebApp(db)
+            }
             client.post("/accounts/foo/transactions") {
                 expectSuccess = true
                 basicAuth("foo", "pw")
@@ -111,7 +115,9 @@ class LibeuFinApiTest {
         val db = initDb()
         assert(db.customerCreate(customerFoo) != null)
         testApplication {
-            application(webApp)
+            application {
+                corebankWebApp(db)
+            }
             client.post("/accounts/foo/token") {
                 expectSuccess = true
                 contentType(ContentType.Application.Json)
@@ -170,7 +176,9 @@ class LibeuFinApiTest {
             )
         ))
         testApplication {
-            application(webApp)
+            application {
+                corebankWebApp(db)
+            }
             val r = client.get("/accounts/foo") {
                 expectSuccess = true
                 basicAuth("foo", "pw")
@@ -212,7 +220,9 @@ class LibeuFinApiTest {
             val ibanPayto = genIbanPaytoUri()
             // Bank needs those to operate:
             db.configSet("max_debt_ordinary_customers", "KUDOS:11")
-            application(webApp)
+            application {
+                corebankWebApp(db)
+            }
             var resp = client.post("/accounts") {
                 expectSuccess = false
                 contentType(ContentType.Application.Json)
diff --git a/bank/src/test/kotlin/TalerApiTest.kt 
b/bank/src/test/kotlin/TalerApiTest.kt
index 8f88eea6..2f6f3e0a 100644
--- a/bank/src/test/kotlin/TalerApiTest.kt
+++ b/bank/src/test/kotlin/TalerApiTest.kt
@@ -4,7 +4,6 @@ import io.ktor.client.statement.*
 import io.ktor.http.*
 import io.ktor.server.testing.*
 import kotlinx.serialization.json.Json
-import org.junit.Ignore
 import org.junit.Test
 import tech.libeufin.bank.*
 import tech.libeufin.util.CryptoUtil
@@ -60,7 +59,9 @@ class TalerApiTest {
         ))
         // Do POST /transfer.
         testApplication {
-            application(webApp)
+            application {
+                corebankWebApp(db)
+            }
             val req = """
                     {
                       "request_uid": "entropic 0",
@@ -143,7 +144,9 @@ class TalerApiTest {
             )
         // Bar expects two entries in the incoming history
         testApplication {
-            application(webApp)
+            application {
+                corebankWebApp(db)
+            }
             val resp = 
client.get("/accounts/bar/taler-wire-gateway/history/incoming?delta=5") {
                 basicAuth("bar", "secret")
                 expectSuccess = true
@@ -167,7 +170,9 @@ class TalerApiTest {
             TalerAmount(1000, 0, "KUDOS")
         ))
         testApplication {
-            application(webApp)
+            application {
+                corebankWebApp(db)
+            }
             client.post("/accounts/foo/taler-wire-gateway/admin/add-incoming") 
{
                 expectSuccess = true
                 contentType(ContentType.Application.Json)
@@ -199,7 +204,9 @@ class TalerApiTest {
             amount = TalerAmount(1, 0, "KUDOS")
         ))
         testApplication {
-            application(webApp)
+            application {
+                corebankWebApp(db)
+            }
             val r = 
client.post("/taler-integration/withdrawal-operation/${uuid}") {
                 expectSuccess = true
                 contentType(ContentType.Application.Json)
@@ -229,7 +236,9 @@ class TalerApiTest {
             amount = TalerAmount(1, 0, "KUDOS")
         ))
         testApplication {
-            application(webApp)
+            application {
+                corebankWebApp(db)
+            }
             val r = 
client.get("/taler-integration/withdrawal-operation/${uuid}") {
                 expectSuccess = true
             }
@@ -252,7 +261,9 @@ class TalerApiTest {
         val op = db.talerWithdrawalGet(uuid)
         assert(op?.aborted == false)
         testApplication {
-            application(webApp)
+            application {
+                corebankWebApp(db)
+            }
             client.post("/accounts/foo/withdrawals/${uuid}/abort") {
                 expectSuccess = true
                 basicAuth("foo", "pw")
@@ -268,7 +279,9 @@ class TalerApiTest {
         assert(db.customerCreate(customerFoo) != null)
         assert(db.bankAccountCreate(bankAccountFoo))
         testApplication {
-            application(webApp)
+            application {
+                corebankWebApp(db)
+            }
             // Creating the withdrawal as if the SPA did it.
             val r = client.post("/accounts/foo/withdrawals") {
                 basicAuth("foo", "pw")
@@ -312,7 +325,9 @@ class TalerApiTest {
 
         // Starting the bank and POSTing as Foo to /confirm the operation.
         testApplication {
-            application(webApp)
+            application {
+                corebankWebApp(db)
+            }
             client.post("/accounts/foo/withdrawals/${uuid}/confirm") {
                 expectSuccess = true // Sufficient to assert on success.
                 basicAuth("foo", "pw")
diff --git a/util/src/main/kotlin/TalerConfig.kt 
b/util/src/main/kotlin/TalerConfig.kt
index 83ca40a0..e74e9584 100644
--- a/util/src/main/kotlin/TalerConfig.kt
+++ b/util/src/main/kotlin/TalerConfig.kt
@@ -120,6 +120,14 @@ class TalerConfig {
         return Optional.ofNullable(lookupEntry(section, option)?.value)
     }
 
+    fun requireValueString(section: String, option: String): String {
+        val entry = lookupEntry(section, option)
+        if (entry == null) {
+            throw TalerConfigError("expected string in configuration section 
$section option $option")
+        }
+        return entry.value
+    }
+
     /**
      * Create a string representation of the loaded configuration.
      */

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]