summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Taler.xcodeproj/project.pbxproj10
-rw-r--r--Taler/Amount.swift194
-rw-r--r--Taler/WalletBackend.swift203
-rw-r--r--TalerTests/AmountTests.swift62
4 files changed, 461 insertions, 8 deletions
diff --git a/Taler.xcodeproj/project.pbxproj b/Taler.xcodeproj/project.pbxproj
index a8a4b3d..12f8c85 100644
--- a/Taler.xcodeproj/project.pbxproj
+++ b/Taler.xcodeproj/project.pbxproj
@@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
D112510026B12E3200D02E00 /* taler-wallet-embedded.js in CopyFiles */ = {isa = PBXBuildFile; fileRef = D11250FF26B12E3200D02E00 /* taler-wallet-embedded.js */; };
+ D1472E5526B9206800896566 /* AmountTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1472E5426B9206800896566 /* AmountTests.swift */; };
D14AFD2124D232B300C51073 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14AFD2024D232B300C51073 /* AppDelegate.swift */; };
D14AFD2324D232B300C51073 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14AFD2224D232B300C51073 /* SceneDelegate.swift */; };
D14AFD2524D232B300C51073 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14AFD2424D232B300C51073 /* ContentView.swift */; };
@@ -34,6 +35,7 @@
D17D8B8425ADB29B001BD43D /* libhistogram.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D17D8B5625ADB130001BD43D /* libhistogram.a */; };
D17D8B8525ADB29B001BD43D /* libcares.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D17D8B4825ADB12B001BD43D /* libcares.a */; };
D1AFF0F3268D59C200FBB744 /* libiono.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D1AFF0F2268D59A500FBB744 /* libiono.a */; };
+ D1BA3F9226B8889600A5848B /* Amount.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BA3F9126B8889600A5848B /* Amount.swift */; };
D1D65B9826992E4600C1012A /* WalletBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1D65B9726992E4600C1012A /* WalletBackend.swift */; };
/* End PBXBuildFile section */
@@ -111,6 +113,7 @@
D145D1EF25AC416B00CDD61B /* libv8_base_without_compiler.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libv8_base_without_compiler.a; path = "ios-node-v8/taler-ios-build/compiled/node-x64/libv8_base_without_compiler.a"; sourceTree = "<group>"; };
D145D1F025AC416B00CDD61B /* libhistogram.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libhistogram.a; path = "ios-node-v8/taler-ios-build/compiled/node-x64/libhistogram.a"; sourceTree = "<group>"; };
D145D1F125AC416B00CDD61B /* libv8_libbase.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libv8_libbase.a; path = "ios-node-v8/taler-ios-build/compiled/node-x64/libv8_libbase.a"; sourceTree = "<group>"; };
+ D1472E5426B9206800896566 /* AmountTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmountTests.swift; sourceTree = "<group>"; };
D14AFD1D24D232B300C51073 /* Taler.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Taler.app; sourceTree = BUILT_PRODUCTS_DIR; };
D14AFD2024D232B300C51073 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
D14AFD2224D232B300C51073 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
@@ -148,6 +151,7 @@
D17D8B5725ADB130001BD43D /* libtorque_base.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtorque_base.a; path = "ios-node-v8/taler-ios-build/compiled/node-arm64/libtorque_base.a"; sourceTree = "<group>"; };
D1AB963B259EB13D00DEAB23 /* libnode.89.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libnode.89.dylib; path = "ios-node-v8/taler-ios-build/compiled/x64-v8a/libnode.89.dylib"; sourceTree = "<group>"; };
D1AFF0F2268D59A500FBB744 /* libiono.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libiono.a; path = iono/compiled/x64/libiono.a; sourceTree = "<group>"; };
+ D1BA3F9126B8889600A5848B /* Amount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Amount.swift; sourceTree = "<group>"; };
D1D65B9726992E4600C1012A /* WalletBackend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletBackend.swift; sourceTree = "<group>"; };
D1F0C22F25A958AE00C3179D /* libllhttp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libllhttp.a; path = "ios-node-v8/tools/ios-framework/bin/x64/libllhttp.a"; sourceTree = "<group>"; };
D1F0C23025A958AE00C3179D /* libv8_initializers.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libv8_initializers.a; path = "ios-node-v8/tools/ios-framework/bin/x64/libv8_initializers.a"; sourceTree = "<group>"; };
@@ -266,6 +270,7 @@
D14AFD2024D232B300C51073 /* AppDelegate.swift */,
D14AFD2224D232B300C51073 /* SceneDelegate.swift */,
D1D65B9726992E4600C1012A /* WalletBackend.swift */,
+ D1BA3F9126B8889600A5848B /* Amount.swift */,
D14AFD2424D232B300C51073 /* ContentView.swift */,
D14AFD2624D232B500C51073 /* Assets.xcassets */,
D14AFD2B24D232B500C51073 /* LaunchScreen.storyboard */,
@@ -279,6 +284,7 @@
children = (
D14AFD3724D232B500C51073 /* TalerTests.swift */,
D14AFD3924D232B500C51073 /* Info.plist */,
+ D1472E5426B9206800896566 /* AmountTests.swift */,
);
path = TalerTests;
sourceTree = "<group>";
@@ -570,7 +576,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "WALLET_CORE_VERSION=\"v0.8.1\"\nWALLET_CORE_HASH=\"23bf89b663f0fd0e84a3d7e54a19766766c7306e5704e43a25df57da72056fa7\"\nWALLET_SRC=\"https://git.taler.net/wallet-core.git/plain/${WALLET_CORE_VERSION}/taler-wallet-embedded.js?h=prebuilt\"\nWALLET_DST=\"${SRCROOT}/taler-wallet-embedded.js\"\n\n[ ! -e $WALLET_DST ] || rm $WALLET_DST\ncurl $WALLET_SRC --output $WALLET_DST\n\nRECEIVED_HASH=$(openssl sha256 -r $WALLET_DST)\nRECEIVED_HASH_SPLIT=($RECEIVED_HASH)\nif [ $WALLET_CORE_HASH != ${RECEIVED_HASH_SPLIT[0]} ]\nthen\n exit 1\nfi\n";
+ shellScript = "exit 0\nWALLET_CORE_VERSION=\"v0.8.1\"\nWALLET_CORE_HASH=\"23bf89b663f0fd0e84a3d7e54a19766766c7306e5704e43a25df57da72056fa7\"\nWALLET_SRC=\"https://git.taler.net/wallet-core.git/plain/${WALLET_CORE_VERSION}/taler-wallet-embedded.js?h=prebuilt\"\nWALLET_DST=\"${SRCROOT}/taler-wallet-embedded.js\"\n\n[ ! -e $WALLET_DST ] || rm $WALLET_DST\ncurl $WALLET_SRC --output $WALLET_DST\n\nRECEIVED_HASH=$(openssl sha256 -r $WALLET_DST)\nRECEIVED_HASH_SPLIT=($RECEIVED_HASH)\nif [ $WALLET_CORE_HASH != ${RECEIVED_HASH_SPLIT[0]} ]\nthen\n exit 1\nfi\n";
};
/* End PBXShellScriptBuildPhase section */
@@ -579,6 +585,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ D1BA3F9226B8889600A5848B /* Amount.swift in Sources */,
D14AFD2124D232B300C51073 /* AppDelegate.swift in Sources */,
D14AFD2324D232B300C51073 /* SceneDelegate.swift in Sources */,
D14AFD2524D232B300C51073 /* ContentView.swift in Sources */,
@@ -591,6 +598,7 @@
buildActionMask = 2147483647;
files = (
D14AFD3824D232B500C51073 /* TalerTests.swift in Sources */,
+ D1472E5526B9206800896566 /* AmountTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/Taler/Amount.swift b/Taler/Amount.swift
new file mode 100644
index 0000000..83445b4
--- /dev/null
+++ b/Taler/Amount.swift
@@ -0,0 +1,194 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2021 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import Foundation
+
+enum AmountError: Error {
+ case invalidStringRepresentation
+ case incompatibleCurrency
+ case invalidAmount
+ case negativeAmount
+}
+
+class Amount: Codable, CustomStringConvertible {
+ private static let maxValue: UInt64 = 1 << 52
+ private static let fractionalBase: UInt32 = 100000000
+ private static let fractionalBaseDigits: UInt = 8
+ var currency: String
+ var value: UInt64
+ var fraction: UInt32
+ var description: String {
+ if fraction == 0 {
+ return "\(currency):\(value)"
+ } else {
+ var frac = fraction
+ var fracStr = ""
+ while (frac > 0) {
+ fracStr += "\(frac / (Amount.fractionalBase / 10))"
+ frac = (frac * 10) % Amount.fractionalBase
+ }
+ return "\(currency):\(value).\(fracStr)"
+ }
+ }
+ var valid: Bool {
+ return (value <= Amount.maxValue && currency != "")
+ }
+
+ init(fromString string: String) throws {
+ guard let separatorIndex = string.firstIndex(of: ":") else { throw AmountError.invalidStringRepresentation }
+ self.currency = String(string[..<separatorIndex])
+ let amountStr = String(string[string.index(separatorIndex, offsetBy: 1)...])
+ if let dotIndex = amountStr.firstIndex(of: ".") {
+ let valueStr = String(amountStr[..<dotIndex])
+ let fractionStr = String(amountStr[string.index(dotIndex, offsetBy: 1)...])
+ guard let _value = UInt64(valueStr) else { throw AmountError.invalidStringRepresentation }
+ self.value = _value
+ self.fraction = 0
+ var digitValue = Amount.fractionalBase / 10
+ for char in fractionStr {
+ guard let digit = char.wholeNumberValue else { throw AmountError.invalidStringRepresentation }
+ self.fraction += digitValue * UInt32(digit)
+ digitValue /= 10
+ }
+ } else {
+ guard let _value = UInt64(amountStr) else { throw AmountError.invalidStringRepresentation }
+ self.value = _value
+ self.fraction = 0
+ }
+ guard self.valid else { throw AmountError.invalidAmount }
+ }
+
+ init(currency: String, value: UInt64, fraction: UInt32) {
+ self.currency = currency
+ self.value = value
+ self.fraction = fraction
+ }
+
+ init(fromDecoder decoder: Decoder) throws {
+ let container = try decoder.singleValueContainer()
+ let string = try container.decode(String.self)
+ /* TODO: de-duplicate */
+ guard let separatorIndex = string.firstIndex(of: ":") else { throw AmountError.invalidStringRepresentation }
+ self.currency = String(string[..<separatorIndex])
+ let amountStr = String(string[string.index(separatorIndex, offsetBy: 1)...])
+ if let dotIndex = amountStr.firstIndex(of: ".") {
+ let valueStr = String(amountStr[..<dotIndex])
+ let fractionStr = String(amountStr[string.index(dotIndex, offsetBy: 1)...])
+ guard let _value = UInt64(valueStr) else { throw AmountError.invalidStringRepresentation }
+ self.value = _value
+ self.fraction = 0
+ var digitValue = Amount.fractionalBase / 10
+ for char in fractionStr {
+ guard let digit = char.wholeNumberValue else { throw AmountError.invalidStringRepresentation }
+ self.fraction += digitValue * UInt32(digit)
+ digitValue /= 10
+ }
+ } else {
+ guard let _value = UInt64(amountStr) else { throw AmountError.invalidStringRepresentation }
+ self.value = _value
+ self.fraction = 0
+ }
+ guard self.valid else { throw AmountError.invalidAmount }
+ }
+
+ func copy() -> Amount {
+ return Amount(currency: self.currency, value: self.value, fraction: self.fraction)
+ }
+
+ func normalizedCopy() throws -> Amount {
+ let amount = self.copy()
+ try amount.normalize()
+ return amount
+ }
+
+ func encode(to encoder: Encoder) throws {
+ var container = encoder.singleValueContainer()
+ try container.encode(self.description)
+ }
+
+ func normalize() throws {
+ if !valid {
+ throw AmountError.invalidAmount
+ }
+ self.value += UInt64(self.fraction / Amount.fractionalBase)
+ self.fraction = self.fraction % Amount.fractionalBase
+ if !valid {
+ throw AmountError.invalidAmount
+ }
+ }
+
+ static func + (left: Amount, right: Amount) throws -> Amount {
+ if left.currency != right.currency {
+ throw AmountError.incompatibleCurrency
+ }
+ let leftNormalized = try left.normalizedCopy()
+ let rightNormalized = try right.normalizedCopy()
+ let result: Amount = leftNormalized
+ result.value += rightNormalized.value
+ result.fraction += rightNormalized.fraction
+ try result.normalize()
+ return result
+ }
+
+ static func - (left: Amount, right: Amount) throws -> Amount {
+ if left.currency != right.currency {
+ throw AmountError.incompatibleCurrency
+ }
+ let leftNormalized = try left.normalizedCopy()
+ let rightNormalized = try right.normalizedCopy()
+ if (leftNormalized.fraction < rightNormalized.fraction) {
+ guard leftNormalized.value != 0 else { throw AmountError.negativeAmount }
+ leftNormalized.value -= 1
+ leftNormalized.fraction += Amount.fractionalBase
+ }
+ guard leftNormalized.value >= rightNormalized.value else { throw AmountError.negativeAmount }
+ let diff = Amount.zero(currency: left.currency)
+ diff.value = leftNormalized.value - rightNormalized.value
+ diff.fraction = leftNormalized.fraction - rightNormalized.fraction
+ try diff.normalize()
+ return diff
+ }
+
+ static func == (left: Amount, right: Amount) throws -> Bool {
+ if left.currency != right.currency {
+ throw AmountError.incompatibleCurrency
+ }
+ let leftNormalized = try left.normalizedCopy()
+ let rightNormalized = try right.normalizedCopy()
+ return (leftNormalized.value == rightNormalized.value && leftNormalized.fraction == rightNormalized.fraction)
+ }
+
+ static func < (left: Amount, right: Amount) throws -> Bool {
+ if left.currency != right.currency {
+ throw AmountError.incompatibleCurrency
+ }
+ let leftNormalized = try left.normalizedCopy()
+ let rightNormalized = try right.normalizedCopy()
+ if (leftNormalized.value == rightNormalized.value) {
+ return (leftNormalized.fraction < rightNormalized.fraction)
+ } else {
+ return (leftNormalized.value < rightNormalized.value)
+ }
+ }
+
+ static func > (left: Amount, right: Amount) throws -> Bool {
+ return try right < left
+ }
+
+ static func zero(currency: String) -> Amount {
+ return Amount(currency: currency, value: 0, fraction: 0)
+ }
+}
diff --git a/Taler/WalletBackend.swift b/Taler/WalletBackend.swift
index 5a28371..d5cc3ab 100644
--- a/Taler/WalletBackend.swift
+++ b/Taler/WalletBackend.swift
@@ -17,19 +17,26 @@
import Foundation
import iono
+enum WalletBackendResponseError: Error {
+ case malformedResponse
+}
+
protocol WalletBackendRequest {
associatedtype Args: Encodable
+ associatedtype Response: Decodable
func operation() -> String
func args() -> Args
+ func success(result: Response)
+ func error(_ err: WalletBackendResponseError)
}
fileprivate struct WalletBackendRequestData<T: WalletBackendRequest>: Encodable {
var operation: String
- var id: Int
+ var id: UInt
var args: T.Args
- init(request: T, id: Int) {
+ init(request: T, id: UInt) {
operation = request.operation()
self.id = id
args = request.args()
@@ -41,10 +48,22 @@ fileprivate struct WalletBackendInitRequest: WalletBackendRequest {
var persistentStoragePath: String
}
typealias Args = RequestArgs
+ struct Response: Codable {
+ struct SupportedProtocolVersions: Codable {
+ var exchange: String
+ var merchant: String
+ }
+ var supportedProtocolVersions: SupportedProtocolVersions
+ enum CodingKeys: String, CodingKey {
+ case supportedProtocolVersions = "supported_protocol_versions"
+ }
+ }
private var requestArgs: RequestArgs
+ private let success: () -> Void
- init(persistentStoragePath: String) {
+ init(persistentStoragePath: String, onSuccess: @escaping () -> Void) {
requestArgs = RequestArgs(persistentStoragePath: persistentStoragePath)
+ self.success = onSuccess
}
func operation() -> String {
@@ -54,6 +73,14 @@ fileprivate struct WalletBackendInitRequest: WalletBackendRequest {
func args() -> Args {
return requestArgs
}
+
+ func success(result: Response) {
+ self.success()
+ }
+
+ func error(_ err: WalletBackendResponseError) {
+
+ }
}
fileprivate struct WalletBackendGetTransactionsRequest: WalletBackendRequest {
@@ -61,6 +88,7 @@ fileprivate struct WalletBackendGetTransactionsRequest: WalletBackendRequest {
}
typealias Args = RequestArgs
+ typealias Response = String
private var requestArgs: RequestArgs
init() {
@@ -74,19 +102,110 @@ fileprivate struct WalletBackendGetTransactionsRequest: WalletBackendRequest {
func args() -> Args {
return requestArgs
}
+
+ func success(result: Response) {
+
+ }
+
+ func error(_ err: WalletBackendResponseError) {
+
+ }
+}
+
+struct WalletBackendGetBalancesRequest: WalletBackendRequest {
+ struct Balance: Decodable {
+ var available: Amount
+ var pendingIncoming: Amount
+ var pendingOutgoing: Amount
+ var requiresUserInput: Bool
+ }
+ struct BalancesResponse: Decodable {
+ var balances: [Balance]
+ }
+ struct RequestArgs: Encodable {
+
+ }
+ typealias Args = RequestArgs
+ typealias Response = BalancesResponse
+ private var requestArgs: RequestArgs
+ private let success: ([Balance]) -> Void
+ private let failure: () -> Void
+
+ init(onSuccess: @escaping ([Balance]) -> Void, onFailure: @escaping () -> Void) {
+ self.requestArgs = RequestArgs()
+ self.success = onSuccess
+ self.failure = onFailure
+ }
+
+ func operation() -> String {
+ return "getBalances"
+ }
+
+ func args() -> Args {
+ return requestArgs
+ }
+
+ func success(result: Response) {
+ print("balances???")
+ }
+
+ func error(_ err: WalletBackendResponseError) {
+
+ }
+}
+
+struct WalletBackendWithdrawTestBalance: WalletBackendRequest {
+ struct RequestArgs: Encodable {
+ var amount: String
+ var bankBaseUrl: String
+ var exchangeBaseUrl: String
+ }
+ typealias Args = RequestArgs
+ typealias Response = String
+ private var requestArgs: RequestArgs
+
+ init(amount: String, bankBaseUrl: String, exchangeBaseUrl: String) {
+ requestArgs = RequestArgs(amount: amount, bankBaseUrl: bankBaseUrl, exchangeBaseUrl: exchangeBaseUrl)
+ }
+
+ func operation() -> String {
+ return "withdrawTestBalance"
+ }
+
+ func args() -> Args {
+ return requestArgs
+ }
+
+ func success(result: Response) {
+
+ }
+
+ func error(_ err: WalletBackendResponseError) {
+
+ }
}
enum WalletBackendError: Error {
case serializationError
+ case deserializationError
}
class WalletBackend: IonoMessageHandler {
private var iono: Iono
- private var requestsMade: Int
+ private var requestsMade: UInt
+ private var backendReady: Bool
+ private var backendReadyCondition: NSCondition
+ private struct RequestDetail {
+ let decodeSuccess: (Data) -> Void
+ //let handleError: (Data) -> Void
+ }
+ private var requests: [UInt : RequestDetail] = [:]
init() {
iono = Iono()
requestsMade = 0
+ self.backendReady = false
+ self.backendReadyCondition = NSCondition()
iono.messageHandler = self
@@ -107,21 +226,91 @@ class WalletBackend: IonoMessageHandler {
var storageDir = documentUrls[0]
storageDir.appendPathComponent("talerwalletdb-v30", isDirectory: false)
storageDir.appendPathExtension("json")
- try! sendRequest(request: WalletBackendInitRequest(persistentStoragePath: storageDir.path))
+ try! sendRequest(request: WalletBackendInitRequest(persistentStoragePath: storageDir.path, onSuccess: {
+ self.backendReady = true
+ self.backendReadyCondition.broadcast()
+ }))
+ }
+
+ waitUntilReady()
+ //try! sendRequest(request: WalletBackendWithdrawTestBalance(amount: "TESTKUDOS:10", bankBaseUrl: "https://bank.test.taler.net/", exchangeBaseUrl: "https://exchange.test.taler.net/"))
+ try! sendRequest(request: WalletBackendGetBalancesRequest(onSuccess: { ([WalletBackendGetBalancesRequest.Balance]) -> Void in
+
+ }, onFailure: { () -> Void in
+
+ }))
+ }
+
+ func waitUntilReady() {
+ backendReadyCondition.lock()
+ while (!self.backendReady) {
+ backendReadyCondition.wait()
}
- //try! sendRequest(request: WalletBackendGetTransactionsRequest())
+ backendReadyCondition.unlock()
}
func handleMessage(message: String) {
- print("received message \(message)")
+ print(message)
+ do {
+ guard let messageData = message.data(using: .utf8) else { throw WalletBackendError.deserializationError }
+ let data = try JSONSerialization.jsonObject(with: messageData, options: .allowFragments) as? [String : Any]
+ if let responseData = data {
+ let type = (responseData["type"] as? String) ?? ""
+ if type == "response" {
+ guard let id = responseData["id"] as? UInt else { /* TODO: error. */ return }
+ guard let request = requests[id] else { /* TODO: error. */ return }
+ request.decodeSuccess(messageData)
+ requests[id] = nil
+ } else if type == "tunnelHttp" {
+
+ } else if type == "notification" {
+
+ } else if type == "error" {
+ guard let id = responseData["id"] as? UInt else { /* TODO: error. */ return }
+ guard let request = requests[id] else { /* TODO: error. */ return }
+ //request.handleError(messageData)
+ requests[id] = nil
+ } else {
+ /* TODO: unknown response type. */
+ }
+ }
+ } catch {
+
+ }
}
+ private struct FullResponse<T: WalletBackendRequest>: Decodable {
+ let type: String
+ let operation: String
+ let id: UInt
+ let result: T.Response
+ }
+
+ /*private struct FullError: Decodable {
+ let type: String
+ let operation: String
+ let id: UInt
+ let error: WalletErrorDetail
+ }*/
+
func sendRequest<T: WalletBackendRequest>(request: T) throws {
let data = WalletBackendRequestData<T>(request: request, id: requestsMade)
requestsMade += 1
+ let decodeSuccess = { (data: Data) -> Void in
+ do {
+ let decoded = try JSONDecoder().decode(FullResponse<T>.self, from: data)
+ request.success(result: decoded.result)
+ } catch {
+
+ }
+ }
+
+ /* Encode the request and send it to the backend. */
do {
let encoded = try JSONEncoder().encode(data)
guard let jsonString = String(data: encoded, encoding: .utf8) else { throw WalletBackendError.serializationError }
+ let detail = RequestDetail(decodeSuccess: decodeSuccess)
+ requests[data.id] = detail
iono.sendMessage(message: jsonString)
} catch {
throw WalletBackendError.serializationError
diff --git a/TalerTests/AmountTests.swift b/TalerTests/AmountTests.swift
new file mode 100644
index 0000000..b7dd28d
--- /dev/null
+++ b/TalerTests/AmountTests.swift
@@ -0,0 +1,62 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2021 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import XCTest
+@testable import Taler
+
+class AmountTests: XCTestCase {
+
+ override func setUpWithError() throws {
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+ }
+
+ override func tearDownWithError() throws {
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+ }
+
+ func testAmounts() throws {
+ var amount: Amount = try! Amount(fromString: "EUR:633.59")
+ XCTAssert(amount.currency == "EUR")
+ XCTAssert(amount.value == 633)
+ XCTAssert(amount.fraction == 59000000)
+ XCTAssert(amount.description == "EUR:633.59")
+ XCTAssert(try amount == Amount(currency: "EUR", value: 633, fraction: 59000000))
+ XCTAssert(try amount == amount)
+
+ amount = try! Amount(fromString: "EUR:883")
+ XCTAssert(amount.currency == "EUR")
+ XCTAssert(amount.value == 883)
+ XCTAssert(amount.fraction == 0)
+ XCTAssert(amount.description == "EUR:883")
+
+ XCTAssertThrowsError(try Amount(fromString: "EUR:6548$f.59.**"))
+
+ let amount2: Amount = try! Amount(fromString: "EUR:971.32")
+ XCTAssert(try amount < amount2)
+ XCTAssert(try amount2 > amount)
+ XCTAssert(try (amount + amount2) == Amount(fromString: "EUR:1854.32"))
+ XCTAssert(try (amount2 - amount) == Amount(fromString: "EUR:88.32"))
+ XCTAssertThrowsError(try amount - amount2)
+
+ let amount3: Amount = try! Amount(fromString: "USD:12.34")
+ XCTAssertThrowsError(try amount == amount3)
+ XCTAssertThrowsError(try amount < amount3)
+ XCTAssertThrowsError(try amount > amount3)
+ XCTAssertThrowsError(try amount + amount3)
+ XCTAssertThrowsError(try amount - amount3)
+ }
+
+}