summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Buchanan <jonathan.russ.buchanan@gmail.com>2022-06-15 16:38:23 -0400
committerJonathan Buchanan <jonathan.russ.buchanan@gmail.com>2022-06-15 16:38:23 -0400
commit9da8903e826aea2ff2d780b975d085512f0b21fa (patch)
tree1a4eba45a1581850493ad944a148e93c282e22f6
parent00e03ddb0cc3ae1192f312c0f547097e9eeb5d17 (diff)
downloadtaler-ios-9da8903e826aea2ff2d780b975d085512f0b21fa.tar.gz
taler-ios-9da8903e826aea2ff2d780b975d085512f0b21fa.tar.bz2
taler-ios-9da8903e826aea2ff2d780b975d085512f0b21fa.zip
fix amount parsing and add tests to reflect kotlin api tests
-rw-r--r--Taler.xcodeproj/project.pbxproj4
-rw-r--r--Taler.xcodeproj/project.xcworkspace/xcuserdata/jonathan.xcuserdatad/UserInterfaceState.xcuserstatebin30487 -> 35145 bytes
-rw-r--r--TalerTests/TalerTests.swift42
-rw-r--r--taler-swift/Sources/taler-swift/Amount.swift380
-rw-r--r--taler-swift/Sources/taler-swift/taler_swift.swift6
-rw-r--r--taler-swift/Tests/taler-swiftTests/AmountTests.swift145
-rw-r--r--taler-swift/Tests/taler-swiftTests/taler_swiftTests.swift11
7 files changed, 266 insertions, 322 deletions
diff --git a/Taler.xcodeproj/project.pbxproj b/Taler.xcodeproj/project.pbxproj
index a1be70f..d88fb8e 100644
--- a/Taler.xcodeproj/project.pbxproj
+++ b/Taler.xcodeproj/project.pbxproj
@@ -11,7 +11,6 @@
D112510026B12E3200D02E00 /* taler-wallet-embedded.js in CopyFiles */ = {isa = PBXBuildFile; fileRef = D11250FF26B12E3200D02E00 /* taler-wallet-embedded.js */; };
D14AFD2124D232B300C51073 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14AFD2024D232B300C51073 /* AppDelegate.swift */; };
D14AFD2324D232B300C51073 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14AFD2224D232B300C51073 /* SceneDelegate.swift */; };
- D14AFD3824D232B500C51073 /* TalerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14AFD3724D232B500C51073 /* TalerTests.swift */; };
D14AFD4324D232B500C51073 /* TalerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14AFD4224D232B500C51073 /* TalerUITests.swift */; };
D14CE1B226C39E5D00612DBE /* BalanceRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14CE1B126C39E5D00612DBE /* BalanceRow.swift */; };
D14CE1B426C3A2D400612DBE /* BalanceList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14CE1B326C3A2D400612DBE /* BalanceList.swift */; };
@@ -122,7 +121,6 @@
D14AFD2C24D232B500C51073 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
D14AFD2E24D232B500C51073 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D14AFD3324D232B500C51073 /* TalerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TalerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
- D14AFD3724D232B500C51073 /* TalerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TalerTests.swift; sourceTree = "<group>"; };
D14AFD3924D232B500C51073 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D14AFD3E24D232B500C51073 /* TalerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TalerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
D14AFD4224D232B500C51073 /* TalerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TalerUITests.swift; sourceTree = "<group>"; };
@@ -286,7 +284,6 @@
D14AFD3624D232B500C51073 /* TalerTests */ = {
isa = PBXGroup;
children = (
- D14AFD3724D232B500C51073 /* TalerTests.swift */,
D14AFD3924D232B500C51073 /* Info.plist */,
D18DBB5D26DF160D00A4480D /* TimestampTests.swift */,
);
@@ -604,7 +601,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- D14AFD3824D232B500C51073 /* TalerTests.swift in Sources */,
D18DBB5E26DF160D00A4480D /* TimestampTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/Taler.xcodeproj/project.xcworkspace/xcuserdata/jonathan.xcuserdatad/UserInterfaceState.xcuserstate b/Taler.xcodeproj/project.xcworkspace/xcuserdata/jonathan.xcuserdatad/UserInterfaceState.xcuserstate
index f65f460..74ac5da 100644
--- a/Taler.xcodeproj/project.xcworkspace/xcuserdata/jonathan.xcuserdatad/UserInterfaceState.xcuserstate
+++ b/Taler.xcodeproj/project.xcworkspace/xcuserdata/jonathan.xcuserdatad/UserInterfaceState.xcuserstate
Binary files differ
diff --git a/TalerTests/TalerTests.swift b/TalerTests/TalerTests.swift
deleted file mode 100644
index 3200278..0000000
--- a/TalerTests/TalerTests.swift
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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 TalerTests: 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 testExample() throws {
- // This is an example of a functional test case.
- // Use XCTAssert and related functions to verify your tests produce the correct results.
- }
-
- func testPerformanceExample() throws {
- // This is an example of a performance test case.
- self.measure {
- // Put the code you want to measure the time of here.
- }
- }
-
-}
diff --git a/taler-swift/Sources/taler-swift/Amount.swift b/taler-swift/Sources/taler-swift/Amount.swift
index e775a65..b2f005f 100644
--- a/taler-swift/Sources/taler-swift/Amount.swift
+++ b/taler-swift/Sources/taler-swift/Amount.swift
@@ -1,6 +1,6 @@
/*
* This file is part of GNU Taler
- * (C) 2021 Taler Systems S.A.
+ * (C) 2022 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
@@ -16,73 +16,48 @@
import Foundation
-/**
- Errors for `Amount`.
- */
+/// Errors for `Amount`.
enum AmountError: Error {
- /**
- The string cannot be parsed to create an `Amount`.
- */
+ /// The string cannot be parsed to create an `Amount`.
case invalidStringRepresentation
- /**
- Could not compare or operate on two `Amount`s of different currencies.
- */
+ /// Could not compare or operate on two `Amount`s of different currencies.
case incompatibleCurrency
- /**
- The amount is invalid. The value is either greater than the maximum, or the currency is the empty string.
- */
+ /// The amount is invalid. The value is either greater than the maximum, or the currency string is not 1-12 characters long.
case invalidAmount
- /**
- The result of the operation would yield a negative amount.
- */
+ /// The result of the operation would yield a negative amount.
case negativeAmount
- /**
- The operation was division by zero.
- */
+ /// The operation was division by zero.
case divideByZero
}
-/**
- A value of a currency.
- */
+/// A value of some currency.
public class Amount: Codable, CustomStringConvertible {
- /**
- The largest possible value that can be represented.
- */
+ /// Format that a currency must match.
+ private static let currencyRegex = #"^[-_*A-Za-z0-9]{1,12}$"#
+
+ /// The largest possible value that can be represented.
private static let maxValue: UInt64 = 1 << 52
- /**
- The size of `value` in relation to `fraction`.
- */
+ /// The size of `value` in relation to `fraction`.
private static let fractionalBase: UInt32 = 100000000
- /**
- The greatest number of decimal digits that can be represented.
- */
+ /// The greatest number of decimal digits that can be represented.
private static let fractionalBaseDigits: UInt = 8
- /**
- The currency of the amount.
- */
+ /// The currency of the amount.
var currency: String
- /**
- The value of the amount (number to the left of the decimal point).
- */
+ /// The value of the amount (number to the left of the decimal point).
var value: UInt64
- /**
- The fractional value of the amount (number to the right of the decimal point).
- */
+ /// The fractional value of the amount (number to the right of the decimal point).
var fraction: UInt32
- /**
- The string representation of the amount, formatted as "`currency`:`value`.`fraction`".
- */
+ /// The string representation of the amount, formatted as "`currency`:`value`.`fraction`".
public var description: String {
if fraction == 0 {
return "\(currency):\(value)"
@@ -97,135 +72,105 @@ public class Amount: Codable, CustomStringConvertible {
}
}
- /**
- Whether the value is valid. An amount is valid if and only if the currency is not empty and the value is less than the maximum allowed value.
- */
+ /// Whether the value is valid. An amount is valid if and only if the currency is not empty and the value is less than the maximum allowed value.
var valid: Bool {
+ if currency.range(of: Amount.currencyRegex, options: .regularExpression) == nil {
+ return false
+ }
return (value <= Amount.maxValue && currency != "")
}
- /**
- Initializes an amount by parsing a string representing the amount. The string should be formatted as "`currency`:`value`.`fraction`".
-
- - Parameters:
- - fromString: The string to parse.
-
- - Throws:
- - `AmountError.invalidStringRepresentation` if the string cannot be parsed.
- - `AmountError.invalidAmount` if the string can be parsed, but the resulting amount is not valid.
- */
+ /// Whether this amount is zero or not.
+ var isZero: Bool {
+ return value == 0 && fraction == 0
+ }
+
+ /// Initializes an amount by parsing a string representing the amount. The string should be formatted as "`currency`:`value`.`fraction`".
+ /// - Parameters:
+ /// - fromString: The string to parse.
+ /// - Throws:
+ /// - `AmountError.invalidStringRepresentation` if the string cannot be parsed.
+ /// - `AmountError.invalidAmount` if the string can be parsed, but the resulting amount is not valid.
public 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
+ if let separatorIndex = string.firstIndex(of: ":") {
+ 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)...])
+ if (fractionStr.count > Amount.fractionalBaseDigits) {
+ throw AmountError.invalidStringRepresentation
+ }
+ 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
}
} else {
- guard let _value = UInt64(amountStr) else { throw AmountError.invalidStringRepresentation }
- self.value = _value
+ self.currency = string
+ self.value = 0
self.fraction = 0
}
guard self.valid else { throw AmountError.invalidAmount }
}
- /**
- Initializes an amount with the specified currency, value, and fraction.
-
- - Parameters:
- - currency: The currency of the amount.
- - value: The value of the amount (number to the left of the decimal point).
- - fraction: The fractional value of the amount (number to the right of the decimal point).
- */
- init(currency: String, value: UInt64, fraction: UInt32) {
+ /// Initializes an amount with the specified currency, value, and fraction.
+ /// - Parameters:
+ /// - currency: The currency of the amount.
+ /// - value: The value of the amount (number to the left of the decimal point).
+ /// - fraction: The fractional value of the amount (number to the right of the decimal point).
+ public init(currency: String, value: UInt64, fraction: UInt32) {
self.currency = currency
self.value = value
self.fraction = fraction
}
- /**
- Initializes an amount from a decoder.
-
- - Parameters:
- - fromDecoder: The decoder to extract the amount from.
-
- - Throws:
- - `AmountError.invalidStringRepresentation` if the string cannot be parsed.
- - `AmountError.invalidAmount` if the string can be parsed, but the resulting amount is not valid.
- */
- init(fromDecoder decoder: Decoder) throws {
+ /// Initializes an amount from a decoder.
+ /// - Parameters:
+ /// - from: The decoder to extract the amount from.
+ /// - Throws:
+ /// - `AmountError.invalidStringRepresentation` if the string cannot be parsed.
+ /// - `AmountError.invalidAmount` if the string can be parsed, but the resulting amount is not valid.
+ required public convenience init(from 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 }
+ try self.init(fromString: string)
}
- /**
- Copies an amount.
-
- - Returns: A copy of the amount.
- */
+ /// Copies an amount.
+ /// - Returns: A copy of the amount.
func copy() -> Amount {
return Amount(currency: self.currency, value: self.value, fraction: self.fraction)
}
- /**
- Creates a normalized copy of an amount.
-
- - Returns: A copy of the amount that has been normalized
- */
+ /// Creates a normalized copy of an amount (the fractional part is strictly less than one unit of currency).
+ /// - Returns: A copy of the amount that has been normalized
func normalizedCopy() throws -> Amount {
let amount = self.copy()
try amount.normalize()
return amount
}
- /**
- Encodes an amount.
-
- - Parameters:
- - to: The encoder to encode the amount with.
- */
+ /// Encodes an amount.
+ /// - Parameters:
+ /// - to: The encoder to encode the amount with.
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.description)
}
- /**
- Normalizes an amount by reducing `fraction` until it is less than `Amount.fractionalBase`, increasing `value` appropriately.
-
- - Throws:
- - `AmountError.invalidAmount` if the amount is invalid either before or after normalization.
- */
+ /// Normalizes an amount by reducing `fraction` until it is less than `Amount.fractionalBase`, increasing `value` appropriately.
+ /// - Throws:
+ /// - `AmountError.invalidAmount` if the amount is invalid either before or after normalization.
func normalize() throws {
if !valid {
throw AmountError.invalidAmount
@@ -237,18 +182,13 @@ public class Amount: Codable, CustomStringConvertible {
}
}
- /**
- Adds two amounts together.
-
- - Parameters:
- - left: The amount on the left.
- - right: The amount on the right.
-
- - Throws:
- - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency.
-
- - Returns: The sum of `left` and `right`, normalized.
- */
+ /// Adds two amounts together.
+ /// - Parameters:
+ /// - left: The amount on the left.
+ /// - right: The amount on the right.
+ /// - Throws:
+ /// - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency.
+ /// - Returns: The sum of `left` and `right`, normalized.
static func + (left: Amount, right: Amount) throws -> Amount {
if left.currency != right.currency {
throw AmountError.incompatibleCurrency
@@ -262,18 +202,13 @@ public class Amount: Codable, CustomStringConvertible {
return result
}
- /**
- Subtracts one amount from another.
-
- - Parameters:
- - left: The amount on the left.
- - right: The amount on the right.
-
- - Throws:
- - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency.
-
- - Returns: The difference of `left` and `right`, normalized.
- */
+ /// Subtracts one amount from another.
+ /// - Parameters:
+ /// - left: The amount on the left.
+ /// - right: The amount on the right.
+ /// - Throws:
+ /// - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency.
+ /// - Returns: The difference of `left` and `right`, normalized.
static func - (left: Amount, right: Amount) throws -> Amount {
if left.currency != right.currency {
throw AmountError.incompatibleCurrency
@@ -293,15 +228,11 @@ public class Amount: Codable, CustomStringConvertible {
return diff
}
- /**
- Divides an amount by a scalar, possibly introducing rounding error.
-
- - Parameters:
- - dividend: The amount to divide.
- - divisor: The scalar dividing `dividend`.
-
- - Returns: The quotient of `dividend` and `divisor`, normalized.
- */
+ /// Divides an amount by a scalar, possibly introducing rounding error.
+ /// - Parameters:
+ /// - dividend: The amount to divide.
+ /// - divisor: The scalar dividing `dividend`.
+ /// - Returns: The quotient of `dividend` and `divisor`, normalized.
static func / (dividend: Amount, divisor: UInt32) throws -> Amount {
guard divisor != 0 else { throw AmountError.divideByZero }
let result = try dividend.normalizedCopy()
@@ -316,15 +247,11 @@ public class Amount: Codable, CustomStringConvertible {
return result
}
- /**
- Multiply an amount by a scalar.
-
- - Parameters:
- - amount: The amount to multiply.
- - factor: The scalar multiplying `amount`.
-
- - Returns: The product of `amount` and `factor`, normalized.
- */
+ /// Multiply an amount by a scalar.
+ /// - Parameters:
+ /// - amount: The amount to multiply.
+ /// - factor: The scalar multiplying `amount`.
+ /// - Returns: The product of `amount` and `factor`, normalized.
static func * (amount: Amount, factor: UInt32) throws -> Amount {
let result = try amount.normalizedCopy()
result.value = result.value * UInt64(factor)
@@ -334,19 +261,14 @@ public class Amount: Codable, CustomStringConvertible {
return result
}
- /**
- Compares two amounts.
-
- - Parameters:
- - left: The first amount.
- - right: The second amount.
-
- - Throws:
- - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency.
-
- - Returns: `true` if and only if the amounts have the same `value` and `fraction` after normalization, `false` otherwise.
- */
- static func == (left: Amount, right: Amount) throws -> Bool {
+ /// Compares two amounts.
+ /// - Parameters:
+ /// - left: The first amount.
+ /// - right: The second amount.
+ /// - Throws:
+ /// - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency.
+ /// - Returns: `true` if and only if the amounts have the same `value` and `fraction` after normalization, `false` otherwise.
+ public static func == (left: Amount, right: Amount) throws -> Bool {
if left.currency != right.currency {
throw AmountError.incompatibleCurrency
}
@@ -355,19 +277,14 @@ public class Amount: Codable, CustomStringConvertible {
return (leftNormalized.value == rightNormalized.value && leftNormalized.fraction == rightNormalized.fraction)
}
- /**
- Compares two amounts.
-
- - Parameters:
- - left: The amount on the left.
- - right: The amount on the right.
-
- - Throws:
- - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency.
-
- - Returns: `true` if and only if `left` is smaller than `right` after normalization, `false` otherwise.
- */
- static func < (left: Amount, right: Amount) throws -> Bool {
+ /// Compares two amounts.
+ /// - Parameters:
+ /// - left: The amount on the left.
+ /// - right: The amount on the right.
+ /// - Throws:
+ /// - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency.
+ /// - Returns: `true` if and only if `left` is lesser than `right` after normalization, `false` otherwise.
+ public static func < (left: Amount, right: Amount) throws -> Bool {
if left.currency != right.currency {
throw AmountError.incompatibleCurrency
}
@@ -380,30 +297,43 @@ public class Amount: Codable, CustomStringConvertible {
}
}
- /**
- Compares two amounts.
-
- - Parameters:
- - left: The amount on the left.
- - right: The amount on the right.
-
- - Throws:
- - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency.
-
- - Returns: `true` if and only if `left` is bigger than `right` after normalization, `false` otherwise.
- */
- static func > (left: Amount, right: Amount) throws -> Bool {
+ /// Compares two amounts.
+ /// - Parameters:
+ /// - left: The amount on the left.
+ /// - right: The amount on the right.
+ /// - Throws:
+ /// - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency.
+ /// - Returns: `true` if and only if `left` is lesser than or equal to `right` after normalization, `false` otherwise.
+ public static func <= (left: Amount, right: Amount) throws -> Bool {
+ return try left < right || left == right
+ }
+
+ /// Compares two amounts.
+ /// - Parameters:
+ /// - left: The amount on the left.
+ /// - right: The amount on the right.
+ /// - Throws:
+ /// - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency.
+ /// - Returns: `true` if and only if `left` is greater than `right` after normalization, `false` otherwise.
+ public static func > (left: Amount, right: Amount) throws -> Bool {
return try right < left
}
- /**
- Creates the amount representing zero in a given currency.
-
- - Parameters:
- - currency: The currency to use.
-
- - Returns: The zero amount for `currency`.
- */
+ /// Compares two amounts.
+ /// - Parameters:
+ /// - left: The amount on the left.
+ /// - right: The amount on the right.
+ /// - Throws:
+ /// - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency.
+ /// - Returns: `true` if and only if `left` is greater than or equal `right` after normalization, `false` otherwise.
+ public static func >= (left: Amount, right: Amount) throws -> Bool {
+ return try left > right || left == right
+ }
+
+ /// Creates the amount representing zero in a given currency.
+ /// - Parameters:
+ /// - currency: The currency to use.
+ /// - Returns: The zero amount for `currency`.
static func zero(currency: String) -> Amount {
return Amount(currency: currency, value: 0, fraction: 0)
}
diff --git a/taler-swift/Sources/taler-swift/taler_swift.swift b/taler-swift/Sources/taler-swift/taler_swift.swift
deleted file mode 100644
index 79438a4..0000000
--- a/taler-swift/Sources/taler-swift/taler_swift.swift
+++ /dev/null
@@ -1,6 +0,0 @@
-public struct taler_swift {
- public private(set) var text = "Hello, World!"
-
- public init() {
- }
-}
diff --git a/taler-swift/Tests/taler-swiftTests/AmountTests.swift b/taler-swift/Tests/taler-swiftTests/AmountTests.swift
index 6cbbf77..58f981f 100644
--- a/taler-swift/Tests/taler-swiftTests/AmountTests.swift
+++ b/taler-swift/Tests/taler-swiftTests/AmountTests.swift
@@ -15,10 +15,9 @@
*/
import XCTest
-@testable import Taler
+@testable import taler_swift
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.
}
@@ -27,41 +26,119 @@ class AmountTests: XCTestCase {
// 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")
+ func testParsing() {
+ var str = "TESTKUDOS:23.42"
+ var amt = try! Amount(fromString: str)
+ XCTAssert(str == amt.description)
+ XCTAssert("TESTKUDOS" == amt.currency)
+ XCTAssert(23 == amt.value)
+ XCTAssert(UInt64(0.42 * 1e8) == amt.fraction)
- XCTAssertThrowsError(try Amount(fromString: "EUR:6548$f.59.**"))
+ str = "EUR:500000000.00000001"
+ amt = try! Amount(fromString: str)
+ XCTAssert(str == amt.description)
+ XCTAssert("EUR" == amt.currency)
+ XCTAssert(500000000 == amt.value)
+ XCTAssert(1 == amt.fraction)
- 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)
+ str = "EUR:1500000000.00000003"
+ amt = try! Amount(fromString: str)
+ XCTAssert(str == amt.description)
+ XCTAssert("EUR" == amt.currency)
+ XCTAssert(1500000000 == amt.value)
+ XCTAssert(3 == amt.fraction)
- 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)
+ let maxValue = 4503599627370496
+ str = "TESTKUDOS123:\(maxValue).99999999"
+ amt = try! Amount(fromString: str)
+ XCTAssert(str == amt.description)
+ XCTAssert("TESTKUDOS123" == amt.currency)
+ XCTAssert(maxValue == amt.value)
- XCTAssertThrowsError(try amount3 / 0)
- XCTAssert(try (amount3 / 1) == amount3)
- XCTAssert(try (amount3 / 3) == Amount(fromString: "USD:4.11333333"))
- XCTAssert(try (amount3 * 5) == Amount(fromString: "USD:61.7"))
+ XCTAssertThrowsError(try Amount(fromString: "TESTKUDOS1234:\(maxValue).99999999"))
+ XCTAssertThrowsError(try Amount(fromString: "TESTKUDOS123:\(maxValue + 1).99999999"))
+ XCTAssertThrowsError(try Amount(fromString: "TESTKUDOS123:\(maxValue).999999990"))
+ XCTAssertThrowsError(try Amount(fromString: "TESTKUDOS:0,5"))
+ XCTAssertThrowsError(try Amount(fromString: "+TESTKUDOS:0.5"))
+ XCTAssertThrowsError(try Amount(fromString: "0.5"))
+ XCTAssertThrowsError(try Amount(fromString: ":0.5"))
+ XCTAssertThrowsError(try Amount(fromString: "EUR::0.5"))
+ XCTAssertThrowsError(try Amount(fromString: "EUR:.5"))
+ }
+
+ func testAddition() {
+ XCTAssert(try Amount(fromString: "EUR:1") + Amount(fromString: "EUR:1") == Amount(fromString: "EUR:2"))
+ XCTAssert(try Amount(fromString: "EUR:1.5") + Amount(fromString: "EUR:1.5") == Amount(fromString: "EUR:3"))
+ XCTAssert(try Amount(fromString: "EUR:500000000.00000001") + Amount(fromString: "EUR:0.00000001") == Amount(fromString: "EUR:500000000.00000002"))
+ XCTAssertThrowsError(try Amount(fromString: "EUR:1") + Amount(fromString: "USD:1"))
+ XCTAssertThrowsError(try Amount(fromString: "EUR:4503599627370496.99999999") + Amount(fromString: "EUR:4503599627370496.99999999"))
+ XCTAssertThrowsError(try Amount(fromString: "EUR:4000000000000000") + Amount(fromString: "EUR:4000000000000000"))
+ }
+
+ func testSubtraction() {
+ XCTAssert(try Amount(fromString: "EUR:1") - Amount(fromString: "EUR:1") == Amount(fromString: "EUR:0"))
+ XCTAssert(try Amount(fromString: "EUR:3") - Amount(fromString: "EUR:1.5") == Amount(fromString: "EUR:1.5"))
+ XCTAssert(try Amount(fromString: "EUR:500000000.00000002") - Amount(fromString: "EUR:0.00000001") == Amount(fromString: "EUR:500000000.00000001"))
+ XCTAssertThrowsError(try Amount(fromString: "EUR:3") - Amount(fromString: "USD:1.5"))
+ XCTAssertThrowsError(try Amount(fromString: "EUR:23.42") - Amount(fromString: "EUR:42.23"))
+ XCTAssertThrowsError(try Amount(fromString: "EUR:0.5") - Amount(fromString: "EUR:0.50000001"))
+ }
+
+ func testMultiplication() {
+ XCTAssert(try Amount(fromString: "EUR:2") * 1 == Amount(fromString: "EUR:2"))
+ XCTAssert(try Amount(fromString: "EUR:1") * 2 == Amount(fromString: "EUR:2"))
+ XCTAssert(try Amount(fromString: "EUR:1.5") * 3 == Amount(fromString: "EUR:4.5"))
+ XCTAssert(try Amount(fromString: "EUR:1.11") * 0 == Amount(fromString: "EUR:0"))
+ XCTAssert(try Amount(fromString: "EUR:1.11") * 1 == Amount(fromString: "EUR:1.11"))
+ XCTAssert(try Amount(fromString: "EUR:1.11") * 2 == Amount(fromString: "EUR:2.22"))
+ XCTAssert(try Amount(fromString: "EUR:1.11") * 3 == Amount(fromString: "EUR:3.33"))
+ XCTAssert(try Amount(fromString: "EUR:1.11") * 4 == Amount(fromString: "EUR:4.44"))
+ XCTAssert(try Amount(fromString: "EUR:1.11") * 5 == Amount(fromString: "EUR:5.55"))
+ XCTAssert(try Amount(fromString: "EUR:500000000.00000001") * 3 == Amount(fromString: "EUR:1500000000.00000003"))
+ XCTAssertThrowsError(try Amount(fromString: "4000000000000000") * 2)
+ }
+
+ func testDivision() {
+ XCTAssert(try Amount(fromString: "EUR:2") / 1 == Amount(fromString: "EUR:2"))
+ XCTAssert(try Amount(fromString: "EUR:2") / 2 == Amount(fromString: "EUR:1"))
+ XCTAssert(try Amount(fromString: "EUR:4.5") / 3 == Amount(fromString: "EUR:1.5"))
+ XCTAssert(try Amount(fromString: "EUR:0") / 5 == Amount(fromString: "EUR:0"))
+ XCTAssert(try Amount(fromString: "EUR:1.11") / 1 == Amount(fromString: "EUR:1.11"))
+ XCTAssert(try Amount(fromString: "EUR:2.22") / 2 == Amount(fromString: "EUR:1.11"))
+ XCTAssert(try Amount(fromString: "EUR:3.33") / 3 == Amount(fromString: "EUR:1.11"))
+ XCTAssert(try Amount(fromString: "EUR:4.44") / 4 == Amount(fromString: "EUR:1.11"))
+ XCTAssert(try Amount(fromString: "EUR:5.55") / 5 == Amount(fromString: "EUR:1.11"))
+ XCTAssert(try Amount(fromString: "EUR:1500000000.00000003") / 3 == Amount(fromString: "EUR:500000000.00000001"))
+ XCTAssert(try Amount(fromString: "EUR:0.00000003") / 2 == Amount(fromString: "EUR:0.00000001"))
+ XCTAssertThrowsError(try Amount(fromString: "EUR:1") / 0)
+ }
+
+ func testZero() {
+ XCTAssert(try Amount(fromString: "EUR") == Amount.zero(currency: "EUR"))
+ XCTAssert(Amount.zero(currency: "EUR").isZero)
+ XCTAssert(try Amount(fromString: "EUR:0").isZero)
+ XCTAssert(try Amount(fromString: "EUR:0.0").isZero)
+ XCTAssert(try Amount(fromString: "EUR:0.00000").isZero)
+ XCTAssert(try (Amount(fromString: "EUR:1.001") - Amount(fromString: "EUR:1.001")).isZero)
+ XCTAssert(try !Amount(fromString: "EUR:0.00000001").isZero)
+ XCTAssert(try !Amount(fromString: "EUR:1.0").isZero)
+ XCTAssert(try !Amount(fromString: "EUR:0001.0").isZero)
+ }
+
+ func testComparison() {
+ XCTAssert(try Amount(fromString: "EUR:0") <= Amount(fromString: "EUR:0"))
+ XCTAssert(try Amount(fromString: "EUR:0") <= Amount(fromString: "EUR:0.00000001"))
+ XCTAssert(try Amount(fromString: "EUR:0") < Amount(fromString: "EUR:0.00000001"))
+ XCTAssert(try Amount(fromString: "EUR:0") < Amount(fromString: "EUR:1"))
+ XCTAssert(try Amount(fromString: "EUR:0") == Amount(fromString: "EUR:0"))
+ XCTAssert(try Amount(fromString: "EUR:42") == Amount(fromString: "EUR:42"))
+ XCTAssert(try Amount(fromString: "EUR:42.00000001") == Amount(fromString: "EUR:42.00000001"))
+ XCTAssert(try Amount(fromString: "EUR:42.00000001") >= Amount(fromString: "EUR:42.00000001"))
+ XCTAssert(try Amount(fromString: "EUR:42.00000002") >= Amount(fromString: "EUR:42.00000001"))
+ XCTAssert(try Amount(fromString: "EUR:42.00000002") > Amount(fromString: "EUR:42.00000001"))
+ XCTAssert(try Amount(fromString: "EUR:0.00000002") > Amount(fromString: "EUR:0.00000001"))
+ XCTAssert(try Amount(fromString: "EUR:0.00000001") > Amount(fromString: "EUR:0"))
+ XCTAssert(try Amount(fromString: "EUR:2") > Amount(fromString: "EUR:1"))
+ XCTAssertThrowsError(try Amount(fromString: "EUR:0.5") < Amount(fromString: "USD:0.50000001"))
}
-
}
diff --git a/taler-swift/Tests/taler-swiftTests/taler_swiftTests.swift b/taler-swift/Tests/taler-swiftTests/taler_swiftTests.swift
deleted file mode 100644
index d7b1c8f..0000000
--- a/taler-swift/Tests/taler-swiftTests/taler_swiftTests.swift
+++ /dev/null
@@ -1,11 +0,0 @@
-import XCTest
-@testable import taler_swift
-
-final class taler_swiftTests: XCTestCase {
- func testExample() throws {
- // This is an example of a functional test case.
- // Use XCTAssert and related functions to verify your tests produce the correct
- // results.
- XCTAssertEqual(taler_swift().text, "Hello, World!")
- }
-}