-
Notifications
You must be signed in to change notification settings - Fork 0
/
ValidatedString.swift
84 lines (68 loc) · 2.6 KB
/
ValidatedString.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import Foundation
public protocol StringValidator {
/// Returns a sanitized string if the input is valid or nil otherwise
static func validate(_ string: String) -> String?
}
public protocol StringNormalizer {
/// Returns a normalized version of `rawValue`
static func normalize(_ rawValue: String) -> String
}
public protocol StringComparator {
static func areInIncreasingOrder(_ lhs: String, _ rhs: String) -> Bool
}
public struct ValidatedString<Validator: StringValidator> {
public let rawValue: String
var normalized: String {
if let normalizer = Validator.self as? StringNormalizer.Type {
return normalizer.normalize(rawValue)
} else {
return rawValue
}
}
public init?(_ rawValue: String) {
guard let validated = Validator.validate(rawValue) else {return nil}
self.rawValue = validated
}
}
extension ValidatedString: CustomStringConvertible,
CustomDebugStringConvertible,
LosslessStringConvertible {
public var description: String {return rawValue}
public var debugDescription: String {return #"<ValidatedString<\#(Validator.self)>, "\#(rawValue)">"#}
}
extension ValidatedString: Hashable {
public static func == (lhs: ValidatedString<Validator>, rhs: ValidatedString<Validator>) -> Bool {
return lhs.normalized == rhs.normalized
}
public func hash(into hasher: inout Hasher) {
hasher.combine(normalized)
}
}
extension ValidatedString: Comparable where Validator: StringComparator {
public static func < (lhs: ValidatedString<Validator>, rhs: ValidatedString<Validator>) -> Bool {
return Validator.areInIncreasingOrder(lhs.rawValue, rhs.rawValue)
}
}
extension ValidatedString: ExpressibleByStringLiteral {
public typealias StringLiteralType = String
public init(stringLiteral value: StringLiteralType) {
guard let validated = ValidatedString.init(value) else {
fatalError(#"Invalid string literal "\#(value)" for validator \#(Validator.self)"#)
}
self = validated
}
}
extension ValidatedString: Codable {
enum Error: Swift.Error {
case validation
}
public init(from decoder: Decoder) throws {
let decoded = try decoder.singleValueContainer().decode(String.self)
guard let validated = ValidatedString<Validator>(decoded) else {throw Error.validation}
self = validated
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(rawValue)
}
}