-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathExamplesNew.swift
More file actions
146 lines (116 loc) · 4.52 KB
/
Copy pathExamplesNew.swift
File metadata and controls
146 lines (116 loc) · 4.52 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
// Usage examples for the proposed single-file Swift 6 API.
import Foundation
import XCERequirement
enum NewAPIExample {
// MARK: Value specifications
enum FirstName: ValueSpecification {
typealias Raw = String
static let field = ValidationField(
name: "First name",
placeholder: "First name",
hint: "How should we address you?"
)
static let conditionsOnRaw: [Condition<String, Never>] = [
Condition("At least two characters") { $0.count >= 2 }
]
}
enum Nickname: ValueSpecification {
typealias Raw = String
static let conditionsOnRaw: [Condition<String, Never>] = [
Condition("At most thirty characters") { $0.count <= 30 }
]
}
enum Email: ValueSpecification {
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: ValueSpecification, SecretValue {
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: ValueSpecification {
typealias Raw = String
typealias Valid = Int
enum ConversionFailure: Error, Sendable {
case notAnInteger
}
static let conditionsOnValid: [Condition<Int, Never>] = [
Condition("At least eighteen") { $0 >= 18 }
]
static func convert(_ rawValue: String) throws(ConversionFailure) -> Int {
guard let age = Int(rawValue) else { throw .notAnInteger }
return age
}
}
enum PreferredTheme: ValueSpecification {
typealias Raw = String
enum Valid: String, Codable, Sendable {
case light, dark
}
}
// MARK: Draft entity and validated model
struct RegistrationForm: ValidatableEntity, Codable {
// The wrapped properties expose editable Raw? values directly.
@Required<FirstName> var firstName: String? = nil
@Required<Email> var email: String? = nil
@Required<Password> var password: String? = nil
@Required<Age> var age: String? = nil
@NonRequired<Nickname> var nickname: String? = nil
@NonRequired<PreferredTheme> var preferredTheme: String? = nil
// The synthesized wrapper storage is discovered automatically.
}
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()
// Property-wrapper syntax keeps ordinary raw-value editing lightweight.
form.firstName = "Ada"
form.email = "ada@example.com"
form.password = "CorrectHorse1"
form.age = "37"
form.nickname = "Countess"
form.preferredTheme = "dark"
return form
}
static func draftOverview(_ form: RegistrationForm) -> (name: String?, age: String?) {
(form.firstName, form.age)
}
static func validate(_ form: RegistrationForm) throws(ValidationError) -> Registration {
try form.validate()
return try Registration(
firstName: form.$firstName.value(),
email: form.$email.value(),
password: form.$password.value(),
age: form.$age.value(),
nickname: form.$nickname.value(),
preferredTheme: form.$preferredTheme.value()
)
}
static func validationIssues(_ form: RegistrationForm) -> [ValidationIssue] {
form.validationIssues()
}
static func inspectPassword(_ form: RegistrationForm) -> ValidationMetadata {
form.$password.metadata
}
static func encode(_ form: RegistrationForm) throws -> Data {
try JSONEncoder().encode(form)
}
}