forked from gsabran/apollo-ios
-
Notifications
You must be signed in to change notification settings - Fork 0
/
GraphQLNullable.swift
276 lines (249 loc) · 9.21 KB
/
GraphQLNullable.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
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
/// Indicates the presence of a value, supporting both `nil` and `null` values.
///
/// ``GraphQLNullable`` is generally only used for setting input values on generated ``GraphQLOperation`` objects.
///
/// In GraphQL, explicitly providing a `null` value for an input value to a field argument is
/// semantically different from not providing a value at all (`nil`). This enum allows you to
/// distinguish your input values between `null` and `nil`.
///
/// # Usage
///
/// ``GraphQLNullable`` provides a similar interface to Swift's `Optional`, however in addition to
/// the `.some(Wrapped)` and `.none` cases, it provides an additional `.null` case. The `.null`
/// case parallels `NSNull`, while `.none` paralells `nil`. This requires you to specify either
/// a `.null` or an omitted (`.none`) value when constructing the query object. The `nil` literal
/// also translates to the `.none` case.
///
/// ```swift
/// public init(inputValue: GraphQLNullable<String>)
///
/// // Null Value:
/// .init(episode: .null)
///
/// // Nil Value:
/// .init(episode: .none) or .init(episode: nil)
/// ```
///
/// ### Literals, Enums, and InputObjects
/// For Swift scalar types that have an `ExpressibleBy...Literal` protocol, ``GraphQLNullable``
/// can be initialized with a literal value.
///
/// ```swift
/// // String
/// public init(inputValue: GraphQLNullable<String>)
///
/// .init(inputValue: "InputValueString")
///
/// // Integer Array
/// public init(inputValue: GraphQLNullable<[Int]>)
///
/// .init(inputValue: [1, 2, 4, 5, 3, 6]) // The best order for watching the Star Wars movies.
/// ```
/// To provide a value for a ``GraphQLEnum`` or ``InputObject`` value, you need to wrap it in a
/// ``GraphQLNullable``, either with the provided convenience initializer, or by using the `.some`
/// case.
///
/// ```swift
/// /// Enum
/// public init(episode: GraphQLNullable<GraphQLEnum<StarWarsAPI.Episode>>)
///
/// .init(episode: GraphQLNullable(.empire))
/// or
/// .init(episode: .some(.empire))
///
/// // Input Object
/// public init(inputValue: GraphQLNullable<CustomInputObject>)
///
/// .init(inputValue: GraphQLNullable(CustomInputObject()))
/// or
/// .init(inputValue: .some(CustomInputObject()))
/// ```
/// ### Optional Values with the Nil Coalescing Operator
///
/// You can initialize a ``GraphQLNullable`` with an optional value, but you'll need to indicate
/// the default value to use if the optional is `nil`. Usually this value is either
/// ``none`` or ``null``.
/// ```swift
/// let optionalValue: String?
///
/// .init(inputValue: optionalValue ?? .none)
/// or
/// .init(inputValue: optionalValue ?? .null)
/// ```
///
/// ### Accessing Properties on Wrapped Objects
///
/// ``GraphQLNullable`` uses `@dynamicMemberLookup` to provide access to properties on the wrapped
/// object without unwrapping.
///
/// ```swift
/// let nullableString: GraphQLNullable<String> = "MyString"
/// print(nullableString.count) // 8
/// ```
/// You can also unwrap the wrapped value to access it directly.
/// ```swift
/// let nullableString: GraphQLNullable<String> = "MyString"
/// if let string = nullableString.unwrapped {
/// print(string.count) // 8
/// }
/// ```
/// # See Also
/// [GraphQLSpec - Input Values - Null Value](http://spec.graphql.org/October2021/#sec-Null-Value)
@dynamicMemberLookup
public enum GraphQLNullable<Wrapped> {
/// The absence of a value.
/// Functionally equivalent to `nil`.
case none
/// The presence of an explicity null value.
/// Functionally equivalent to `NSNull`
case null
/// The presence of a value, stored as `Wrapped`
case some(Wrapped)
/// The wrapped value if one exists, `nil` if the receiver is ``none`` or ``null``.
///
/// This property can be used to use a `GraphQLNullable` as you would an `Optional`.
/// ```swift
/// let childProperty: String? = nullableInputObject.unwrapped?.childProperty
/// ```
@inlinable public var unwrapped: Wrapped? {
guard case let .some(wrapped) = self else { return nil }
return wrapped
}
/// Subscript for `@dynamicMemberLookup`. Accesses values on the wrapped type.
/// Will return `nil` if the receiver is ``none`` or ``null``.
///
/// This dynamic member subscript allows you to optionally access properties of the wrapped value.
/// ```swift
/// let childProperty: String? = nullableInputObject.childProperty
/// ```
@inlinable public subscript<T>(dynamicMember path: KeyPath<Wrapped, T>) -> T? {
unwrapped?[keyPath: path]
}
}
// MARK: - ExpressibleBy Literal Extensions
extension GraphQLNullable: ExpressibleByNilLiteral {
/// The `ExpressibleByNilLiteral` Initializer. Initializes as ``none``.
///
/// This initializer allows you to initialize a ``GraphQLNullable`` by assigning `nil`.
/// ```swift
/// let GraphQLNullable<String> = nil // .none
/// ```
@inlinable public init(nilLiteral: ()) {
self = .none
}
}
extension GraphQLNullable: ExpressibleByUnicodeScalarLiteral
where Wrapped: ExpressibleByUnicodeScalarLiteral {
@inlinable public init(unicodeScalarLiteral value: Wrapped.UnicodeScalarLiteralType) {
self = .some(Wrapped(unicodeScalarLiteral: value))
}
}
extension GraphQLNullable: ExpressibleByExtendedGraphemeClusterLiteral
where Wrapped: ExpressibleByExtendedGraphemeClusterLiteral {
@inlinable public init(extendedGraphemeClusterLiteral value: Wrapped.ExtendedGraphemeClusterLiteralType) {
self = .some(Wrapped(extendedGraphemeClusterLiteral: value))
}
}
extension GraphQLNullable: ExpressibleByStringLiteral
where Wrapped: ExpressibleByStringLiteral {
@inlinable public init(stringLiteral value: Wrapped.StringLiteralType) {
self = .some(Wrapped(stringLiteral: value))
}
}
extension GraphQLNullable: ExpressibleByIntegerLiteral
where Wrapped: ExpressibleByIntegerLiteral {
@inlinable public init(integerLiteral value: Wrapped.IntegerLiteralType) {
self = .some(Wrapped(integerLiteral: value))
}
}
extension GraphQLNullable: ExpressibleByFloatLiteral
where Wrapped: ExpressibleByFloatLiteral {
@inlinable public init(floatLiteral value: Wrapped.FloatLiteralType) {
self = .some(Wrapped(floatLiteral: value))
}
}
extension GraphQLNullable: ExpressibleByBooleanLiteral
where Wrapped: ExpressibleByBooleanLiteral {
@inlinable public init(booleanLiteral value: Wrapped.BooleanLiteralType) {
self = .some(Wrapped(booleanLiteral: value))
}
}
extension GraphQLNullable: ExpressibleByArrayLiteral
where Wrapped: _InitializableByArrayLiteralElements {
@inlinable public init(arrayLiteral elements: Wrapped.ArrayLiteralElement...) {
self = .some(Wrapped(elements))
}
}
extension GraphQLNullable: ExpressibleByDictionaryLiteral
where Wrapped: _InitializableByDictionaryLiteralElements {
@inlinable public init(dictionaryLiteral elements: (Wrapped.Key, Wrapped.Value)...) {
self = .some(Wrapped(elements))
}
}
/// A helper protocol used to enable wrapper types to conform to `ExpressibleByArrayLiteral`.
/// Used by ``GraphQLNullable/init(arrayLiteral:)``
public protocol _InitializableByArrayLiteralElements: ExpressibleByArrayLiteral {
init(_ array: [ArrayLiteralElement])
}
extension Array: _InitializableByArrayLiteralElements {}
/// A helper protocol used to enable wrapper types to conform to `ExpressibleByDictionaryLiteral`.
/// Used by ``GraphQLNullable/init(dictionaryLiteral:)``
public protocol _InitializableByDictionaryLiteralElements: ExpressibleByDictionaryLiteral {
init(_ elements: [(Key, Value)])
}
extension Dictionary: _InitializableByDictionaryLiteralElements {
@inlinable public init(_ elements: [(Key, Value)]) {
self.init(uniqueKeysWithValues: elements)
}
}
// MARK: - Custom Type Initialization
public extension GraphQLNullable {
/// Initializer for use with a ``GraphQLEnum`` value.
///
/// This initializer enables easier creation of the ``GraphQLNullable`` and ``GraphQLEnum``.
/// ```swift
/// let value: GraphQLNullable<GraphQLEnum<StarWarsAPI.Episode>>
///
/// value = .init(.NEWHOPE)
/// // Instead of
/// value = .init(.case(.NEWHOPE))
/// ```
/// - Parameter caseValue: A case of a generated ``EnumType`` to initialize a
/// `GraphQLNullable<GraphQLEnum<T>` with.
@inlinable init<T: EnumType>(_ caseValue: T) where Wrapped == GraphQLEnum<T> {
self = .some(Wrapped(caseValue))
}
/// Intializer for use with an ``InputObject`` value.
/// - Parameter object: The ``InputObject`` to initalize a ``GraphQLNullable`` with.
@inlinable init(_ object: Wrapped) where Wrapped: InputObject {
self = .some(object)
}
}
// MARK: - Nil Coalescing Operator
/// Nil Coalescing Operator overload for ``GraphQLNullable``
///
/// This operator allows for optional variables to easily be used with ``GraphQLNullable``
/// parameters and a default value.
///
/// ```swift
/// class MyQuery: GraphQLQuery {
///
/// var myVar: GraphQLNullable<String>
///
/// init(myVar: GraphQLNullable<String>) { ... }
/// // ...
/// }
///
/// let optionalString: String?
/// let query = MyQuery(myVar: optionalString ?? .none)
/// ```
@_disfavoredOverload
@inlinable public func ??<T>(lhs: T?, rhs: GraphQLNullable<T>) -> GraphQLNullable<T> {
if let lhs = lhs {
return .some(lhs)
}
return rhs
}
// MARK: - Hashable/Equatable Conformance
extension GraphQLNullable: Equatable where Wrapped: Equatable {}
extension GraphQLNullable: Hashable where Wrapped: Hashable {}