libeufin

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

commit f24be2dae4b9d081825d9409f9dd00afca7181bc
parent 6182bc939d9e0c4d15e4e1a6289c99898a2821dc
Author: Antoine A <>
Date:   Mon,  8 Jan 2024 16:53:32 +0000

Simplify cli error handling

Diffstat:
Mintegration/src/main/kotlin/Main.kt | 9++++-----
Mnexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt | 12+++++-------
Mnexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt | 37++++++++++---------------------------
Mnexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt | 36++----------------------------------
Mnexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt | 63++++++++++++++++++++++-----------------------------------------
Mnexus/src/main/kotlin/tech/libeufin/nexus/Main.kt | 59++++++++++++++++++++++++++++++-----------------------------
Mutil/src/main/kotlin/Cli.kt | 1-
7 files changed, 73 insertions(+), 144 deletions(-)

diff --git a/integration/src/main/kotlin/Main.kt b/integration/src/main/kotlin/Main.kt @@ -50,7 +50,8 @@ fun step(name: String) { } fun ask(question: String): String? { - println("\u001b[;1m$question\u001b[0m") + print("\u001b[;1m$question\u001b[0m") + System.out.flush() return readlnOrNull() } @@ -86,16 +87,14 @@ class PostFinanceCli : CliktCommand("Run tests on postfinance", name="postfinanc if (!hasClientKeys) { step("Test INI order") - println("Got to https://testplattform.postfinance.ch/corporates/user/settings/ebics and click on 'Reset EBICS user'.\nPress Enter when done>") - readlnOrNull() + ask("Got to https://testplattform.postfinance.ch/corporates/user/settings/ebics and click on 'Reset EBICS user'.\nPress Enter when done>") nexusCmd.test("ebics-setup -c $conf") .assertErr("ebics-setup should failed the first time") } if (!hasBankKeys) { step("Test HIA order") - println("Got to https://testplattform.postfinance.ch/corporates/user/settings/ebics and click on 'Activate EBICS user'.\nPress Enter when done>") - readlnOrNull() + ask("Got to https://testplattform.postfinance.ch/corporates/user/settings/ebics and click on 'Activate EBICS user'.\nPress Enter when done>") nexusCmd.test("ebics-setup --auto-accept-keys -c $conf") .assertOk("ebics-setup should succeed the second time") } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt @@ -18,14 +18,12 @@ class DbInit : CliktCommand("Initialize the libeufin-nexus database", name = "db ).flag() override fun run() { - val cfg = loadConfigOrFail(common.config).extractDbConfigOrFail() - doOrFail { - pgDataSource(cfg.dbConnStr).pgConnection().use { conn -> - if (requestReset) { - resetDatabaseTables(conn, cfg, sqlFilePrefix = "libeufin-nexus") - } - initializeDatabaseTables(conn, cfg, sqlFilePrefix = "libeufin-nexus") + val cfg = loadConfig(common.config).dbConfig() + pgDataSource(cfg.dbConnStr).pgConnection().use { conn -> + if (requestReset) { + resetDatabaseTables(conn, cfg, sqlFilePrefix = "libeufin-nexus") } + initializeDatabaseTables(conn, cfg, sqlFilePrefix = "libeufin-nexus") } } } \ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt @@ -402,14 +402,11 @@ private fun ingestNotification( try { runBlocking { incomingPayments.forEach { - println(it) - ingestIncomingPayment( - db, - it - ) + logger.debug("incoming tx: $it") + ingestIncomingPayment(db, it) } outgoingPayments.forEach { - println(it) + logger.debug("outgoing tx: $it") ingestOutgoingPayment(db, it) } } @@ -532,7 +529,7 @@ class EbicsFetch: CliktCommand("Fetches bank records. Defaults to camt.054 noti override fun run() = cliCmd(logger) { val cfg: EbicsSetupConfig = extractEbicsConfig(common.config) - val dbCfg = cfg.config.extractDbConfigOrFail() + val dbCfg = cfg.config.dbConfig() val db = Database(dbCfg.dbConnStr) // Deciding what to download. @@ -568,17 +565,7 @@ class EbicsFetch: CliktCommand("Fetches bank records. Defaults to camt.054 noti return@cliCmd } - // Fail now if keying is incomplete. - if (!isKeyingComplete(cfg)) throw Error() - val bankKeys = loadBankKeys(cfg.bankPublicKeysFilename) ?: throw Error() - if (!bankKeys.accepted && !import && !parse) { - throw Error("Bank keys are not accepted, yet. Won't fetch any records.") - } - val clientKeys = loadPrivateKeysFromDisk(cfg.clientPrivateKeysFilename) - if (clientKeys == null) { - throw Error("Client private keys not found at: ${cfg.clientPrivateKeysFilename}") - } - + val (clientKeys, bankKeys) = expectFullKeys(cfg) val ctx = FetchContext( cfg, HttpClient(), @@ -593,10 +580,8 @@ class EbicsFetch: CliktCommand("Fetches bank records. Defaults to camt.054 noti val pinnedStartVal = pinnedStart val pinnedStartArg = if (pinnedStartVal != null) { logger.debug("Pinning start date to: $pinnedStartVal") - doOrFail { - // Converting YYYY-MM-DD to Instant. - LocalDate.parse(pinnedStartVal).atStartOfDay(ZoneId.of("UTC")).toInstant() - } + // Converting YYYY-MM-DD to Instant. + LocalDate.parse(pinnedStartVal).atStartOfDay(ZoneId.of("UTC")).toInstant() } else null ctx.pinnedStart = pinnedStartArg if (whichDoc == SupportedDocument.PAIN_002_LOGS) @@ -606,11 +591,9 @@ class EbicsFetch: CliktCommand("Fetches bank records. Defaults to camt.054 noti } return@cliCmd } - val frequency: NexusFrequency = doOrFail { - val configValue = cfg.config.requireString("nexus-fetch", "frequency") - val frequencySeconds = checkFrequency(configValue) - NexusFrequency(frequencySeconds, configValue) - } + val configValue = cfg.config.requireString("nexus-fetch", "frequency") + val frequencySeconds = checkFrequency(configValue) + val frequency: NexusFrequency = NexusFrequency(frequencySeconds, configValue) logger.debug("Running with a frequency of ${frequency.fromConfig}") if (frequency.inSeconds == 0) { logger.warn("Long-polling not implemented, running therefore in transient mode") diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt @@ -35,32 +35,6 @@ import java.time.Instant import kotlin.reflect.typeOf /** - * Checks the configuration to secure that the key exchange between - * the bank and the subscriber took place. Helps to fail before starting - * to talk EBICS to the bank. - * - * @param cfg configuration handle. - * @return true if the keying was made before, false otherwise. - */ -fun isKeyingComplete(cfg: EbicsSetupConfig): Boolean { - val maybeClientKeys = loadPrivateKeysFromDisk(cfg.clientPrivateKeysFilename) - if (maybeClientKeys == null || - (!maybeClientKeys.submitted_ini) || - (!maybeClientKeys.submitted_hia)) { - logger.error("Cannot operate without or with unsubmitted subscriber keys." + - " Run 'libeufin-nexus ebics-setup' first.") - return false - } - val maybeBankKeys = loadBankKeys(cfg.bankPublicKeysFilename) - if (maybeBankKeys == null || (!maybeBankKeys.accepted)) { - logger.error("Cannot operate without or with unaccepted bank keys." + - " Run 'libeufin-nexus ebics-setup' until accepting the bank keys.") - return false - } - return true -} - -/** * Writes the JSON content to disk. Used when we create or update * keys and other metadata JSON content to disk. WARNING: this overrides * silently what's found under the given location! @@ -290,14 +264,8 @@ suspend fun doKeysRequestAndUpdateState( * @return internal representation of the configuration. */ fun extractEbicsConfig(configFile: String?): EbicsSetupConfig { - val config = loadConfigOrFail(configFile) - // Checking the config. - val cfg = try { - EbicsSetupConfig(config) - } catch (e: TalerConfigError) { - throw Error(e.message) - } - return cfg + val config = loadConfig(configFile) + return EbicsSetupConfig(config) } /** diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt @@ -37,7 +37,6 @@ import java.time.ZoneId import java.util.* import kotlin.concurrent.fixedRateTimer import kotlin.io.path.createDirectories -import kotlin.system.exitProcess /** * Possible stages when an error may occur. These stages @@ -117,17 +116,16 @@ private fun maybeLog( val asUtcDate = LocalDate.ofInstant(now, ZoneId.of("UTC")) val subDir = "${asUtcDate.year}-${asUtcDate.monthValue}-${asUtcDate.dayOfMonth}" val dirs = Path.of(maybeLogDir, subDir) - doOrFail { dirs.createDirectories() } + dirs.createDirectories() val f = File( dirs.toString(), "${now.toDbMicros()}_requestUid_${requestUid}_pain.001.xml" ) // Very rare: same pain.001 should not be submitted twice in the same microsecond. if (f.exists()) { - logger.error("pain.001 log file exists already at: $f") - exitProcess(1) + throw Error("pain.001 log file exists already at: $f") } - doOrFail { f.writeText(xml) } + f.writeText(xml) } /** @@ -270,24 +268,11 @@ class EbicsSubmit : CliktCommand("Submits any initiated payment found in the dat * or long-polls (currently not implemented) for new payments. * FIXME: reduce code duplication with the fetch subcommand. */ - override fun run() { - val cfg: EbicsSetupConfig = doOrFail { - extractEbicsConfig(common.config) - } - // Fail now if keying is incomplete. - if (!isKeyingComplete(cfg)) exitProcess(1) - val dbCfg = cfg.config.extractDbConfigOrFail() + override fun run() = cliCmd(logger) { + val cfg: EbicsSetupConfig = extractEbicsConfig(common.config) + val dbCfg = cfg.config.dbConfig() val db = Database(dbCfg.dbConnStr) - val bankKeys = loadBankKeys(cfg.bankPublicKeysFilename) ?: exitProcess(1) - if (!bankKeys.accepted) { - logger.error("Bank keys are not accepted, yet. Won't submit any payment.") - exitProcess(1) - } - val clientKeys = loadPrivateKeysFromDisk(cfg.clientPrivateKeysFilename) - if (clientKeys == null) { - logger.error("Client private keys not found at: ${cfg.clientPrivateKeysFilename}") - exitProcess(1) - } + val (clientKeys, bankKeys) = expectFullKeys(cfg) val ctx = SubmissionContext( cfg = cfg, bankPublicKeysFile = bankKeys, @@ -298,35 +283,31 @@ class EbicsSubmit : CliktCommand("Submits any initiated payment found in the dat if (debug) { logger.info("Running in debug mode, submitting STDIN to the bank") val maybeStdin = generateSequence(::readLine).joinToString("\n") - doOrFail { - runBlocking { - submitPain001( - maybeStdin, - ctx.cfg, - ctx.clientPrivateKeysFile, - ctx.bankPublicKeysFile, - ctx.httpClient, - ctx.ebicsExtraLog - ) - } + runBlocking { + submitPain001( + maybeStdin, + ctx.cfg, + ctx.clientPrivateKeysFile, + ctx.bankPublicKeysFile, + ctx.httpClient, + ctx.ebicsExtraLog + ) } - return + return@cliCmd } if (transient) { logger.info("Transient mode: submitting what found and returning.") submitBatch(ctx, db) - return - } - val frequency: NexusFrequency = doOrFail { - val configValue = cfg.config.requireString("nexus-submit", "frequency") - val frequencySeconds = checkFrequency(configValue) - return@doOrFail NexusFrequency(frequencySeconds, configValue) + return@cliCmd } + val configValue = cfg.config.requireString("nexus-submit", "frequency") + val frequencySeconds = checkFrequency(configValue) + val frequency: NexusFrequency = NexusFrequency(frequencySeconds, configValue) logger.debug("Running with a frequency of ${frequency.fromConfig}") if (frequency.inSeconds == 0) { logger.warn("Long-polling not implemented, running therefore in transient mode") submitBatch(ctx, db) - return + return@cliCmd } fixedRateTimer( name = "ebics submit period", diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt @@ -35,7 +35,6 @@ import kotlinx.serialization.KSerializer import org.slf4j.Logger import org.slf4j.LoggerFactory import java.io.File -import kotlin.system.exitProcess import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor @@ -269,19 +268,31 @@ data class BankPublicKeysFile( ) /** - * Runs the argument and fails the process, if that throws - * an exception. + * Load client and bank keys from disk. + * Checks that the keying process has been fully completed. + * + * Helps to fail before starting to talk EBICS to the bank. * - * @param getLambda function that might return a value. - * @return the value from getLambda. + * @param cfg configuration handle. + * @return both client and bank keys */ -fun <T>doOrFail(getLambda: () -> T): T = - try { - getLambda() - } catch (e: Exception) { - logger.error(e.message) - exitProcess(1) +fun expectFullKeys( + cfg: EbicsSetupConfig +): Pair<ClientPrivateKeysFile, BankPublicKeysFile> { + val clientKeys = loadPrivateKeysFromDisk(cfg.clientPrivateKeysFilename) + if (clientKeys == null || + !clientKeys.submitted_ini || + !clientKeys.submitted_hia) { + throw Error("Cannot operate without or with unsubmitted subscriber keys." + + " Run 'libeufin-nexus ebics-setup' first.") + } + val bankKeys = loadBankKeys(cfg.bankPublicKeysFilename) + if (bankKeys == null || !bankKeys.accepted) { + throw Error("Cannot operate without or with unaccepted bank keys." + + " Run 'libeufin-nexus ebics-setup' until accepting the bank keys.") } + return Pair(clientKeys, bankKeys) +} /** * Load the bank keys file from disk. @@ -342,35 +353,25 @@ fun loadPrivateKeysFromDisk(location: String): ClientPrivateKeysFile? { } /** - * Abstracts the config loading and exception handling. + * Abstracts the config loading * * @param configFile potentially NULL configuration file location. * @return the configuration handle. */ -fun loadConfigOrFail(configFile: String?): TalerConfig { +fun loadConfig(configFile: String?): TalerConfig { val config = TalerConfig(NEXUS_CONFIG_SOURCE) - try { - config.load(configFile) - } catch (e: Exception) { - logger.error("Could not load configuration from ${configFile}, detail: ${e.message}") - exitProcess(1) - } + config.load(configFile) return config } /** * Abstracts fetching the DB config values to set up Nexus. */ -fun TalerConfig.extractDbConfigOrFail(): DatabaseConfig = - try { - DatabaseConfig( - dbConnStr = requireString("nexus-postgres", "config"), - sqlDir = requirePath("libeufin-nexusdb-postgres", "sql_dir") - ) - } catch (e: Exception) { - logger.error("Could not load config options for Nexus DB, detail: ${e.message}.") - exitProcess(1) - } +fun TalerConfig.dbConfig(): DatabaseConfig = + DatabaseConfig( + dbConnStr = requireString("nexus-postgres", "config"), + sqlDir = requirePath("libeufin-nexusdb-postgres", "sql_dir") + ) /** * Main CLI class that collects all the subcommands. diff --git a/util/src/main/kotlin/Cli.kt b/util/src/main/kotlin/Cli.kt @@ -29,7 +29,6 @@ import com.github.ajalt.clikt.parameters.options.* import com.github.ajalt.clikt.parameters.groups.* import org.slf4j.Logger import org.slf4j.LoggerFactory -import kotlin.system.exitProcess private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.util.ConfigCli")