aboutsummaryrefslogtreecommitdiff
path: root/common/src/main/kotlin/TalerCommon.kt
diff options
context:
space:
mode:
Diffstat (limited to 'common/src/main/kotlin/TalerCommon.kt')
-rw-r--r--common/src/main/kotlin/TalerCommon.kt100
1 files changed, 100 insertions, 0 deletions
diff --git a/common/src/main/kotlin/TalerCommon.kt b/common/src/main/kotlin/TalerCommon.kt
new file mode 100644
index 00000000..96d20a86
--- /dev/null
+++ b/common/src/main/kotlin/TalerCommon.kt
@@ -0,0 +1,100 @@
+/*
+ * This file is part of LibEuFin.
+ * Copyright (C) 2024 Taler Systems S.A.
+ *
+ * LibEuFin is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3, or
+ * (at your option) any later version.
+ *
+ * LibEuFin is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with LibEuFin; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>
+ */
+
+package tech.libeufin.common
+
+import kotlinx.serialization.*
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.json.*
+
+sealed class CommonError(msg: String): Exception(msg) {
+ class AmountFormat(msg: String): CommonError(msg)
+ class AmountNumberTooBig(msg: String): CommonError(msg)
+}
+
+@Serializable(with = TalerAmount.Serializer::class)
+class TalerAmount {
+ val value: Long
+ val frac: Int
+ val currency: String
+
+ constructor(value: Long, frac: Int, currency: String) {
+ this.value = value
+ this.frac = frac
+ this.currency = currency
+ }
+ constructor(encoded: String) {
+ val match = PATTERN.matchEntire(encoded) ?:
+ throw CommonError.AmountFormat("Invalid amount format");
+ val (currency, value, frac) = match.destructured
+ this.currency = currency
+ this.value = value.toLongOrNull() ?:
+ throw CommonError.AmountFormat("Invalid value")
+ if (this.value > MAX_VALUE)
+ throw CommonError.AmountNumberTooBig("Value specified in amount is too large")
+ this.frac = if (frac.isEmpty()) {
+ 0
+ } else {
+ var tmp = frac.toIntOrNull() ?:
+ throw CommonError.AmountFormat("Invalid fractional value")
+ if (tmp > FRACTION_BASE)
+ throw CommonError.AmountFormat("Fractional calue specified in amount is too large")
+ repeat(8 - frac.length) {
+ tmp *= 10
+ }
+ tmp
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is TalerAmount &&
+ other.value == this.value &&
+ other.frac == this.frac &&
+ other.currency == this.currency
+ }
+
+ override fun toString(): String {
+ if (frac == 0) {
+ return "$currency:$value"
+ } else {
+ return "$currency:$value.${frac.toString().padStart(8, '0')}"
+ .dropLastWhile { it == '0' } // Trim useless fractional trailing 0
+ }
+ }
+
+ internal object Serializer : KSerializer<TalerAmount> {
+ override val descriptor: SerialDescriptor =
+ PrimitiveSerialDescriptor("TalerAmount", PrimitiveKind.STRING)
+
+ override fun serialize(encoder: Encoder, value: TalerAmount) {
+ encoder.encodeString(value.toString())
+ }
+
+ override fun deserialize(decoder: Decoder): TalerAmount {
+ return TalerAmount(decoder.decodeString())
+ }
+ }
+
+ companion object {
+ const val FRACTION_BASE = 100000000
+ const val MAX_VALUE = 4503599627370496L; // 2^52
+ private val PATTERN = Regex("([A-Z]{1,11}):([0-9]+)(?:\\.([0-9]{1,8}))?");
+ }
+} \ No newline at end of file