Sources/Currency/CurrencyMint.swift
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftCurrency open source project
//
// Copyright (c) 2020 SwiftCurrency project authors
// Licensed under MIT License
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftCurrency project authors
//
// SPDX-License-Identifier: MIT
//
//===----------------------------------------------------------------------===//
import struct Foundation.Decimal
// MARK: CurrencyIdentifier
extension CurrencyMint {
/// An identifier of a currency.
///
/// Numeric codes are normally between 0-999 in value.
///
/// Alphabetic codes are normally 3 Latin characters, and will be normalized to all capital letters.
public enum CurrencyIdentifier: Equatable, ExpressibleByStringLiteral, ExpressibleByIntegerLiteral {
case alphaCode(String), numericCode(UInt16)
/// Creates an identifier to represent the alphabetic code.
/// - Parameter value: The alphabetic code identifier.
public init(_ value: String) { self = .alphaCode(value.uppercased()) }
/// Creates an identifier to represent the numeric code.
/// - Parameter value: The numeric code identifier.
public init(_ value: UInt16) { self = .numericCode(value) }
public init(stringLiteral value: String) { self.init(value) }
public init(integerLiteral value: UInt16) { self.init(value) }
public static func ==(lhs: CurrencyIdentifier, rhs: CurrencyIdentifier) -> Bool {
switch (lhs, rhs) {
case let (.alphaCode(lhsValue), .alphaCode(rhsValue)): return lhsValue == rhsValue
case let (.numericCode(lhsValue), .numericCode(rhsValue)): return lhsValue == rhsValue
default: return false
}
}
}
}
// MARK: CurrencyMint
/// A generator object that supports creation of type-safe currencies by their alphabetic or numeric code identifiers.
public final class CurrencyMint {
/// A closure that receives a currency identifier and finds a matching concrete currency type.
public typealias IdentifierLookup = (CurrencyIdentifier) -> AnyCurrency.Type?
/// Returns a shared currency generator that only provides ISO 4217 defined currencies.
public static var standard: CurrencyMint { return .init() }
private init() {
self.fallbackLookup = { _ in return nil }
}
/// Creates an instance that will use the provided lookup closure if an identifier doesn't match the ISO 4217 specification.
/// - Parameter fallbackLookup: An escaping closure that will be invoked when a currency's identifier is not found in the ISO 4217 specification.
public init(fallbackLookup: @escaping IdentifierLookup) {
self.fallbackLookup = fallbackLookup
}
/// Creates an instance that will always resolves the provided currency type when ISO 4217 specification lookup fails.
/// - Parameter defaultCurrency: The default currency type to provide when a currency's identifier is not found in the ISO 4217 specification.
public init<T: CurrencyProtocol>(defaultCurrency: T.Type) {
self.fallbackLookup = { _ in return defaultCurrency }
}
private let fallbackLookup: IdentifierLookup
}
// MARK: Factory Methods
extension CurrencyMint {
/// Creates a currency value for the provided identifier.
/// - Parameters:
/// - identifier: The identifier of the currency to be created.
/// - minorUnits: The quantity of minor units the currency value should represent. The default is `0`.
/// - Returns: An instance of a currency that matches the provided identifier with the desired amount; otherwise `nil`.
public func make(identifier: CurrencyIdentifier, minorUnits value: Int64 = .zero) -> AnyCurrency? {
guard let currencyType = self.lookup(identifier) else { return nil }
return currencyType.init(minorUnits: value)
}
/// Creates a currency value for the provided identifier.
/// - Parameters:
/// - identifier: The identifier of the currency to be created.
/// - amount: The amount the currency value should represent.
/// - Returns: An instance of a currency that matches the provided identifier with the desired amount; otherwise `nil`.
public func make(identifier: CurrencyIdentifier, amount value: Decimal) -> AnyCurrency? {
guard let currencyType = self.lookup(identifier) else { return nil }
return currencyType.init(amount: value)
}
private func lookup(_ identifier: CurrencyIdentifier) -> AnyCurrency.Type? {
var typeFound: AnyCurrency.Type? = nil
switch identifier {
case let .alphaCode(value): typeFound = CurrencyMint.lookup(byAlphaCode: value)
case let .numericCode(value): typeFound = CurrencyMint.lookup(byNumCode: value)
}
return typeFound ?? self.fallbackLookup(identifier)
}
}