@apollo/federation-internals 2.0.0-preview.7 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -3
- package/dist/buildSchema.d.ts.map +1 -1
- package/dist/buildSchema.js +51 -41
- package/dist/buildSchema.js.map +1 -1
- package/dist/coreSpec.d.ts +16 -8
- package/dist/coreSpec.d.ts.map +1 -1
- package/dist/coreSpec.js +205 -53
- package/dist/coreSpec.js.map +1 -1
- package/dist/definitions.d.ts +28 -11
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +185 -67
- package/dist/definitions.js.map +1 -1
- package/dist/directiveAndTypeSpecification.d.ts +11 -1
- package/dist/directiveAndTypeSpecification.d.ts.map +1 -1
- package/dist/directiveAndTypeSpecification.js +77 -20
- package/dist/directiveAndTypeSpecification.js.map +1 -1
- package/dist/error.d.ts +17 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +54 -2
- package/dist/error.js.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.js +7 -1
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
- package/dist/federation.d.ts +22 -5
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +143 -86
- package/dist/federation.js.map +1 -1
- package/dist/federationSpec.d.ts +6 -2
- package/dist/federationSpec.d.ts.map +1 -1
- package/dist/federationSpec.js +47 -22
- package/dist/federationSpec.js.map +1 -1
- package/dist/inaccessibleSpec.d.ts +10 -2
- package/dist/inaccessibleSpec.d.ts.map +1 -1
- package/dist/inaccessibleSpec.js +634 -16
- package/dist/inaccessibleSpec.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/introspection.d.ts.map +1 -1
- package/dist/introspection.js +8 -3
- package/dist/introspection.js.map +1 -1
- package/dist/joinSpec.d.ts +5 -1
- package/dist/joinSpec.d.ts.map +1 -1
- package/dist/joinSpec.js +21 -0
- package/dist/joinSpec.js.map +1 -1
- package/dist/knownCoreFeatures.d.ts +4 -0
- package/dist/knownCoreFeatures.d.ts.map +1 -0
- package/dist/knownCoreFeatures.js +16 -0
- package/dist/knownCoreFeatures.js.map +1 -0
- package/dist/operations.d.ts +1 -0
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +16 -1
- package/dist/operations.js.map +1 -1
- package/dist/{sharing.d.ts → precompute.d.ts} +1 -1
- package/dist/precompute.d.ts.map +1 -0
- package/dist/{sharing.js → precompute.js} +3 -3
- package/dist/precompute.js.map +1 -0
- package/dist/schemaUpgrader.d.ts.map +1 -1
- package/dist/schemaUpgrader.js +17 -7
- package/dist/schemaUpgrader.js.map +1 -1
- package/dist/suggestions.d.ts +1 -1
- package/dist/suggestions.d.ts.map +1 -1
- package/dist/suggestions.js.map +1 -1
- package/dist/supergraphs.d.ts.map +1 -1
- package/dist/supergraphs.js +2 -0
- package/dist/supergraphs.js.map +1 -1
- package/dist/tagSpec.d.ts +7 -2
- package/dist/tagSpec.d.ts.map +1 -1
- package/dist/tagSpec.js +35 -14
- package/dist/tagSpec.js.map +1 -1
- package/dist/validate.js +13 -7
- package/dist/validate.js.map +1 -1
- package/dist/values.d.ts +2 -2
- package/dist/values.d.ts.map +1 -1
- package/dist/values.js +13 -11
- package/dist/values.js.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/coreSpec.test.ts +212 -0
- package/src/__tests__/definitions.test.ts +75 -0
- package/src/__tests__/removeInaccessibleElements.test.ts +2229 -137
- package/src/__tests__/schemaUpgrader.test.ts +3 -2
- package/src/__tests__/subgraphValidation.test.ts +419 -4
- package/src/__tests__/values.test.ts +315 -3
- package/src/buildSchema.ts +98 -51
- package/src/coreSpec.ts +277 -65
- package/src/definitions.ts +317 -92
- package/src/directiveAndTypeSpecification.ts +98 -21
- package/src/error.ts +119 -1
- package/src/extractSubgraphsFromSupergraph.ts +7 -1
- package/src/federation.ts +184 -102
- package/src/federationSpec.ts +56 -24
- package/src/inaccessibleSpec.ts +985 -39
- package/src/index.ts +2 -0
- package/src/introspection.ts +8 -3
- package/src/joinSpec.ts +33 -3
- package/src/knownCoreFeatures.ts +13 -0
- package/src/operations.ts +15 -0
- package/src/{sharing.ts → precompute.ts} +3 -6
- package/src/schemaUpgrader.ts +29 -13
- package/src/suggestions.ts +1 -1
- package/src/supergraphs.ts +2 -0
- package/src/tagSpec.ts +49 -16
- package/src/validate.ts +20 -9
- package/src/values.ts +39 -12
- package/tsconfig.test.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/sharing.d.ts.map +0 -1
- package/dist/sharing.js.map +0 -1
package/src/inaccessibleSpec.ts
CHANGED
|
@@ -1,39 +1,110 @@
|
|
|
1
1
|
import { FeatureDefinition, FeatureDefinitions, FeatureUrl, FeatureVersion } from "./coreSpec";
|
|
2
2
|
import {
|
|
3
|
+
ArgumentDefinition,
|
|
4
|
+
CoreFeatures,
|
|
3
5
|
DirectiveDefinition,
|
|
6
|
+
EnumType,
|
|
7
|
+
EnumValue,
|
|
8
|
+
ErrGraphQLAPISchemaValidationFailed,
|
|
9
|
+
executableDirectiveLocations,
|
|
4
10
|
FieldDefinition,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
11
|
+
InputFieldDefinition,
|
|
12
|
+
InputObjectType,
|
|
13
|
+
InputType,
|
|
14
|
+
InterfaceType,
|
|
15
|
+
isEnumType,
|
|
16
|
+
isInputObjectType,
|
|
17
|
+
isListType,
|
|
18
|
+
isNonNullType,
|
|
19
|
+
isScalarType,
|
|
20
|
+
isVariable,
|
|
21
|
+
NamedType,
|
|
22
|
+
ObjectType,
|
|
23
|
+
ScalarType,
|
|
8
24
|
Schema,
|
|
25
|
+
SchemaDefinition,
|
|
26
|
+
SchemaElement,
|
|
27
|
+
UnionType,
|
|
9
28
|
} from "./definitions";
|
|
10
29
|
import { GraphQLError, DirectiveLocation } from "graphql";
|
|
30
|
+
import { registerKnownFeature } from "./knownCoreFeatures";
|
|
31
|
+
import { ERRORS } from "./error";
|
|
32
|
+
import { createDirectiveSpecification, DirectiveSpecification } from "./directiveAndTypeSpecification";
|
|
33
|
+
import { assert } from "./utils";
|
|
11
34
|
|
|
12
35
|
export const inaccessibleIdentity = 'https://specs.apollo.dev/inaccessible';
|
|
13
36
|
|
|
14
37
|
export class InaccessibleSpecDefinition extends FeatureDefinition {
|
|
38
|
+
public readonly inaccessibleLocations: DirectiveLocation[];
|
|
39
|
+
public readonly inaccessibleDirectiveSpec: DirectiveSpecification;
|
|
40
|
+
private readonly printedInaccessibleDefinition: string;
|
|
41
|
+
|
|
15
42
|
constructor(version: FeatureVersion) {
|
|
16
43
|
super(new FeatureUrl(inaccessibleIdentity, 'inaccessible', version));
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
addElementsToSchema(schema: Schema) {
|
|
20
|
-
this.addDirective(schema, 'inaccessible').addLocations(
|
|
44
|
+
this.inaccessibleLocations = [
|
|
21
45
|
DirectiveLocation.FIELD_DEFINITION,
|
|
22
46
|
DirectiveLocation.OBJECT,
|
|
23
47
|
DirectiveLocation.INTERFACE,
|
|
24
48
|
DirectiveLocation.UNION,
|
|
25
|
-
|
|
49
|
+
];
|
|
50
|
+
this.printedInaccessibleDefinition = 'directive @inaccessible on FIELD_DEFINITION | INTERFACE | OBJECT | UNION';
|
|
51
|
+
if (!this.isV01()) {
|
|
52
|
+
this.inaccessibleLocations.push(
|
|
53
|
+
DirectiveLocation.ARGUMENT_DEFINITION,
|
|
54
|
+
DirectiveLocation.SCALAR,
|
|
55
|
+
DirectiveLocation.ENUM,
|
|
56
|
+
DirectiveLocation.ENUM_VALUE,
|
|
57
|
+
DirectiveLocation.INPUT_OBJECT,
|
|
58
|
+
DirectiveLocation.INPUT_FIELD_DEFINITION,
|
|
59
|
+
);
|
|
60
|
+
this.printedInaccessibleDefinition = 'directive @inaccessible on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION';
|
|
61
|
+
}
|
|
62
|
+
this.inaccessibleDirectiveSpec = createDirectiveSpecification({
|
|
63
|
+
name: 'inaccessible',
|
|
64
|
+
locations: this.inaccessibleLocations,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
isV01() {
|
|
69
|
+
return this.version.equals(new FeatureVersion(0, 1));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
addElementsToSchema(schema: Schema): GraphQLError[] {
|
|
73
|
+
return this.addDirectiveSpec(schema, this.inaccessibleDirectiveSpec);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
inaccessibleDirective(schema: Schema): DirectiveDefinition<Record<string, never>> | undefined {
|
|
77
|
+
return this.directive(schema, 'inaccessible');
|
|
26
78
|
}
|
|
27
79
|
|
|
28
|
-
|
|
29
|
-
|
|
80
|
+
checkCompatibleDirective(definition: DirectiveDefinition): GraphQLError | undefined {
|
|
81
|
+
const hasUnknownArguments = Object.keys(definition.arguments()).length > 0;
|
|
82
|
+
const hasRepeatable = definition.repeatable;
|
|
83
|
+
const hasValidLocations = definition.locations.every(loc => this.inaccessibleLocations.includes(loc));
|
|
84
|
+
if (hasUnknownArguments || hasRepeatable || !hasValidLocations) {
|
|
85
|
+
return ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
|
|
86
|
+
message: `Found invalid @inaccessible directive definition. Please ensure the directive definition in your schema's definitions matches the following:\n\t${this.printedInaccessibleDefinition}`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
allElementNames(): string[] {
|
|
93
|
+
return ['@inaccessible'];
|
|
30
94
|
}
|
|
31
95
|
}
|
|
32
96
|
|
|
33
97
|
export const INACCESSIBLE_VERSIONS = new FeatureDefinitions<InaccessibleSpecDefinition>(inaccessibleIdentity)
|
|
34
|
-
.add(new InaccessibleSpecDefinition(new FeatureVersion(0, 1)))
|
|
98
|
+
.add(new InaccessibleSpecDefinition(new FeatureVersion(0, 1)))
|
|
99
|
+
.add(new InaccessibleSpecDefinition(new FeatureVersion(0, 2)));
|
|
100
|
+
|
|
101
|
+
registerKnownFeature(INACCESSIBLE_VERSIONS);
|
|
35
102
|
|
|
36
103
|
export function removeInaccessibleElements(schema: Schema) {
|
|
104
|
+
// Note it doesn't hurt to validate here, since we expect the schema to be
|
|
105
|
+
// validated already, and if it has been, it's cached/inexpensive.
|
|
106
|
+
schema.validate();
|
|
107
|
+
|
|
37
108
|
const coreFeatures = schema.coreFeatures;
|
|
38
109
|
if (!coreFeatures) {
|
|
39
110
|
return;
|
|
@@ -43,46 +114,921 @@ export function removeInaccessibleElements(schema: Schema) {
|
|
|
43
114
|
if (!inaccessibleFeature) {
|
|
44
115
|
return;
|
|
45
116
|
}
|
|
46
|
-
const inaccessibleSpec = INACCESSIBLE_VERSIONS.find(
|
|
117
|
+
const inaccessibleSpec = INACCESSIBLE_VERSIONS.find(
|
|
118
|
+
inaccessibleFeature.url.version
|
|
119
|
+
);
|
|
47
120
|
if (!inaccessibleSpec) {
|
|
48
|
-
throw new GraphQLError(
|
|
49
|
-
`Cannot remove inaccessible elements: the schema uses unsupported
|
|
121
|
+
throw ErrGraphQLAPISchemaValidationFailed([new GraphQLError(
|
|
122
|
+
`Cannot remove inaccessible elements: the schema uses unsupported` +
|
|
123
|
+
` inaccessible spec version ${inaccessibleFeature.url.version}` +
|
|
124
|
+
` (supported versions: ${INACCESSIBLE_VERSIONS.versions().join(', ')})`
|
|
125
|
+
)]);
|
|
50
126
|
}
|
|
51
127
|
|
|
52
128
|
const inaccessibleDirective = inaccessibleSpec.inaccessibleDirective(schema);
|
|
53
129
|
if (!inaccessibleDirective) {
|
|
54
|
-
throw new GraphQLError(
|
|
55
|
-
`Invalid schema: declares ${inaccessibleSpec.url} spec but does not
|
|
130
|
+
throw ErrGraphQLAPISchemaValidationFailed([new GraphQLError(
|
|
131
|
+
`Invalid schema: declares ${inaccessibleSpec.url} spec but does not` +
|
|
132
|
+
` define a @inaccessible directive.`
|
|
133
|
+
)]);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const incompatibleError =
|
|
137
|
+
inaccessibleSpec.checkCompatibleDirective(inaccessibleDirective);
|
|
138
|
+
if (incompatibleError) {
|
|
139
|
+
throw ErrGraphQLAPISchemaValidationFailed([incompatibleError]);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
validateInaccessibleElements(
|
|
143
|
+
schema,
|
|
144
|
+
coreFeatures,
|
|
145
|
+
inaccessibleSpec,
|
|
146
|
+
inaccessibleDirective,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
removeInaccessibleElementsAssumingValid(
|
|
150
|
+
schema,
|
|
151
|
+
inaccessibleDirective,
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// These are elements that may be hidden, by either @inaccessible or core
|
|
156
|
+
// feature definition hiding.
|
|
157
|
+
type HideableElement =
|
|
158
|
+
| ObjectType
|
|
159
|
+
| InterfaceType
|
|
160
|
+
| UnionType
|
|
161
|
+
| ScalarType
|
|
162
|
+
| EnumType
|
|
163
|
+
| InputObjectType
|
|
164
|
+
| DirectiveDefinition
|
|
165
|
+
| FieldDefinition<ObjectType | InterfaceType>
|
|
166
|
+
| ArgumentDefinition<
|
|
167
|
+
| DirectiveDefinition
|
|
168
|
+
| FieldDefinition<ObjectType | InterfaceType>>
|
|
169
|
+
| InputFieldDefinition
|
|
170
|
+
| EnumValue
|
|
171
|
+
|
|
172
|
+
// Validate the applications of @inaccessible in the schema. Some of these may
|
|
173
|
+
// technically be caught by Schema.validate() later, but we'd like to give
|
|
174
|
+
// clearer error messaging when possible.
|
|
175
|
+
function validateInaccessibleElements(
|
|
176
|
+
schema: Schema,
|
|
177
|
+
coreFeatures: CoreFeatures,
|
|
178
|
+
inaccessibleSpec: InaccessibleSpecDefinition,
|
|
179
|
+
inaccessibleDirective: DirectiveDefinition,
|
|
180
|
+
): void {
|
|
181
|
+
function isInaccessible(element: SchemaElement<any, any>): boolean {
|
|
182
|
+
return element.hasAppliedDirective(inaccessibleDirective);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const featureList = [...coreFeatures.allFeatures()];
|
|
186
|
+
function isFeatureDefinition(
|
|
187
|
+
element: NamedType | DirectiveDefinition
|
|
188
|
+
): boolean {
|
|
189
|
+
return featureList.some((feature) => feature.isFeatureDefinition(element));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function isInAPISchema(element: HideableElement): boolean {
|
|
193
|
+
// If this element is @inaccessible, it's not in the API schema.
|
|
194
|
+
if (
|
|
195
|
+
!(element instanceof DirectiveDefinition) &&
|
|
196
|
+
isInaccessible(element)
|
|
197
|
+
) return false;
|
|
198
|
+
|
|
199
|
+
if (
|
|
200
|
+
(element instanceof ObjectType) ||
|
|
201
|
+
(element instanceof InterfaceType) ||
|
|
202
|
+
(element instanceof UnionType) ||
|
|
203
|
+
(element instanceof ScalarType) ||
|
|
204
|
+
(element instanceof EnumType) ||
|
|
205
|
+
(element instanceof InputObjectType) ||
|
|
206
|
+
(element instanceof DirectiveDefinition)
|
|
207
|
+
) {
|
|
208
|
+
// These are top-level elements. If they're not @inaccessible, the only
|
|
209
|
+
// way they won't be in the API schema is if they're definitions of some
|
|
210
|
+
// core feature.
|
|
211
|
+
return !isFeatureDefinition(element);
|
|
212
|
+
} else if (
|
|
213
|
+
(element instanceof FieldDefinition) ||
|
|
214
|
+
(element instanceof ArgumentDefinition) ||
|
|
215
|
+
(element instanceof InputFieldDefinition) ||
|
|
216
|
+
(element instanceof EnumValue)
|
|
217
|
+
) {
|
|
218
|
+
// While this element isn't marked @inaccessible, this element won't be in
|
|
219
|
+
// the API schema if its parent isn't.
|
|
220
|
+
return isInAPISchema(element.parent);
|
|
221
|
+
}
|
|
222
|
+
assert(false, "Unreachable code, element is of unknown type.");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function fetchInaccessibleElementsDeep(
|
|
226
|
+
element: HideableElement
|
|
227
|
+
): HideableElement[] {
|
|
228
|
+
const inaccessibleElements: HideableElement[] = [];
|
|
229
|
+
if (isInaccessible(element)) {
|
|
230
|
+
inaccessibleElements.push(element);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (
|
|
234
|
+
(element instanceof ObjectType) ||
|
|
235
|
+
(element instanceof InterfaceType) ||
|
|
236
|
+
(element instanceof InputObjectType)
|
|
237
|
+
) {
|
|
238
|
+
for (const field of element.fields()) {
|
|
239
|
+
inaccessibleElements.push(
|
|
240
|
+
...fetchInaccessibleElementsDeep(field),
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
return inaccessibleElements;
|
|
244
|
+
} else if (element instanceof EnumType) {
|
|
245
|
+
for (const enumValue of element.values) {
|
|
246
|
+
inaccessibleElements.push(
|
|
247
|
+
...fetchInaccessibleElementsDeep(enumValue),
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
return inaccessibleElements;
|
|
251
|
+
} else if (
|
|
252
|
+
(element instanceof DirectiveDefinition) ||
|
|
253
|
+
(element instanceof FieldDefinition)
|
|
254
|
+
) {
|
|
255
|
+
for (const argument of element.arguments()) {
|
|
256
|
+
inaccessibleElements.push(
|
|
257
|
+
...fetchInaccessibleElementsDeep(argument),
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
return inaccessibleElements;
|
|
261
|
+
} else if (
|
|
262
|
+
(element instanceof UnionType) ||
|
|
263
|
+
(element instanceof ScalarType) ||
|
|
264
|
+
(element instanceof ArgumentDefinition) ||
|
|
265
|
+
(element instanceof InputFieldDefinition) ||
|
|
266
|
+
(element instanceof EnumValue)
|
|
267
|
+
) {
|
|
268
|
+
return inaccessibleElements;
|
|
269
|
+
}
|
|
270
|
+
assert(false, "Unreachable code, element is of unknown type.");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const errors: GraphQLError[] = [];
|
|
274
|
+
let defaultValueReferencers: Map<
|
|
275
|
+
DefaultValueReference,
|
|
276
|
+
SchemaElementWithDefaultValue[]
|
|
277
|
+
> | undefined = undefined;
|
|
278
|
+
if (!inaccessibleSpec.isV01()) {
|
|
279
|
+
// Note that for inaccessible v0.1, enum values and input fields can't be
|
|
280
|
+
// @inaccessible, so there's no need to compute references (the inaccessible
|
|
281
|
+
// v0.1 spec also doesn't require default values to be valid, so it doesn't
|
|
282
|
+
// make sense to compute them).
|
|
283
|
+
defaultValueReferencers = computeDefaultValueReferencers(schema);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
for (const type of schema.allTypes()) {
|
|
287
|
+
if (hasBuiltInName(type)) {
|
|
288
|
+
// Built-in types (and their descendants) aren't allowed to be
|
|
289
|
+
// @inaccessible, regardless of shadowing.
|
|
290
|
+
const inaccessibleElements = fetchInaccessibleElementsDeep(type);
|
|
291
|
+
if (inaccessibleElements.length > 0) {
|
|
292
|
+
errors.push(ERRORS.DISALLOWED_INACCESSIBLE.err({
|
|
293
|
+
message:
|
|
294
|
+
`Built-in type "${type.coordinate}" cannot use @inaccessible.`,
|
|
295
|
+
nodes: type.sourceAST,
|
|
296
|
+
extensions: {
|
|
297
|
+
inaccessible_elements: inaccessibleElements
|
|
298
|
+
.map((element) => element.coordinate),
|
|
299
|
+
inaccessible_referencers: [type.coordinate],
|
|
300
|
+
}
|
|
301
|
+
}));
|
|
302
|
+
}
|
|
303
|
+
} else if (isFeatureDefinition(type)) {
|
|
304
|
+
// Core feature types (and their descendants) aren't allowed to be
|
|
305
|
+
// @inaccessible.
|
|
306
|
+
const inaccessibleElements = fetchInaccessibleElementsDeep(type);
|
|
307
|
+
if (inaccessibleElements.length > 0) {
|
|
308
|
+
errors.push(ERRORS.DISALLOWED_INACCESSIBLE.err({
|
|
309
|
+
message:
|
|
310
|
+
`Core feature type "${type.coordinate}" cannot use @inaccessible.`,
|
|
311
|
+
nodes: type.sourceAST,
|
|
312
|
+
extensions: {
|
|
313
|
+
inaccessible_elements: inaccessibleElements
|
|
314
|
+
.map((element) => element.coordinate),
|
|
315
|
+
inaccessible_referencers: [type.coordinate],
|
|
316
|
+
}
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
} else if (isInaccessible(type)) {
|
|
320
|
+
// Types can be referenced by other schema elements in a few ways:
|
|
321
|
+
// 1. Fields, arguments, and input fields may have the type as their base
|
|
322
|
+
// type.
|
|
323
|
+
// 2. Union types may have the type as a member (for object types).
|
|
324
|
+
// 3. Object and interface types may implement the type (for interface
|
|
325
|
+
// types).
|
|
326
|
+
// 4. Schemas may have the type as a root operation type (for object
|
|
327
|
+
// types).
|
|
328
|
+
//
|
|
329
|
+
// When a type is hidden, the referencer must follow certain rules for the
|
|
330
|
+
// schema to be valid. Respectively, these rules are:
|
|
331
|
+
// 1. The field/argument/input field must not be in the API schema.
|
|
332
|
+
// 2. The union type, if empty, must not be in the API schema.
|
|
333
|
+
// 3. No rules are imposed in this case.
|
|
334
|
+
// 4. The root operation type must not be the query type.
|
|
335
|
+
//
|
|
336
|
+
// We validate the 1st and 4th rules above, and leave the 2nd for when we
|
|
337
|
+
// look at accessible union types.
|
|
338
|
+
const referencers = type.referencers();
|
|
339
|
+
for (const referencer of referencers) {
|
|
340
|
+
if (
|
|
341
|
+
referencer instanceof FieldDefinition ||
|
|
342
|
+
referencer instanceof ArgumentDefinition ||
|
|
343
|
+
referencer instanceof InputFieldDefinition
|
|
344
|
+
) {
|
|
345
|
+
if (isInAPISchema(referencer)) {
|
|
346
|
+
errors.push(ERRORS.REFERENCED_INACCESSIBLE.err({
|
|
347
|
+
message:
|
|
348
|
+
`Type "${type.coordinate}" is @inaccessible but is referenced` +
|
|
349
|
+
` by "${referencer.coordinate}", which is in the API schema.`,
|
|
350
|
+
nodes: type.sourceAST,
|
|
351
|
+
extensions: {
|
|
352
|
+
inaccessible_elements: [type.coordinate],
|
|
353
|
+
inaccessible_referencers: [referencer.coordinate],
|
|
354
|
+
}
|
|
355
|
+
}));
|
|
356
|
+
}
|
|
357
|
+
} else if (referencer instanceof SchemaDefinition) {
|
|
358
|
+
if (type === referencer.rootType('query')) {
|
|
359
|
+
errors.push(ERRORS.QUERY_ROOT_TYPE_INACCESSIBLE.err({
|
|
360
|
+
message:
|
|
361
|
+
`Type "${type.coordinate}" is @inaccessible but is the root` +
|
|
362
|
+
` query type, which must be in the API schema.`,
|
|
363
|
+
nodes: type.sourceAST,
|
|
364
|
+
extensions: {
|
|
365
|
+
inaccessible_elements: [type.coordinate],
|
|
366
|
+
}
|
|
367
|
+
}));
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
} else {
|
|
372
|
+
// At this point, we know the type must be in the API schema. For types
|
|
373
|
+
// with children (all types except scalar), we check that at least one of
|
|
374
|
+
// the children is accessible.
|
|
375
|
+
if (
|
|
376
|
+
(type instanceof ObjectType) ||
|
|
377
|
+
(type instanceof InterfaceType) ||
|
|
378
|
+
(type instanceof InputObjectType)
|
|
379
|
+
) {
|
|
380
|
+
let isEmpty = true;
|
|
381
|
+
for (const field of type.fields()) {
|
|
382
|
+
if (!isInaccessible(field)) isEmpty = false;
|
|
383
|
+
}
|
|
384
|
+
if (isEmpty) {
|
|
385
|
+
errors.push(ERRORS.ONLY_INACCESSIBLE_CHILDREN.err({
|
|
386
|
+
message:
|
|
387
|
+
`Type "${type.coordinate}" is in the API schema but all of its` +
|
|
388
|
+
` ${(type instanceof InputObjectType) ? 'input ' : ''}fields` +
|
|
389
|
+
` are @inaccessible.`,
|
|
390
|
+
nodes: type.sourceAST,
|
|
391
|
+
extensions: {
|
|
392
|
+
inaccessible_elements: type.fields()
|
|
393
|
+
.map((field) => field.coordinate),
|
|
394
|
+
inaccessible_referencers: [type.coordinate],
|
|
395
|
+
}
|
|
396
|
+
}));
|
|
397
|
+
}
|
|
398
|
+
} else if (type instanceof UnionType) {
|
|
399
|
+
let isEmpty = true;
|
|
400
|
+
for (const member of type.types()) {
|
|
401
|
+
if (!isInaccessible(member)) isEmpty = false;
|
|
402
|
+
}
|
|
403
|
+
if (isEmpty) {
|
|
404
|
+
errors.push(ERRORS.ONLY_INACCESSIBLE_CHILDREN.err({
|
|
405
|
+
message:
|
|
406
|
+
`Type "${type.coordinate}" is in the API schema but all of its` +
|
|
407
|
+
` members are @inaccessible.`,
|
|
408
|
+
nodes: type.sourceAST,
|
|
409
|
+
extensions: {
|
|
410
|
+
inaccessible_elements: type.types()
|
|
411
|
+
.map((type) => type.coordinate),
|
|
412
|
+
inaccessible_referencers: [type.coordinate],
|
|
413
|
+
}
|
|
414
|
+
}));
|
|
415
|
+
}
|
|
416
|
+
} else if (type instanceof EnumType) {
|
|
417
|
+
let isEmpty = true;
|
|
418
|
+
for (const enumValue of type.values) {
|
|
419
|
+
if (!isInaccessible(enumValue)) isEmpty = false;
|
|
420
|
+
}
|
|
421
|
+
if (isEmpty) {
|
|
422
|
+
errors.push(ERRORS.ONLY_INACCESSIBLE_CHILDREN.err({
|
|
423
|
+
message:
|
|
424
|
+
`Type "${type.coordinate}" is in the API schema but all of its` +
|
|
425
|
+
` values are @inaccessible.`,
|
|
426
|
+
nodes: type.sourceAST,
|
|
427
|
+
extensions: {
|
|
428
|
+
inaccessible_elements: type.values
|
|
429
|
+
.map((enumValue) => enumValue.coordinate),
|
|
430
|
+
inaccessible_referencers: [type.coordinate],
|
|
431
|
+
}
|
|
432
|
+
}));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Descend into the type's children if needed.
|
|
437
|
+
if (
|
|
438
|
+
(type instanceof ObjectType) ||
|
|
439
|
+
(type instanceof InterfaceType)
|
|
440
|
+
) {
|
|
441
|
+
const implementedInterfaces = type.interfaces();
|
|
442
|
+
const implementingTypes: (ObjectType | InterfaceType)[] = [];
|
|
443
|
+
if (type instanceof InterfaceType) {
|
|
444
|
+
for (const referencer of type.referencers()) {
|
|
445
|
+
if (
|
|
446
|
+
(referencer instanceof ObjectType) ||
|
|
447
|
+
(referencer instanceof InterfaceType)
|
|
448
|
+
) {
|
|
449
|
+
implementingTypes.push(referencer);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
for (const field of type.fields()) {
|
|
454
|
+
if (isInaccessible(field)) {
|
|
455
|
+
// Fields can be "referenced" by the corresponding fields of any
|
|
456
|
+
// interfaces their parent type implements. When a field is hidden
|
|
457
|
+
// (but its parent isn't), we check that such implemented fields
|
|
458
|
+
// aren't in the API schema.
|
|
459
|
+
for (const implementedInterface of implementedInterfaces) {
|
|
460
|
+
const implementedField = implementedInterface.field(field.name);
|
|
461
|
+
if (implementedField && isInAPISchema(implementedField)) {
|
|
462
|
+
errors.push(ERRORS.IMPLEMENTED_BY_INACCESSIBLE.err({
|
|
463
|
+
message:
|
|
464
|
+
`Field "${field.coordinate}" is @inaccessible but` +
|
|
465
|
+
` implements the interface field` +
|
|
466
|
+
` "${implementedField.coordinate}", which is in the API` +
|
|
467
|
+
` schema.`,
|
|
468
|
+
nodes: field.sourceAST,
|
|
469
|
+
extensions: {
|
|
470
|
+
inaccessible_elements: [field.coordinate],
|
|
471
|
+
inaccessible_referencers: [implementedField.coordinate],
|
|
472
|
+
}
|
|
473
|
+
}));
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
} else {
|
|
477
|
+
// Descend into the field's arguments.
|
|
478
|
+
for (const argument of field.arguments()) {
|
|
479
|
+
if (isInaccessible(argument)) {
|
|
480
|
+
// When an argument is hidden (but its ancestors aren't), we
|
|
481
|
+
// check that it isn't a required argument of its field.
|
|
482
|
+
if (argument.isRequired()) {
|
|
483
|
+
errors.push(ERRORS.REQUIRED_INACCESSIBLE.err({
|
|
484
|
+
message:
|
|
485
|
+
`Argument "${argument.coordinate}" is @inaccessible but` +
|
|
486
|
+
` is a required argument of its field.`,
|
|
487
|
+
nodes: argument.sourceAST,
|
|
488
|
+
extensions: {
|
|
489
|
+
inaccessible_elements: [argument.coordinate],
|
|
490
|
+
inaccessible_referencers: [argument.coordinate],
|
|
491
|
+
}
|
|
492
|
+
}));
|
|
493
|
+
}
|
|
494
|
+
// When an argument is hidden (but its ancestors aren't), we
|
|
495
|
+
// check that it isn't a required argument of any implementing
|
|
496
|
+
// fields in the API schema. This is because the GraphQL spec
|
|
497
|
+
// requires that any arguments of an implementing field that
|
|
498
|
+
// aren't in its implemented field are optional.
|
|
499
|
+
//
|
|
500
|
+
// You might be thinking that a required argument in an
|
|
501
|
+
// implementing field would necessitate that the implemented
|
|
502
|
+
// field would also require that argument (and thus the check
|
|
503
|
+
// above would also always error, removing the need for this
|
|
504
|
+
// one), but the GraphQL spec does not enforce this. E.g. it's
|
|
505
|
+
// valid GraphQL for the implementing and implemented arguments
|
|
506
|
+
// to be both non-nullable, but for just the implemented
|
|
507
|
+
// argument to have a default value. Not providing a value for
|
|
508
|
+
// the argument when querying the implemented type succeeds
|
|
509
|
+
// GraphQL operation validation, but results in input coercion
|
|
510
|
+
// failure for the field at runtime.
|
|
511
|
+
for (const implementingType of implementingTypes) {
|
|
512
|
+
const implementingField = implementingType.field(field.name);
|
|
513
|
+
assert(
|
|
514
|
+
implementingField,
|
|
515
|
+
"Schema should have been valid, but an implementing type" +
|
|
516
|
+
" did not implement one of this type's fields."
|
|
517
|
+
);
|
|
518
|
+
const implementingArgument = implementingField
|
|
519
|
+
.argument(argument.name);
|
|
520
|
+
assert(
|
|
521
|
+
implementingArgument,
|
|
522
|
+
"Schema should have been valid, but an implementing type" +
|
|
523
|
+
" did not implement one of this type's field's arguments."
|
|
524
|
+
);
|
|
525
|
+
if (
|
|
526
|
+
isInAPISchema(implementingArgument) &&
|
|
527
|
+
implementingArgument.isRequired()
|
|
528
|
+
) {
|
|
529
|
+
errors.push(ERRORS.REQUIRED_INACCESSIBLE.err({
|
|
530
|
+
message:
|
|
531
|
+
`Argument "${argument.coordinate}" is @inaccessible` +
|
|
532
|
+
` but is implemented by the required argument` +
|
|
533
|
+
` "${implementingArgument.coordinate}", which is` +
|
|
534
|
+
` in the API schema.`,
|
|
535
|
+
nodes: argument.sourceAST,
|
|
536
|
+
extensions: {
|
|
537
|
+
inaccessible_elements: [argument.coordinate],
|
|
538
|
+
inaccessible_referencers: [
|
|
539
|
+
implementingArgument.coordinate,
|
|
540
|
+
],
|
|
541
|
+
}
|
|
542
|
+
}));
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Arguments can be "referenced" by the corresponding arguments
|
|
547
|
+
// of any interfaces their parent type implements. When an
|
|
548
|
+
// argument is hidden (but its ancestors aren't), we check that
|
|
549
|
+
// such implemented arguments aren't in the API schema.
|
|
550
|
+
for (const implementedInterface of implementedInterfaces) {
|
|
551
|
+
const implementedArgument = implementedInterface
|
|
552
|
+
.field(field.name)
|
|
553
|
+
?.argument(argument.name);
|
|
554
|
+
if (
|
|
555
|
+
implementedArgument &&
|
|
556
|
+
isInAPISchema(implementedArgument)
|
|
557
|
+
) {
|
|
558
|
+
errors.push(ERRORS.IMPLEMENTED_BY_INACCESSIBLE.err({
|
|
559
|
+
message:
|
|
560
|
+
`Argument "${argument.coordinate}" is @inaccessible` +
|
|
561
|
+
` but implements the interface argument` +
|
|
562
|
+
` "${implementedArgument.coordinate}", which is in` +
|
|
563
|
+
` the API schema.`,
|
|
564
|
+
nodes: argument.sourceAST,
|
|
565
|
+
extensions: {
|
|
566
|
+
inaccessible_elements: [argument.coordinate],
|
|
567
|
+
inaccessible_referencers: [
|
|
568
|
+
implementedArgument.coordinate,
|
|
569
|
+
],
|
|
570
|
+
}
|
|
571
|
+
}));
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
} else if (type instanceof InputObjectType) {
|
|
579
|
+
for (const inputField of type.fields()) {
|
|
580
|
+
if (isInaccessible(inputField)) {
|
|
581
|
+
// When an input field is hidden (but its parent isn't), we check
|
|
582
|
+
// that it isn't a required argument of its field.
|
|
583
|
+
if (inputField.isRequired()) {
|
|
584
|
+
errors.push(ERRORS.REQUIRED_INACCESSIBLE.err({
|
|
585
|
+
message:
|
|
586
|
+
`Input field "${inputField.coordinate}" is @inaccessible` +
|
|
587
|
+
` but is a required input field of its type.`,
|
|
588
|
+
nodes: inputField.sourceAST,
|
|
589
|
+
extensions: {
|
|
590
|
+
inaccessible_elements: [inputField.coordinate],
|
|
591
|
+
inaccessible_referencers: [inputField.coordinate],
|
|
592
|
+
}
|
|
593
|
+
}));
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Input fields can be referenced by schema default values. When an
|
|
597
|
+
// input field is hidden (but its parent isn't), we check that the
|
|
598
|
+
// arguments/input fields with such default values aren't in the API
|
|
599
|
+
// schema.
|
|
600
|
+
assert(
|
|
601
|
+
defaultValueReferencers,
|
|
602
|
+
"Input fields can't be @inaccessible in v0.1, but default value" +
|
|
603
|
+
" referencers weren't computed (which is only skipped for v0.1)."
|
|
604
|
+
);
|
|
605
|
+
const referencers = defaultValueReferencers.get(inputField) ?? [];
|
|
606
|
+
for (const referencer of referencers) {
|
|
607
|
+
if (isInAPISchema(referencer)) {
|
|
608
|
+
errors.push(ERRORS.DEFAULT_VALUE_USES_INACCESSIBLE.err({
|
|
609
|
+
message:
|
|
610
|
+
`Input field "${inputField.coordinate}" is @inaccessible` +
|
|
611
|
+
` but is used in the default value of` +
|
|
612
|
+
` "${referencer.coordinate}", which is in the API schema.`,
|
|
613
|
+
nodes: type.sourceAST,
|
|
614
|
+
extensions: {
|
|
615
|
+
inaccessible_elements: [type.coordinate],
|
|
616
|
+
inaccessible_referencers: [referencer.coordinate],
|
|
617
|
+
}
|
|
618
|
+
}));
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
} else if (type instanceof EnumType) {
|
|
624
|
+
for (const enumValue of type.values) {
|
|
625
|
+
if (isInaccessible(enumValue)) {
|
|
626
|
+
// Enum values can be referenced by schema default values. When an
|
|
627
|
+
// enum value is hidden (but its parent isn't), we check that the
|
|
628
|
+
// arguments/input fields with such default values aren't in the API
|
|
629
|
+
// schema.
|
|
630
|
+
assert(
|
|
631
|
+
defaultValueReferencers,
|
|
632
|
+
"Enum values can't be @inaccessible in v0.1, but default value" +
|
|
633
|
+
" referencers weren't computed (which is only skipped for v0.1)."
|
|
634
|
+
);
|
|
635
|
+
const referencers = defaultValueReferencers.get(enumValue) ?? [];
|
|
636
|
+
for (const referencer of referencers) {
|
|
637
|
+
if (isInAPISchema(referencer)) {
|
|
638
|
+
errors.push(ERRORS.DEFAULT_VALUE_USES_INACCESSIBLE.err({
|
|
639
|
+
message:
|
|
640
|
+
`Enum value "${enumValue.coordinate}" is @inaccessible` +
|
|
641
|
+
` but is used in the default value of` +
|
|
642
|
+
` "${referencer.coordinate}", which is in the API schema.`,
|
|
643
|
+
nodes: type.sourceAST,
|
|
644
|
+
extensions: {
|
|
645
|
+
inaccessible_elements: [type.coordinate],
|
|
646
|
+
inaccessible_referencers: [referencer.coordinate],
|
|
647
|
+
}
|
|
648
|
+
}));
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const executableDirectiveLocationSet = new Set(executableDirectiveLocations);
|
|
658
|
+
for (const directive of schema.allDirectives()) {
|
|
659
|
+
const typeSystemLocations = directive.locations.filter((loc) =>
|
|
660
|
+
!executableDirectiveLocationSet.has(loc)
|
|
661
|
+
);
|
|
662
|
+
if (hasBuiltInName(directive)) {
|
|
663
|
+
// Built-in directives (and their descendants) aren't allowed to be
|
|
664
|
+
// @inaccessible, regardless of shadowing.
|
|
665
|
+
const inaccessibleElements =
|
|
666
|
+
fetchInaccessibleElementsDeep(directive);
|
|
667
|
+
if (inaccessibleElements.length > 0) {
|
|
668
|
+
errors.push(ERRORS.DISALLOWED_INACCESSIBLE.err({
|
|
669
|
+
message:
|
|
670
|
+
`Built-in directive "${directive.coordinate}" cannot use` +
|
|
671
|
+
` @inaccessible.`,
|
|
672
|
+
nodes: directive.sourceAST,
|
|
673
|
+
extensions: {
|
|
674
|
+
inaccessible_elements: inaccessibleElements
|
|
675
|
+
.map((element) => element.coordinate),
|
|
676
|
+
inaccessible_referencers: [directive.coordinate],
|
|
677
|
+
}
|
|
678
|
+
}));
|
|
679
|
+
}
|
|
680
|
+
} else if (isFeatureDefinition(directive)) {
|
|
681
|
+
// Core feature directives (and their descendants) aren't allowed to be
|
|
682
|
+
// @inaccessible.
|
|
683
|
+
const inaccessibleElements =
|
|
684
|
+
fetchInaccessibleElementsDeep(directive);
|
|
685
|
+
if (inaccessibleElements.length > 0) {
|
|
686
|
+
errors.push(ERRORS.DISALLOWED_INACCESSIBLE.err({
|
|
687
|
+
message:
|
|
688
|
+
`Core feature directive "${directive.coordinate}" cannot use` +
|
|
689
|
+
` @inaccessible.`,
|
|
690
|
+
nodes: directive.sourceAST,
|
|
691
|
+
extensions: {
|
|
692
|
+
inaccessible_elements: inaccessibleElements
|
|
693
|
+
.map((element) => element.coordinate),
|
|
694
|
+
inaccessible_referencers: [directive.coordinate],
|
|
695
|
+
}
|
|
696
|
+
}));
|
|
697
|
+
}
|
|
698
|
+
} else if (typeSystemLocations.length > 0) {
|
|
699
|
+
// Directives that can appear on type-system locations (and their
|
|
700
|
+
// descendants) aren't allowed to be @inaccessible.
|
|
701
|
+
const inaccessibleElements =
|
|
702
|
+
fetchInaccessibleElementsDeep(directive);
|
|
703
|
+
if (inaccessibleElements.length > 0) {
|
|
704
|
+
errors.push(ERRORS.DISALLOWED_INACCESSIBLE.err({
|
|
705
|
+
message:
|
|
706
|
+
`Directive "${directive.coordinate}" cannot use @inaccessible` +
|
|
707
|
+
` because it may be applied to these type-system locations:` +
|
|
708
|
+
` ${typeSystemLocations.join(', ')}.`,
|
|
709
|
+
nodes: directive.sourceAST,
|
|
710
|
+
extensions: {
|
|
711
|
+
inaccessible_elements: inaccessibleElements
|
|
712
|
+
.map((element) => element.coordinate),
|
|
713
|
+
inaccessible_referencers: [directive.coordinate],
|
|
714
|
+
}
|
|
715
|
+
}));
|
|
716
|
+
}
|
|
717
|
+
} else {
|
|
718
|
+
// At this point, we know the directive must be in the API schema. Descend
|
|
719
|
+
// into the directive's arguments.
|
|
720
|
+
for (const argument of directive.arguments()) {
|
|
721
|
+
// When an argument is hidden (but its parent isn't), we check that it
|
|
722
|
+
// isn't a required argument of its directive.
|
|
723
|
+
if (argument.isRequired()) {
|
|
724
|
+
if (isInaccessible(argument)) {
|
|
725
|
+
errors.push(ERRORS.REQUIRED_INACCESSIBLE.err({
|
|
726
|
+
message:
|
|
727
|
+
`Argument "${argument.coordinate}" is @inaccessible but is a` +
|
|
728
|
+
` required argument of its directive.`,
|
|
729
|
+
nodes: argument.sourceAST,
|
|
730
|
+
extensions: {
|
|
731
|
+
inaccessible_elements: [argument.coordinate],
|
|
732
|
+
inaccessible_referencers: [argument.coordinate],
|
|
733
|
+
}
|
|
734
|
+
}));
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
if (errors.length > 0) {
|
|
742
|
+
throw ErrGraphQLAPISchemaValidationFailed(errors);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
type DefaultValueReference = InputFieldDefinition | EnumValue;
|
|
747
|
+
type SchemaElementWithDefaultValue =
|
|
748
|
+
| ArgumentDefinition<
|
|
749
|
+
| DirectiveDefinition
|
|
750
|
+
| FieldDefinition<ObjectType | InterfaceType>>
|
|
751
|
+
| InputFieldDefinition;
|
|
752
|
+
|
|
753
|
+
// Default values in a schema may contain references to selectable elements that
|
|
754
|
+
// are @inaccessible (input fields and enum values). For a given schema, this
|
|
755
|
+
// function returns a map from such selectable elements to the elements with
|
|
756
|
+
// default values referencing them. (The default values of built-ins and their
|
|
757
|
+
// descendants are skipped.)
|
|
758
|
+
//
|
|
759
|
+
// This function assumes default values are coercible to their location types
|
|
760
|
+
// (see the comments for addValueReferences() for details).
|
|
761
|
+
function computeDefaultValueReferencers(
|
|
762
|
+
schema: Schema,
|
|
763
|
+
): Map<
|
|
764
|
+
DefaultValueReference,
|
|
765
|
+
SchemaElementWithDefaultValue[]
|
|
766
|
+
> {
|
|
767
|
+
const referencers = new Map<
|
|
768
|
+
DefaultValueReference,
|
|
769
|
+
SchemaElementWithDefaultValue[]
|
|
770
|
+
>();
|
|
771
|
+
|
|
772
|
+
function addReference(
|
|
773
|
+
reference: DefaultValueReference,
|
|
774
|
+
referencer: SchemaElementWithDefaultValue,
|
|
775
|
+
) {
|
|
776
|
+
const referencerList = referencers.get(reference) ?? [];
|
|
777
|
+
if (referencerList.length === 0) {
|
|
778
|
+
referencers.set(reference, referencerList);
|
|
779
|
+
}
|
|
780
|
+
referencerList.push(referencer);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Note that the fields/arguments/input fields for built-in schema elements
|
|
784
|
+
// can presumably only have types that are built-in types. Since built-ins and
|
|
785
|
+
// their children aren't allowed to be @inaccessible, this means we shouldn't
|
|
786
|
+
// have to worry about references within the default values of arguments and
|
|
787
|
+
// input fields of built-ins, which is why we skip them below.
|
|
788
|
+
for (const type of schema.allTypes()) {
|
|
789
|
+
if (hasBuiltInName(type)) continue;
|
|
790
|
+
|
|
791
|
+
// Scan object/interface field arguments.
|
|
792
|
+
if (
|
|
793
|
+
(type instanceof ObjectType) ||
|
|
794
|
+
(type instanceof InterfaceType)
|
|
795
|
+
) {
|
|
796
|
+
for (const field of type.fields()) {
|
|
797
|
+
for (const argument of field.arguments()) {
|
|
798
|
+
for (
|
|
799
|
+
const reference of computeDefaultValueReferences(argument)
|
|
800
|
+
) {
|
|
801
|
+
addReference(reference, argument);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Scan input object fields.
|
|
808
|
+
if (type instanceof InputObjectType) {
|
|
809
|
+
for (const inputField of type.fields()) {
|
|
810
|
+
for (
|
|
811
|
+
const reference of computeDefaultValueReferences(inputField)
|
|
812
|
+
) {
|
|
813
|
+
addReference(reference, inputField);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// Scan directive definition arguments.
|
|
820
|
+
for (const directive of schema.allDirectives()) {
|
|
821
|
+
if (hasBuiltInName(directive)) continue;
|
|
822
|
+
for (const argument of directive.arguments()) {
|
|
823
|
+
for (
|
|
824
|
+
const reference of computeDefaultValueReferences(argument)
|
|
825
|
+
) {
|
|
826
|
+
addReference(reference, argument);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
return referencers;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// For the given element, compute a list of input fields and enum values that
|
|
835
|
+
// are referenced in its default value (if any). This function assumes the
|
|
836
|
+
// default value is coercible to the element's type (see the comments for
|
|
837
|
+
// addValueReferences() for details).
|
|
838
|
+
function computeDefaultValueReferences(
|
|
839
|
+
element: SchemaElementWithDefaultValue,
|
|
840
|
+
): DefaultValueReference[] {
|
|
841
|
+
const references: DefaultValueReference[] = [];
|
|
842
|
+
addValueReferences(
|
|
843
|
+
element.defaultValue,
|
|
844
|
+
getInputType(element),
|
|
845
|
+
references,
|
|
846
|
+
)
|
|
847
|
+
return references;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function getInputType(element: SchemaElementWithDefaultValue): InputType {
|
|
851
|
+
const type = element.type;
|
|
852
|
+
assert(
|
|
853
|
+
type,
|
|
854
|
+
"Schema should have been valid, but argument/input field did not have type."
|
|
855
|
+
);
|
|
856
|
+
return type;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// For the given GraphQL input value (represented in the format implicitly
|
|
860
|
+
// defined in buildValue()) and its type, add any references to input fields and
|
|
861
|
+
// enum values in that input value to the given references list.
|
|
862
|
+
//
|
|
863
|
+
// Note that this function requires the input value to be coercible to its type,
|
|
864
|
+
// similar to the "Values of Correct Type" validation in the GraphQL spec.
|
|
865
|
+
// However, there are two noteable differences:
|
|
866
|
+
// 1. Variable references are not allowed.
|
|
867
|
+
// 2. Scalar values are not required to be coercible (due to machine-specific
|
|
868
|
+
// differences in input coercion rules).
|
|
869
|
+
//
|
|
870
|
+
// As it turns out, building a Schema object validates this (and a bit more)
|
|
871
|
+
// already, so in the interests of not duplicating validations/keeping the logic
|
|
872
|
+
// centralized, this code assumes the input values it receives satisfy the above
|
|
873
|
+
// validations.
|
|
874
|
+
//
|
|
875
|
+
// Accordingly, this function's code is structured very similarly to the
|
|
876
|
+
// valueToString() function, which makes similar assumptions about its given
|
|
877
|
+
// value. If any inconsistencies/invalidities are discovered, they will be
|
|
878
|
+
// silently ignored.
|
|
879
|
+
function addValueReferences(
|
|
880
|
+
value: any,
|
|
881
|
+
type: InputType,
|
|
882
|
+
references: DefaultValueReference[],
|
|
883
|
+
): void {
|
|
884
|
+
if (value === undefined || value === null) {
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
if (isNonNullType(type)) {
|
|
889
|
+
return addValueReferences(value, type.ofType, references);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
if (isScalarType(type)) {
|
|
893
|
+
// No need to look at scalar values.
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
if (isVariable(value)) {
|
|
898
|
+
// Values in schemas shouldn't use variables, but we silently ignore it.
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
if (Array.isArray(value)) {
|
|
903
|
+
if (isListType(type)) {
|
|
904
|
+
const itemType = type.ofType;
|
|
905
|
+
for (const item of value) {
|
|
906
|
+
addValueReferences(item, itemType, references);
|
|
907
|
+
}
|
|
908
|
+
} else {
|
|
909
|
+
// At this point a JS array can only be a list type, but we silently
|
|
910
|
+
// ignore when it's not.
|
|
911
|
+
}
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
if (isListType(type)) {
|
|
916
|
+
// Note that GraphQL spec coerces non-list items into single-element lists.
|
|
917
|
+
return addValueReferences(value, type.ofType, references);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if (typeof value === 'object') {
|
|
921
|
+
if (isInputObjectType(type)) {
|
|
922
|
+
// Silently ignore object keys that aren't in the input object.
|
|
923
|
+
for (const field of type.fields()) {
|
|
924
|
+
const fieldValue = value[field.name];
|
|
925
|
+
if (fieldValue !== undefined) {
|
|
926
|
+
references.push(field);
|
|
927
|
+
addValueReferences(fieldValue, field.type!, references);
|
|
928
|
+
} else {
|
|
929
|
+
// Silently ignore when required input fields are omitted.
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
} else {
|
|
933
|
+
// At this point a JS object can only be an input object type, but we
|
|
934
|
+
// silently ignore when it's not.
|
|
935
|
+
}
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
if (typeof value === 'string') {
|
|
940
|
+
if (isEnumType(type)) {
|
|
941
|
+
const enumValue = type.value(value);
|
|
942
|
+
if (enumValue !== undefined) {
|
|
943
|
+
references.push(enumValue);
|
|
944
|
+
} else {
|
|
945
|
+
// Silently ignore enum values that aren't in the enum type.
|
|
946
|
+
}
|
|
947
|
+
} else {
|
|
948
|
+
// At this point a JS string can only be an enum type, but we silently
|
|
949
|
+
// ignore when it's not.
|
|
950
|
+
}
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// This should be unreachable code, but we silently ignore when it's not.
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// Determine whether a given schema element has a built-in's name. Note that
|
|
959
|
+
// this is not the same as the isBuiltIn flag, due to shadowing definitions
|
|
960
|
+
// (which will not have the flag set).
|
|
961
|
+
function hasBuiltInName(element: NamedType | DirectiveDefinition): boolean {
|
|
962
|
+
const schema = element.schema();
|
|
963
|
+
if (
|
|
964
|
+
(element instanceof ObjectType) ||
|
|
965
|
+
(element instanceof InterfaceType) ||
|
|
966
|
+
(element instanceof UnionType) ||
|
|
967
|
+
(element instanceof ScalarType) ||
|
|
968
|
+
(element instanceof EnumType) ||
|
|
969
|
+
(element instanceof InputObjectType)
|
|
970
|
+
) {
|
|
971
|
+
return schema.builtInTypes(true).some((type) =>
|
|
972
|
+
type.name === element.name
|
|
973
|
+
);
|
|
974
|
+
} else if (element instanceof DirectiveDefinition) {
|
|
975
|
+
return schema.builtInDirectives(true).some((directive) =>
|
|
976
|
+
directive.name === element.name
|
|
56
977
|
);
|
|
57
978
|
}
|
|
979
|
+
assert(false, "Unreachable code, element is of unknown type.")
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// Remove schema elements marked with @inaccessible in the schema, assuming the
|
|
983
|
+
// schema has been validated with validateInaccessibleElements().
|
|
984
|
+
//
|
|
985
|
+
// Note the schema that results from this may not necessarily be valid GraphQL
|
|
986
|
+
// until core feature definitions have been removed by removeFeatureElements().
|
|
987
|
+
function removeInaccessibleElementsAssumingValid(
|
|
988
|
+
schema: Schema,
|
|
989
|
+
inaccessibleDirective: DirectiveDefinition,
|
|
990
|
+
): void {
|
|
991
|
+
function isInaccessible(element: SchemaElement<any, any>): boolean {
|
|
992
|
+
return element.hasAppliedDirective(inaccessibleDirective);
|
|
993
|
+
}
|
|
58
994
|
|
|
59
995
|
for (const type of schema.types()) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
996
|
+
if (isInaccessible(type)) {
|
|
997
|
+
type.remove();
|
|
998
|
+
} else {
|
|
999
|
+
if ((type instanceof ObjectType) || (type instanceof InterfaceType)) {
|
|
1000
|
+
for (const field of type.fields()) {
|
|
1001
|
+
if (isInaccessible(field)) {
|
|
1002
|
+
field.remove();
|
|
1003
|
+
} else {
|
|
1004
|
+
for (const argument of field.arguments()) {
|
|
1005
|
+
if (isInaccessible(argument)) {
|
|
1006
|
+
argument.remove();
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
} else if (type instanceof InputObjectType) {
|
|
1012
|
+
for (const inputField of type.fields()) {
|
|
1013
|
+
if (isInaccessible(inputField)) {
|
|
1014
|
+
inputField.remove();
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
} else if (type instanceof EnumType) {
|
|
1018
|
+
for (const enumValue of type.values) {
|
|
1019
|
+
if (isInaccessible(enumValue)) {
|
|
1020
|
+
enumValue.remove();
|
|
76
1021
|
}
|
|
77
1022
|
}
|
|
78
|
-
// Other references can be:
|
|
79
|
-
// - the type may have been a root type: in that case the schema will simply not have a root for that kind.
|
|
80
|
-
// - the type may have been part of a union: it will have been removed from that union. This can leave the union empty but ...
|
|
81
|
-
// - the type may an interface that other types implements: those other will simply not implement the (non-existing) interface.
|
|
82
1023
|
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
for (const directive of schema.directives()) {
|
|
1028
|
+
for (const argument of directive.arguments()) {
|
|
1029
|
+
if (isInaccessible(argument)) {
|
|
1030
|
+
argument.remove();
|
|
1031
|
+
}
|
|
86
1032
|
}
|
|
87
1033
|
}
|
|
88
1034
|
}
|