commit 3052c91ead4fba54f45849770586b8c9c5149af4
parent bc3dc29d4f84cd1cebccbce5e851f41061f04cbc
Author: Antoine A <>
Date: Wed, 29 Nov 2023 16:07:53 +0000
Improve CLI exception handling
Diffstat:
3 files changed, 58 insertions(+), 91 deletions(-)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt
@@ -76,29 +76,28 @@ sealed class ServerConfig {
data class Tcp(val port: Int): ServerConfig()
}
-fun talerConfig(configPath: String?): TalerConfig = catchError {
+fun talerConfig(configPath: String?): TalerConfig {
val config = TalerConfig(BANK_CONFIG_SOURCE)
config.load(configPath)
- config
+ return config
}
-fun TalerConfig.loadDbConfig(): DatabaseConfig = catchError {
- DatabaseConfig(
+fun TalerConfig.loadDbConfig(): DatabaseConfig {
+ return DatabaseConfig(
dbConnStr = requireString("libeufin-bankdb-postgres", "config"),
sqlDir = requirePath("libeufin-bankdb-postgres", "sql_dir")
)
}
-fun TalerConfig.loadServerConfig(): ServerConfig = catchError {
- val method = requireString("libeufin-bank", "serve")
- when (method) {
+fun TalerConfig.loadServerConfig(): ServerConfig {
+ return when (val method = requireString("libeufin-bank", "serve")) {
"tcp" -> ServerConfig.Tcp(requireNumber("libeufin-bank", "port"))
"unix" -> ServerConfig.Unix(requireString("libeufin-bank", "unixpath"), requireNumber("libeufin-bank", "unixpath_mode"))
else -> throw Exception("Unknown server method '$method' expected 'tcp' or 'unix'")
}
}
-fun TalerConfig.loadBankConfig(): BankConfig = catchError {
+fun TalerConfig.loadBankConfig(): BankConfig {
val regionalCurrency = requireString("libeufin-bank", "currency")
var fiatCurrency: String? = null;
var fiatCurrencySpec: CurrencySpecification? = null
@@ -107,7 +106,7 @@ fun TalerConfig.loadBankConfig(): BankConfig = catchError {
fiatCurrency = requireString("libeufin-bank", "fiat_currency");
fiatCurrencySpec = currencySpecificationFor(fiatCurrency)
}
- BankConfig(
+ return BankConfig(
regionalCurrency = regionalCurrency,
regionalCurrencySpec = currencySpecificationFor(regionalCurrency),
allowRegistration = lookupBoolean("libeufin-bank", "allow_registration") ?: false,
@@ -128,14 +127,13 @@ fun TalerConfig.loadBankConfig(): BankConfig = catchError {
fun String.notEmptyOrNull(): String? = if (isEmpty()) null else this
-fun TalerConfig.currencySpecificationFor(currency: String): CurrencySpecification = catchError {
- sections.find {
+fun TalerConfig.currencySpecificationFor(currency: String): CurrencySpecification
+ = sections.find {
it.startsWith("CURRENCY-") && requireBoolean(it, "enabled") && requireString(it, "code") == currency
}?.let { loadCurrencySpecification(it) } ?: throw TalerConfigError("missing currency specification for $currency")
-}
-private fun TalerConfig.loadCurrencySpecification(section: String): CurrencySpecification = catchError {
- CurrencySpecification(
+private fun TalerConfig.loadCurrencySpecification(section: String): CurrencySpecification {
+ return CurrencySpecification(
name = requireString(section, "name"),
num_fractional_input_digits = requireNumber(section, "fractional_input_digits"),
num_fractional_normal_digits = requireNumber(section, "fractional_normal_digits"),
@@ -144,8 +142,8 @@ private fun TalerConfig.loadCurrencySpecification(section: String): CurrencySpec
)
}
-private fun TalerConfig.amount(section: String, option: String, currency: String): TalerAmount? = catchError {
- val amountStr = lookupString(section, option) ?: return@catchError null
+private fun TalerConfig.amount(section: String, option: String, currency: String): TalerAmount? {
+ val amountStr = lookupString(section, option) ?: return null
val amount = try {
TalerAmount(amountStr)
} catch (e: Exception) {
@@ -157,42 +155,31 @@ private fun TalerConfig.amount(section: String, option: String, currency: String
"expected amount for section $section, option $option, but currency is wrong (got ${amount.currency} expected $currency"
)
}
- amount
+ return amount
}
-private fun TalerConfig.requireAmount(section: String, option: String, currency: String): TalerAmount = catchError {
+private fun TalerConfig.requireAmount(section: String, option: String, currency: String): TalerAmount =
amount(section, option, currency) ?:
throw TalerConfigError("expected amount for section $section, option $option, but config value is empty")
-}
-private fun TalerConfig.decimalNumber(section: String, option: String): DecimalNumber? = catchError {
- val numberStr = lookupString(section, option) ?: return@catchError null
+private fun TalerConfig.decimalNumber(section: String, option: String): DecimalNumber? {
+ val numberStr = lookupString(section, option) ?: return null
try {
- DecimalNumber(numberStr)
+ return DecimalNumber(numberStr)
} catch (e: Exception) {
throw TalerConfigError("expected decimal number for section $section, option $option, but number is malformed")
}
}
-private fun TalerConfig.requireDecimalNumber(section: String, option: String): DecimalNumber = catchError {
- decimalNumber(section, option) ?:
+private fun TalerConfig.requireDecimalNumber(section: String, option: String): DecimalNumber
+ = decimalNumber(section, option) ?:
throw TalerConfigError("expected decimal number for section $section, option $option, but config value is empty")
-}
-private fun TalerConfig.RoundingMode(section: String, option: String): RoundingMode? = catchError {
- val str = lookupString(section, option) ?: return@catchError null;
+private fun TalerConfig.RoundingMode(section: String, option: String): RoundingMode? {
+ val str = lookupString(section, option) ?: return null;
try {
- RoundingMode.valueOf(str)
+ return RoundingMode.valueOf(str)
} catch (e: Exception) {
throw TalerConfigError("expected rouding mode for section $section, option $option, but $str is unknown")
}
-}
-
-private fun <R> catchError(lambda: () -> R): R {
- try {
- return lambda()
- } catch (e: TalerConfigError) {
- logger.error(e.message)
- kotlin.system.exitProcess(1)
- }
-}
+}
+\ No newline at end of file
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -44,7 +44,6 @@ import java.util.zip.DataFormatException
import java.util.zip.Inflater
import java.sql.SQLException
import java.io.File
-import kotlin.system.exitProcess
import kotlinx.coroutines.*
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.*
@@ -228,7 +227,7 @@ class BankDbInit : CliktCommand("Initialize the libeufin-bank database", name =
help = "reset database (DANGEROUS: All existing data is lost)"
).flag()
- override fun run() {
+ override fun run() = cliCmd(logger){
val config = talerConfig(configFile)
val cfg = config.loadDbConfig()
if (requestReset) {
@@ -243,13 +242,10 @@ class BankDbInit : CliktCommand("Initialize the libeufin-bank database", name =
when (res) {
AccountCreationResult.BonusBalanceInsufficient -> {}
AccountCreationResult.LoginReuse -> {}
- AccountCreationResult.PayToReuse -> {
- logger.error("Failed to create admin's account")
- exitProcess(1)
- }
- AccountCreationResult.Success -> {
+ AccountCreationResult.PayToReuse ->
+ throw Exception("Failed to create admin's account")
+ AccountCreationResult.Success ->
logger.info("Admin's account created")
- }
}
}
}
@@ -261,7 +257,7 @@ class ServeBank : CliktCommand("Run libeufin-bank HTTP server", name = "serve")
help = "set the configuration file"
)
- override fun run() {
+ override fun run() = cliCmd(logger) {
val cfg = talerConfig(configFile)
val ctx = cfg.loadBankConfig()
val dbCfg = cfg.loadDbConfig()
@@ -272,25 +268,21 @@ class ServeBank : CliktCommand("Run libeufin-bank HTTP server", name = "serve")
logger.info("Ensure exchange account exists")
val info = db.account.bankInfo("exchange")
if (info == null) {
- logger.error("Exchange account missing: an exchange account named 'exchange' is required for conversion to be enabled")
- exitProcess(1)
+ throw Exception("Exchange account missing: an exchange account named 'exchange' is required for conversion to be enabled")
} else if (!info.isTalerExchange) {
- logger.error("Account is not an exchange: an exchange account named 'exchange' is required for conversion to be enabled")
- exitProcess(1)
+ throw Exception("Account is not an exchange: an exchange account named 'exchange' is required for conversion to be enabled")
}
logger.info("Ensure conversion is enabled")
val sqlProcedures = File("${dbCfg.sqlDir}/libeufin-conversion-setup.sql")
if (!sqlProcedures.exists()) {
- logger.error("Missing libeufin-conversion-setup.sql file")
- exitProcess(1)
+ throw Exception("Missing libeufin-conversion-setup.sql file")
}
db.conn { it.execSQLUpdate(sqlProcedures.readText()) }
} else {
logger.info("Ensure conversion is disabled")
val sqlProcedures = File("${dbCfg.sqlDir}/libeufin-conversion-drop.sql")
if (!sqlProcedures.exists()) {
- logger.error("Missing libeufin-conversion-drop.sql file")
- exitProcess(1)
+ throw Exception("Missing libeufin-conversion-drop.sql file")
}
db.conn { it.execSQLUpdate(sqlProcedures.readText()) }
// Remove conversion info from the database ?
@@ -303,10 +295,8 @@ class ServeBank : CliktCommand("Run libeufin-bank HTTP server", name = "serve")
is ServerConfig.Tcp -> {
port = serverCfg.port
}
- is ServerConfig.Unix -> {
- logger.error("Can only serve libeufin-bank via TCP")
- exitProcess(1)
- }
+ is ServerConfig.Unix ->
+ throw Exception("Can only serve libeufin-bank via TCP")
}
}
module { corebankWebApp(db, ctx) }
@@ -323,7 +313,7 @@ class ChangePw : CliktCommand("Change account password", name = "passwd") {
private val username by argument("username")
private val password by argument("password")
- override fun run() {
+ override fun run() = cliCmd(logger) {
val cfg = talerConfig(configFile)
val ctx = cfg.loadBankConfig()
val dbCfg = cfg.loadDbConfig()
@@ -331,14 +321,11 @@ class ChangePw : CliktCommand("Change account password", name = "passwd") {
runBlocking {
val res = db.account.reconfigPassword(username, password, null)
when (res) {
- AccountPatchAuthResult.UnknownAccount -> {
- logger.error("Password change for '$username' account failed: unknown account")
- exitProcess(1)
- }
+ AccountPatchAuthResult.UnknownAccount ->
+ throw Exception("Password change for '$username' account failed: unknown account")
AccountPatchAuthResult.OldPasswordMismatch -> { /* Can never happen */ }
- AccountPatchAuthResult.Success -> {
+ AccountPatchAuthResult.Success ->
logger.info("Password change for '$username' account succeeded")
- }
}
}
}
@@ -351,15 +338,14 @@ class CreateAccount : CliktCommand("Create an account", name = "create-account")
)
private val json: RegisterAccountRequest by argument().convert { Json.decodeFromString<RegisterAccountRequest>(it) }
- override fun run() {
+ override fun run() = cliCmd(logger) {
val cfg = talerConfig(configFile)
val ctx = cfg.loadBankConfig()
val dbCfg = cfg.loadDbConfig()
val db = Database(dbCfg.dbConnStr, ctx.regionalCurrency, ctx.fiatCurrency)
runBlocking {
if (reservedAccounts.contains(json.username)) {
- logger.error("Username '${json.username}' is reserved")
- exitProcess(1)
+ throw Exception("Username '${json.username}' is reserved")
}
val internalPayto = json.internal_payto_uri ?: IbanPayTo(genIbanPaytoUri())
@@ -379,21 +365,14 @@ class CreateAccount : CliktCommand("Create an account", name = "create-account")
)
when (result) {
- AccountCreationResult.BonusBalanceInsufficient -> {
- logger.error("Insufficient admin funds to grant bonus")
- exitProcess(1)
- }
- AccountCreationResult.LoginReuse -> {
- logger.error("Account username reuse '${json.username}'")
- exitProcess(1)
- }
- AccountCreationResult.PayToReuse -> {
- logger.error("Bank internalPayToUri reuse '${internalPayto.canonical}'")
- exitProcess(1)
- }
- AccountCreationResult.Success -> {
+ AccountCreationResult.BonusBalanceInsufficient ->
+ throw Exception("Insufficient admin funds to grant bonus")
+ AccountCreationResult.LoginReuse ->
+ throw Exception("Account username reuse '${json.username}'")
+ AccountCreationResult.PayToReuse ->
+ throw Exception("Bank internalPayToUri reuse '${internalPayto.canonical}'")
+ AccountCreationResult.Success ->
logger.info("Account '${json.username}' created")
- }
}
}
}
diff --git a/util/src/main/kotlin/ConfigCli.kt b/util/src/main/kotlin/ConfigCli.kt
@@ -33,19 +33,19 @@ import kotlin.system.exitProcess
private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.util.ConfigCli")
-private fun <R> catchError(lambda: () -> R): R {
+fun cliCmd(logger: Logger, lambda: () -> Unit) {
try {
- return lambda()
- } catch (e: TalerConfigError) {
+ lambda()
+ } catch (e: Exception) {
logger.error(e.message)
exitProcess(1)
}
}
-private fun talerConfig(configSource: ConfigSource, configPath: String?): TalerConfig = catchError {
+private fun talerConfig(configSource: ConfigSource, configPath: String?): TalerConfig {
val config = TalerConfig(configSource)
config.load(configPath)
- config
+ return config
}
class CliConfigCmd(configSource: ConfigSource) : CliktCommand("Inspect or change the configuration", name = "config") {
@@ -69,7 +69,7 @@ private class CliConfigGet(private val configSource: ConfigSource) : CliktComman
private val optionName by argument()
- override fun run() {
+ override fun run() = cliCmd(logger) {
val config = talerConfig(configSource, configFile)
if (isPath) {
val res = config.lookupPath(sectionName, optionName)
@@ -98,7 +98,7 @@ private class CliConfigPathsub(private val configSource: ConfigSource) : CliktCo
)
private val pathExpr by argument()
- override fun run() {
+ override fun run() = cliCmd(logger) {
val config = talerConfig(configSource, configFile)
println(config.pathsub(pathExpr))
}
@@ -110,7 +110,7 @@ private class CliConfigDump(private val configSource: ConfigSource) : CliktComma
help = "set the configuration file"
)
- override fun run() {
+ override fun run() = cliCmd(logger) {
val config = talerConfig(configSource, configFile)
println("# install path: ${config.getInstallPath()}")
config.load(this.configFile)