-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathExamplesOld.swift
More file actions
150 lines (120 loc) · 4.73 KB
/
Copy pathExamplesOld.swift
File metadata and controls
150 lines (120 loc) · 4.73 KB
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// Usage examples for the current XCEValidatableValue API.
import Foundation
import XCERequirement
import XCEValidatableValue
enum OldAPIExample {
// MARK: Value specifications
enum FirstName: SomeValidatableValue {
typealias Raw = String
static let displayName = "First name"
static let displayHint: String? = "How should we address you?"
static let conditionsOnRaw: [Condition<String, Never>] = [
Condition("At least two characters") { $0.count >= 2 }
]
}
enum Nickname: SomeValidatableValue {
typealias Raw = String
static let conditionsOnRaw: [Condition<String, Never>] = [
Condition("At most thirty characters") { $0.count <= 30 }
]
}
enum Email: SomeValidatableValue {
typealias Raw = String
static let conditionsOnRaw: [Condition<String, Never>] = [
Condition("Contains @") { $0.contains("@") },
Condition("Contains a dot after @") { value in
guard let at = value.firstIndex(of: "@") else { return false }
return value[value.index(after: at)...].contains(".")
}
]
}
enum Password: SomeValidatableValue, IsSecretValue {
typealias Raw = String
static let conditionsOnRaw: [Condition<String, Never>] = [
Condition("Between 8 and 30 characters") { (8...30).contains($0.count) },
Condition("Contains an uppercase letter") { $0.contains(where: \.isUppercase) },
Condition("Contains a lowercase letter") { $0.contains(where: \.isLowercase) },
Condition("Contains a digit") { $0.contains { $0.wholeNumberValue != nil } }
]
}
enum Age: SomeValidatableValue {
typealias Raw = String
typealias Valid = Int
static let conditionsOnValid: [Condition<Int, Never>] = [
Condition("At least eighteen") { $0 >= 18 }
]
static func convert(rawValue: String) -> Int? {
Int(rawValue)
}
}
enum PreferredTheme: SomeValidatableValue {
typealias Raw = String
enum Valid: String, Codable, Sendable {
case light, dark
}
}
// MARK: Draft entity and validated model
struct RegistrationForm: SomeValidatableEntity, Codable {
// A missing required value is represented by an optional Required wrapper.
var firstName: Required<FirstName>?
var email: Required<Email>?
var password: Required<Password>?
var age: Required<Age>?
// Non-required values combine an optional wrapper with an optional valid value.
var nickname: NonRequired<Nickname>?
var preferredTheme: NonRequired<PreferredTheme>?
// SomeValidatableEntity discovers these properties through Mirror.
}
struct Registration: Equatable, Sendable {
let firstName: String
let email: String
let password: String
let age: Int
let nickname: String?
let preferredTheme: PreferredTheme.Valid?
}
// MARK: Typical usage
static func makeForm() -> RegistrationForm {
var form = RegistrationForm()
// Raw-value editing happens through wrapper construction and replacement.
form.firstName = .init("Ada")
form.email = .init("ada@example.com")
form.password = .init("CorrectHorse1")
form.age = .init("37")
form.nickname = .init("Countess")
form.preferredTheme = .init("dark")
return form
}
static func draftOverview(_ form: RegistrationForm) -> (name: String?, age: String?) {
(form.firstName?.rawValue, form.age?.rawValue)
}
static func validate(_ form: RegistrationForm) throws -> Registration {
// Reflection finds every SomeValidatable member and aggregates [Error].
try form.validate()
return try Registration(
firstName: form.firstName.validValue,
email: form.email.validValue,
password: form.password.validValue,
age: form.age.validValue,
nickname: form.nickname?.validValue,
preferredTheme: form.preferredTheme?.validValue
)
}
static func validationIssues(_ form: RegistrationForm) -> [Error] {
do {
try form.validate()
return []
} catch ValidationError.invalidEntity(let issues) {
return issues
} catch {
return [error]
}
}
static func inspectPassword(_ form: RegistrationForm) -> ValidatationMetadata {
// The old public API retains the original ValidatationMetadata typo.
form.password.metadata
}
static func encode(_ form: RegistrationForm) throws -> Data {
try JSONEncoder().encode(form)
}
}