forked from apollographql/apollo-ios
-
Notifications
You must be signed in to change notification settings - Fork 0
/
GraphQLOperation.swift
230 lines (188 loc) · 7.63 KB
/
GraphQLOperation.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
import Foundation
public enum GraphQLOperationType: Hashable {
case query
case mutation
case subscription
}
/// The means of providing the operation document that includes the definition of the operation
/// over network transport.
///
/// This data represents the `Document` as defined in the GraphQL Spec.
/// - See: [GraphQLSpec - Document](https://spec.graphql.org/draft/#Document)
///
/// The Apollo Code Generation Engine will generate the ``OperationDocument`` on each generated
/// ``GraphQLOperation``. You can configure the the code generation engine to include the
/// ``OperationDefinition``, ``operationIdentifier``, or both using the `OperationDocumentFormat`
/// options in your `ApolloCodegenConfiguration`.
public struct OperationDocument: Sendable {
public let operationIdentifier: String?
public let definition: OperationDefinition?
public init(
operationIdentifier: String? = nil,
definition: OperationDefinition? = nil
) {
precondition(operationIdentifier != nil || definition != nil)
self.operationIdentifier = operationIdentifier
self.definition = definition
}
}
/// The definition of an operation to be provided over network transport.
///
/// This data represents the `Definition` for a `Document` as defined in the GraphQL Spec.
/// In the case of the Apollo client, the definition will always be an `ExecutableDefinition`.
/// - See: [GraphQLSpec - Document](https://spec.graphql.org/draft/#Document)
public struct OperationDefinition: Sendable {
let operationDefinition: String
let fragments: [any Fragment.Type]?
public init(_ definition: String, fragments: [any Fragment.Type]? = nil) {
self.operationDefinition = definition
self.fragments = fragments
}
public var queryDocument: String {
var document = operationDefinition
fragments?.forEach {
document.append("\n" + $0.fragmentDefinition.description)
}
return document
}
}
/// A unique identifier used as a key to map a deferred selection set type to an incremental
/// response label and path.
public struct DeferredFragmentIdentifier: Hashable {
let label: String
let fieldPath: [String]
public init(label: String, fieldPath: [String]) {
self.label = label
self.fieldPath = fieldPath
}
}
// MARK: - GraphQLOperation
public protocol GraphQLOperation: AnyObject, Hashable {
typealias Variables = [String: any GraphQLOperationVariableValue]
static var operationName: String { get }
static var operationType: GraphQLOperationType { get }
static var operationDocument: OperationDocument { get }
static var deferredFragments: [DeferredFragmentIdentifier: any SelectionSet.Type]? { get }
var __variables: Variables? { get }
associatedtype Data: RootSelectionSet
}
// MARK: Static Extensions
public extension GraphQLOperation {
static var deferredFragments: [DeferredFragmentIdentifier: any SelectionSet.Type]? {
return nil
}
static func deferredSelectionSetType<T: GraphQLOperation>(
for operation: T.Type,
withLabel label: String,
atFieldPath fieldPath: [String]
) -> (any SelectionSet.Type)? {
return T.deferredFragments?[DeferredFragmentIdentifier(label: label, fieldPath: fieldPath)]
}
static var hasDeferredFragments: Bool {
return !(deferredFragments?.isEmpty ?? true)
}
static var definition: OperationDefinition? {
operationDocument.definition
}
static var operationIdentifier: String? {
operationDocument.operationIdentifier
}
static func ==(lhs: Self, rhs: Self) -> Bool {
lhs.__variables?._jsonEncodableValue?._jsonValue == rhs.__variables?._jsonEncodableValue?._jsonValue
}
}
// MARK: Instance Extensions
public extension GraphQLOperation {
var __variables: Variables? {
return nil
}
func hash(into hasher: inout Hasher) {
hasher.combine(__variables?._jsonEncodableValue?._jsonValue)
}
}
// MARK: - GraphQLQuery
public protocol GraphQLQuery: GraphQLOperation {}
public extension GraphQLQuery {
@inlinable static var operationType: GraphQLOperationType { return .query }
}
// MARK: - GraphQLMutation
public protocol GraphQLMutation: GraphQLOperation {}
public extension GraphQLMutation {
@inlinable static var operationType: GraphQLOperationType { return .mutation }
}
// MARK: - GraphQLSubscription
public protocol GraphQLSubscription: GraphQLOperation {}
public extension GraphQLSubscription {
@inlinable static var operationType: GraphQLOperationType { return .subscription }
}
// MARK: - GraphQLOperationVariableValue
public protocol GraphQLOperationVariableValue {
var _jsonEncodableValue: (any JSONEncodable)? { get }
}
extension Array: GraphQLOperationVariableValue
where Element: GraphQLOperationVariableValue & JSONEncodable & Hashable {}
extension Dictionary: GraphQLOperationVariableValue
where Key == String, Value == any GraphQLOperationVariableValue {
@inlinable public var _jsonEncodableValue: (any JSONEncodable)? { _jsonEncodableObject }
@inlinable public var _jsonEncodableObject: JSONEncodableDictionary {
compactMapValues { $0._jsonEncodableValue }
}
}
extension GraphQLNullable: GraphQLOperationVariableValue
where Wrapped: GraphQLOperationVariableValue {
@inlinable public var _jsonEncodableValue: (any JSONEncodable)? {
switch self {
case .none: return nil
case .null: return NSNull()
case let .some(value): return value._jsonEncodableValue
}
}
}
extension Optional: GraphQLOperationVariableValue where Wrapped: GraphQLOperationVariableValue {
@inlinable public var _jsonEncodableValue: (any JSONEncodable)? {
switch self {
case .none: return nil
case let .some(value): return value._jsonEncodableValue
}
}
}
extension JSONEncodable where Self: GraphQLOperationVariableValue {
@inlinable public var _jsonEncodableValue: (any JSONEncodable)? { self }
}
// MARK: - Deprecations
@available(*, deprecated, renamed: "OperationDocument")
public enum DocumentType {
/// The traditional way of providing the operation `Document`.
/// The `Document` is sent with every operation request.
case notPersisted(definition: OperationDefinition)
/// Automatically persists your operations using Apollo Server's
/// [APQs](https://www.apollographql.com/docs/apollo-server/performance/apq).
///
/// This allow the operation definition to be persisted using an `operationIdentifier` instead of
/// being sent with every operation request. If the server does not recognize the
/// `operationIdentifier`, the network transport can send the provided definition to
/// "automatically persist" the operation definition.
case automaticallyPersisted(operationIdentifier: String, definition: OperationDefinition)
/// Provides only the `operationIdentifier` for operations that have been previously persisted
/// to an Apollo Server using
/// [APQs](https://www.apollographql.com/docs/apollo-server/performance/apq).
///
/// If the server does not recognize the `operationIdentifier`, the operation will fail. This
/// method should only be used if you are manually persisting your queries to an Apollo Server.
case persistedOperationsOnly(operationIdentifier: String)
}
extension GraphQLOperation {
@available(*, deprecated, renamed: "operationDocument")
static public var document: DocumentType {
switch (operationDocument.definition, operationDocument.operationIdentifier) {
case let (definition?, id?):
return .automaticallyPersisted(operationIdentifier: id, definition: definition)
case let (definition?, .none):
return .notPersisted(definition: definition)
case let (.none, id?):
return .persistedOperationsOnly(operationIdentifier: id)
default:
preconditionFailure("operationDocument must have either a definition or an identifier.")
}
}
}