Skip to content

Commit 62297bc

Browse files
gh-action-runnergh-action-runner
authored andcommitted
Squashed 'apollo-ios/' changes from d0f0a87f4..e7d77f000
e7d77f000 improvement: Programmatic field policy, and Field Policy Documentation (#750) git-subtree-dir: apollo-ios git-subtree-split: e7d77f000288ededcd9497c642251079aaab7c00
1 parent 656fd85 commit 62297bc

File tree

6 files changed

+285
-36
lines changed

6 files changed

+285
-36
lines changed

Sources/Apollo/ExecutionSources/CacheDataExecutionSource.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ struct CacheDataExecutionSource: GraphQLExecutionSource {
9393
with info: FieldExecutionInfo,
9494
and type: Selection.Field.OutputType
9595
) -> FieldPolicyResult? {
96-
guard let provider = info.parentInfo.schema.configuration.self as? (any FieldPolicyProvider.Type) else {
96+
guard let provider = info.parentInfo.schema.configuration.self as? (any FieldPolicy.Provider.Type),
97+
let arguments = info.field.arguments else {
9798
return nil
9899
}
99100

@@ -102,16 +103,16 @@ struct CacheDataExecutionSource: GraphQLExecutionSource {
102103
return resolveProgrammaticFieldPolicy(with: info, and: innerType)
103104
case .list(_):
104105
if let keys = provider.cacheKeyList(
105-
for: info.field,
106-
variables: info.parentInfo.variables,
106+
for: FieldPolicy.Field(info.field),
107+
inputData: FieldPolicy.InputData(_rawType: .inputValue(arguments), _variables: info.parentInfo.variables),
107108
path: info.responsePath
108109
) {
109110
return .list(keys)
110111
}
111112
default:
112113
if let key = provider.cacheKey(
113-
for: info.field,
114-
variables: info.parentInfo.variables,
114+
for: FieldPolicy.Field(info.field),
115+
inputData: FieldPolicy.InputData(_rawType: .inputValue(arguments), _variables: info.parentInfo.variables),
115116
path: info.responsePath
116117
) {
117118
return .single(key)

Sources/Apollo/FieldPolicyDirectiveEvaluator.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ enum FieldPolicyResult {
1010

1111
struct FieldPolicyDirectiveEvaluator {
1212
let field: Selection.Field
13-
let fieldPolicy: Selection.FieldPolicy
13+
let fieldPolicy: Selection.FieldPolicyDirective
1414
let arguments: [String: InputValue]
1515
let variables: GraphQLOperation.Variables?
1616

@@ -103,7 +103,7 @@ struct FieldPolicyDirectiveEvaluator {
103103
return keys
104104
}
105105

106-
private func parseKeyArgs(for fieldPolicy: Selection.FieldPolicy) -> [ParsedKey] {
106+
private func parseKeyArgs(for fieldPolicy: Selection.FieldPolicyDirective) -> [ParsedKey] {
107107
fieldPolicy.keyArgs.map { key in
108108
if let dot = key.firstIndex(of: ".") {
109109
let name = String(key[..<dot])
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
public enum FieldPolicy {
2+
3+
/// A protocol that can be added to the ``SchemaConfiguration`` in order to provide custom field policy configuration.
4+
///
5+
/// This protocol should be applied to your existing ``SchemaConfiguration`` and provides a way to provide custom
6+
/// field policy cache keys in lieu of using the @fieldPolicy directive.
7+
public protocol Provider {
8+
/// The entry point for resolving a cache key to read an object from the `NormalizedCache` for a field
9+
/// that returns a single object.
10+
///
11+
/// - Parameters:
12+
/// - field: The ``FieldPolicy.Field`` of the operation being executed.
13+
/// - inputData: The ``FieldPolicy.InputData`` representing the arguments and variables for the operation being executed.
14+
/// - path: The ``ResponsePath`` representing the path within operation to get to the given field.
15+
/// - Returns: A ``CacheKeyInfo`` describing the computed cache key.
16+
static func cacheKey(
17+
for field: Field,
18+
inputData: InputData,
19+
path: ResponsePath
20+
) -> CacheKeyInfo?
21+
22+
/// The entry point for resolving cache keys to read objects from the `NormalizedCache` for a field
23+
/// that returns a list of objects.
24+
///
25+
/// - Parameters:
26+
/// - field: The ``FieldPolicy.Field`` of the operation being executed.
27+
/// - inputData: The ``FieldPolicy.InputData`` representing the arguments and variables for the operation being executed.
28+
/// - path: The ``ResponsePath`` representing the path within operation to get to the given field.
29+
/// - Returns: An array of ``CacheKeyInfo`` describing the computed cache keys.
30+
static func cacheKeyList(
31+
for listField: Field,
32+
inputData: InputData,
33+
path: ResponsePath
34+
) -> [CacheKeyInfo]?
35+
}
36+
37+
public struct Field {
38+
public let name: String
39+
public let alias: String?
40+
public let type: Selection.Field.OutputType
41+
42+
public var responseKey: String {
43+
return alias ?? name
44+
}
45+
46+
public init(
47+
name: String,
48+
alias: String?,
49+
type: Selection.Field.OutputType
50+
) {
51+
self.name = name
52+
self.alias = alias
53+
self.type = type
54+
}
55+
56+
public init(_ selectionField: Selection.Field) {
57+
self.name = selectionField.name
58+
self.alias = selectionField.alias
59+
self.type = selectionField.type
60+
}
61+
}
62+
63+
}

Sources/ApolloAPI/FieldPolicyProvider.swift

Lines changed: 0 additions & 25 deletions
This file was deleted.

Sources/ApolloAPI/InputData.swift

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
extension FieldPolicy {
2+
3+
/// An opaque wrapper for input data of a GraphQL operation. This type wraps data from the
4+
/// arguments and variables for the given field/operation to provide an easy way to work
5+
/// with that data to create cache keys.
6+
public struct InputData {
7+
public let _rawType: RawType
8+
public let _variables: GraphQLOperation.Variables?
9+
10+
public init(
11+
_rawType: RawType,
12+
_variables: GraphQLOperation.Variables?
13+
) {
14+
self._rawType = _rawType
15+
self._variables = _variables
16+
}
17+
18+
@inlinable public subscript(_ key: String) -> (any ScalarType)? {
19+
switch _rawType {
20+
case .inputValue(let dict):
21+
guard let value = dict[key] else {
22+
return nil
23+
}
24+
return value.toAnyScalar(_variables: _variables)
25+
case .json(let dict):
26+
guard let value = dict[key] else {
27+
return nil
28+
}
29+
return value.toAnyScalar()
30+
}
31+
}
32+
33+
@_disfavoredOverload
34+
@inlinable public subscript(_ key: String) -> InputListData? {
35+
switch _rawType {
36+
case .inputValue(let dict):
37+
guard let value = dict[key] else {
38+
return nil
39+
}
40+
return value.toFieldInputListData(_variables: _variables)
41+
case .json(let dict):
42+
guard let value = dict[key] else {
43+
return nil
44+
}
45+
return value.toFieldInputListData()
46+
}
47+
}
48+
49+
@_disfavoredOverload
50+
@inlinable public subscript(_ key: String) -> InputData? {
51+
switch _rawType {
52+
case .inputValue(let dict):
53+
guard let value = dict[key] else { return nil }
54+
return value.toFieldInputData(_variables: _variables)
55+
case .json(let dict):
56+
guard let value = dict[key] else {
57+
return nil
58+
}
59+
return value.toFieldInputData()
60+
}
61+
}
62+
63+
public enum RawType {
64+
case inputValue([String: InputValue])
65+
case json(JSONObject)
66+
}
67+
}
68+
69+
/// An opaque wrapper for input data of a GraphQL operation. This type wraps data from the
70+
/// arguments and variables for the given field/operation to provide an easy way to work
71+
/// with that data to create cache keys.
72+
public struct InputListData {
73+
public let _rawType: RawType
74+
public let _variables: GraphQLOperation.Variables?
75+
public let count: Int
76+
77+
public init(
78+
_rawType: RawType,
79+
_variables: GraphQLOperation.Variables?
80+
) {
81+
self._rawType = _rawType
82+
self._variables = _variables
83+
84+
switch _rawType {
85+
case .hashable(let list):
86+
self.count = list.count
87+
case .inputValue(let list):
88+
self.count = list.count
89+
}
90+
}
91+
92+
@inlinable public subscript(_ index: Int) -> (any ScalarType)? {
93+
switch _rawType {
94+
case .hashable(let list):
95+
let value: AnyHashable = list[index]
96+
return value.toAnyScalar()
97+
case .inputValue(let list):
98+
let value = list[index]
99+
return value.toAnyScalar(_variables: _variables)
100+
}
101+
}
102+
103+
@_disfavoredOverload
104+
@inlinable public subscript(_ index: Int) -> InputData? {
105+
switch _rawType {
106+
case .hashable(let list):
107+
let value = list[index]
108+
return value.toFieldInputData()
109+
case .inputValue(let list):
110+
let value = list[index]
111+
return value.toFieldInputData(_variables: _variables)
112+
}
113+
}
114+
115+
@_disfavoredOverload
116+
@inlinable public subscript(_ index: Int) -> InputListData? {
117+
switch _rawType {
118+
case .hashable(let list):
119+
let value = list[index]
120+
return value.toFieldInputListData()
121+
case .inputValue(let list):
122+
let value = list[index]
123+
return value.toFieldInputListData(_variables: _variables)
124+
}
125+
}
126+
127+
public enum RawType {
128+
case hashable([AnyHashable])
129+
case inputValue([InputValue])
130+
}
131+
}
132+
}
133+
134+
extension AnyHashable {
135+
@usableFromInline func toAnyScalar() -> (any ScalarType)? {
136+
var value = self
137+
if let boolVal = value as? Bool, value.isCanonicalBool {
138+
value = boolVal
139+
} else if let intVal = value as? Int {
140+
value = intVal
141+
}
142+
143+
switch value {
144+
case let scalar as any ScalarType:
145+
return scalar
146+
case let customScalar as any CustomScalarType:
147+
return customScalar._jsonValue as? (any ScalarType)
148+
default:
149+
return nil
150+
}
151+
}
152+
153+
@usableFromInline func toFieldInputListData() -> FieldPolicy.InputListData? {
154+
if let list = self as? [AnyHashable] {
155+
return FieldPolicy.InputListData(_rawType: .hashable(list), _variables: nil)
156+
}
157+
return nil
158+
}
159+
160+
@usableFromInline func toFieldInputData() -> FieldPolicy.InputData? {
161+
if let object = self as? JSONObject {
162+
return FieldPolicy.InputData(_rawType: .json(object), _variables: nil)
163+
}
164+
return nil
165+
}
166+
}
167+
168+
extension InputValue {
169+
@usableFromInline func toAnyScalar(_variables: GraphQLOperation.Variables?) -> (any ScalarType)? {
170+
switch self {
171+
case .scalar(let scalar):
172+
return scalar
173+
case .variable(let varName):
174+
guard let varValue = _variables?[varName] else { return nil }
175+
return varValue._jsonEncodableValue?._jsonValue as? (any ScalarType)
176+
default:
177+
return nil
178+
}
179+
}
180+
181+
@usableFromInline func toFieldInputListData(_variables: GraphQLOperation.Variables?) -> FieldPolicy.InputListData? {
182+
switch self {
183+
case .list(let list):
184+
return FieldPolicy.InputListData(_rawType: .inputValue(list), _variables: _variables)
185+
case .variable(let varName):
186+
guard let varValue = _variables?[varName],
187+
let list = varValue._jsonEncodableValue?._jsonValue as? [AnyHashable] else {
188+
return nil
189+
}
190+
return FieldPolicy.InputListData(_rawType: .hashable(list), _variables: _variables)
191+
default:
192+
return nil
193+
}
194+
}
195+
196+
@usableFromInline func toFieldInputData(_variables: GraphQLOperation.Variables?) -> FieldPolicy.InputData? {
197+
switch self {
198+
case .object(let object):
199+
return FieldPolicy.InputData(_rawType: .inputValue(object), _variables: _variables)
200+
case .variable(let varName):
201+
guard let varValue = _variables?[varName],
202+
let object = varValue._jsonEncodableValue?._jsonValue as? JSONObject else {
203+
return nil
204+
}
205+
return FieldPolicy.InputData(_rawType: .json(object), _variables: _variables)
206+
default:
207+
return nil
208+
}
209+
}
210+
}

Sources/ApolloAPI/Selection.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public enum Selection {
1515
public let alias: String?
1616
public let arguments: [String: InputValue]?
1717
public let type: OutputType
18-
public let fieldPolicy: FieldPolicy?
18+
public let fieldPolicy: FieldPolicyDirective?
1919

2020
public var responseKey: String {
2121
return alias ?? name
@@ -26,7 +26,7 @@ public enum Selection {
2626
alias: String? = nil,
2727
type: OutputType,
2828
arguments: [String: InputValue]? = nil,
29-
fieldPolicy: FieldPolicy? = nil
29+
fieldPolicy: FieldPolicyDirective? = nil
3030
) {
3131
self.name = name
3232
self.alias = alias
@@ -58,7 +58,7 @@ public enum Selection {
5858
}
5959
}
6060

61-
public struct FieldPolicy: Hashable {
61+
public struct FieldPolicyDirective: Hashable {
6262
public let keyArgs: [String]
6363

6464
public init(
@@ -76,7 +76,7 @@ public enum Selection {
7676
alias: String? = nil,
7777
_ type: any OutputTypeConvertible.Type,
7878
arguments: [String: InputValue]? = nil,
79-
fieldPolicy: FieldPolicy? = nil
79+
fieldPolicy: FieldPolicyDirective? = nil
8080
) -> Selection {
8181
.field(.init(name, alias: alias, type: type._asOutputType, arguments: arguments, fieldPolicy: fieldPolicy))
8282
}

0 commit comments

Comments
 (0)