@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/federation.ts
CHANGED
|
@@ -45,13 +45,15 @@ import { KnownTypeNamesInFederationRule } from "./validation/KnownTypeNamesInFed
|
|
|
45
45
|
import { buildSchema, buildSchemaFromAST } from "./buildSchema";
|
|
46
46
|
import { parseSelectionSet, selectionOfElement, SelectionSet } from './operations';
|
|
47
47
|
import { TAG_VERSIONS } from "./tagSpec";
|
|
48
|
+
import { INACCESSIBLE_VERSIONS } from "./inaccessibleSpec";
|
|
48
49
|
import {
|
|
49
50
|
errorCodeDef,
|
|
50
51
|
ErrorCodeDefinition,
|
|
51
52
|
ERROR_CATEGORIES,
|
|
52
53
|
ERRORS,
|
|
54
|
+
withModifiedErrorMessage,
|
|
53
55
|
} from "./error";
|
|
54
|
-
import { computeShareables } from "./
|
|
56
|
+
import { computeShareables } from "./precompute";
|
|
55
57
|
import {
|
|
56
58
|
CoreSpecDefinition,
|
|
57
59
|
FeatureVersion,
|
|
@@ -70,14 +72,18 @@ import {
|
|
|
70
72
|
externalDirectiveSpec,
|
|
71
73
|
extendsDirectiveSpec,
|
|
72
74
|
shareableDirectiveSpec,
|
|
73
|
-
|
|
75
|
+
overrideDirectiveSpec,
|
|
74
76
|
FEDERATION2_SPEC_DIRECTIVES,
|
|
77
|
+
ALL_FEDERATION_DIRECTIVES_DEFAULT_NAMES,
|
|
78
|
+
FEDERATION2_ONLY_SPEC_DIRECTIVES,
|
|
75
79
|
} from "./federationSpec";
|
|
76
80
|
import { defaultPrintOptions, PrintOptions as PrintOptions, printSchema } from "./print";
|
|
77
81
|
import { createObjectTypeSpecification, createScalarTypeSpecification, createUnionTypeSpecification } from "./directiveAndTypeSpecification";
|
|
82
|
+
import { didYouMean, suggestionList } from "./suggestions";
|
|
78
83
|
|
|
79
84
|
const linkSpec = LINK_VERSIONS.latest();
|
|
80
85
|
const tagSpec = TAG_VERSIONS.latest();
|
|
86
|
+
const inaccessibleSpec = INACCESSIBLE_VERSIONS.latest();
|
|
81
87
|
const federationSpec = FEDERATION_VERSIONS.latest();
|
|
82
88
|
|
|
83
89
|
// We don't let user use this as a subgraph name. That allows us to use it in `query graphs` to name the source of roots
|
|
@@ -86,6 +92,8 @@ const federationSpec = FEDERATION_VERSIONS.latest();
|
|
|
86
92
|
// disallowing it feels like more a good thing than a real restriction).
|
|
87
93
|
export const FEDERATION_RESERVED_SUBGRAPH_NAME = '_';
|
|
88
94
|
|
|
95
|
+
export const FEDERATION_UNNAMED_SUBGRAPH_NAME = '<unnamed>';
|
|
96
|
+
|
|
89
97
|
const FEDERATION_OMITTED_VALIDATION_RULES = [
|
|
90
98
|
// We allow subgraphs to declare an extension even if the subgraph itself doesn't have a corresponding definition.
|
|
91
99
|
// The implication being that the definition is in another subgraph.
|
|
@@ -102,6 +110,7 @@ const FEDERATION_SPECIFIC_VALIDATION_RULES = [
|
|
|
102
110
|
|
|
103
111
|
const FEDERATION_VALIDATION_RULES = specifiedSDLRules.filter(rule => !FEDERATION_OMITTED_VALIDATION_RULES.includes(rule)).concat(FEDERATION_SPECIFIC_VALIDATION_RULES);
|
|
104
112
|
|
|
113
|
+
|
|
105
114
|
// Returns a list of the coordinate of all the fields in the selection that are marked external.
|
|
106
115
|
function validateFieldSetSelections(
|
|
107
116
|
directiveName: string,
|
|
@@ -257,53 +266,49 @@ function validateAllFieldSet<TParent extends SchemaElement<any, any>>(
|
|
|
257
266
|
}
|
|
258
267
|
}
|
|
259
268
|
|
|
260
|
-
export function
|
|
261
|
-
const
|
|
269
|
+
export function collectUsedFields(metadata: FederationMetadata): Set<FieldDefinition<CompositeType>> {
|
|
270
|
+
const usedFields = new Set<FieldDefinition<CompositeType>>();
|
|
262
271
|
|
|
263
272
|
// Collects all external fields used by a key, requires or provides
|
|
264
|
-
|
|
265
|
-
metadata,
|
|
273
|
+
collectUsedFieldsForDirective<CompositeType>(
|
|
266
274
|
metadata.keyDirective(),
|
|
267
275
|
type => type,
|
|
268
|
-
|
|
276
|
+
usedFields,
|
|
269
277
|
);
|
|
270
|
-
|
|
271
|
-
metadata,
|
|
278
|
+
collectUsedFieldsForDirective<FieldDefinition<CompositeType>>(
|
|
272
279
|
metadata.requiresDirective(),
|
|
273
280
|
field => field.parent!,
|
|
274
|
-
|
|
281
|
+
usedFields,
|
|
275
282
|
);
|
|
276
|
-
|
|
277
|
-
metadata,
|
|
283
|
+
collectUsedFieldsForDirective<FieldDefinition<CompositeType>>(
|
|
278
284
|
metadata.providesDirective(),
|
|
279
285
|
field => {
|
|
280
286
|
const type = baseType(field.type!);
|
|
281
287
|
return isCompositeType(type) ? type : undefined;
|
|
282
288
|
},
|
|
283
|
-
|
|
289
|
+
usedFields,
|
|
284
290
|
);
|
|
285
291
|
|
|
286
|
-
// Collects all
|
|
287
|
-
for (const itfType of metadata.schema.
|
|
292
|
+
// Collects all fields used to satisfy an interface constraint
|
|
293
|
+
for (const itfType of metadata.schema.interfaceTypes()) {
|
|
288
294
|
const runtimeTypes = itfType.possibleRuntimeTypes();
|
|
289
295
|
for (const field of itfType.fields()) {
|
|
290
296
|
for (const runtimeType of runtimeTypes) {
|
|
291
297
|
const implemField = runtimeType.field(field.name);
|
|
292
|
-
if (implemField
|
|
293
|
-
|
|
298
|
+
if (implemField) {
|
|
299
|
+
usedFields.add(implemField);
|
|
294
300
|
}
|
|
295
301
|
}
|
|
296
302
|
}
|
|
297
303
|
}
|
|
298
304
|
|
|
299
|
-
return
|
|
305
|
+
return usedFields;
|
|
300
306
|
}
|
|
301
307
|
|
|
302
|
-
function
|
|
303
|
-
metadata: FederationMetadata,
|
|
308
|
+
function collectUsedFieldsForDirective<TParent extends SchemaElement<any, any>>(
|
|
304
309
|
definition: DirectiveDefinition<{fields: any}>,
|
|
305
310
|
targetTypeExtractor: (element: TParent) => CompositeType | undefined,
|
|
306
|
-
|
|
311
|
+
usedFieldDefs: Set<FieldDefinition<CompositeType>>
|
|
307
312
|
) {
|
|
308
313
|
for (const application of definition.applications()) {
|
|
309
314
|
const type = targetTypeExtractor(application.parent! as TParent);
|
|
@@ -321,8 +326,7 @@ function collectUsedExternaFieldsForDirective<TParent extends SchemaElement<any,
|
|
|
321
326
|
directive: application as Directive<any, {fields: any}>,
|
|
322
327
|
includeInterfaceFieldsImplementations: true,
|
|
323
328
|
validate: false,
|
|
324
|
-
}).
|
|
325
|
-
.forEach((field) => usedExternalCoordinates.add(field.coordinate));
|
|
329
|
+
}).forEach((field) => usedFieldDefs.add(field));
|
|
326
330
|
}
|
|
327
331
|
}
|
|
328
332
|
|
|
@@ -331,29 +335,26 @@ function collectUsedExternaFieldsForDirective<TParent extends SchemaElement<any,
|
|
|
331
335
|
* interface implementation. Otherwise, the field declaration is somewhat useless.
|
|
332
336
|
*/
|
|
333
337
|
function validateAllExternalFieldsUsed(metadata: FederationMetadata, errorCollector: GraphQLError[]): void {
|
|
334
|
-
const allUsedExternals = collectUsedExternalFieldsCoordinates(metadata);
|
|
335
338
|
for (const type of metadata.schema.types()) {
|
|
336
339
|
if (!isObjectType(type) && !isInterfaceType(type)) {
|
|
337
340
|
continue;
|
|
338
341
|
}
|
|
339
342
|
for (const field of type.fields()) {
|
|
340
|
-
if (!metadata.isFieldExternal(field) ||
|
|
343
|
+
if (!metadata.isFieldExternal(field) || metadata.isFieldUsed(field)) {
|
|
341
344
|
continue;
|
|
342
345
|
}
|
|
343
346
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}));
|
|
350
|
-
}
|
|
347
|
+
errorCollector.push(ERRORS.EXTERNAL_UNUSED.err({
|
|
348
|
+
message: `Field "${field.coordinate}" is marked @external but is not used in any federation directive (@key, @provides, @requires) or to satisfy an interface;`
|
|
349
|
+
+ ' the field declaration has no use and should be removed (or the field should not be @external).',
|
|
350
|
+
nodes: field.sourceAST,
|
|
351
|
+
}));
|
|
351
352
|
}
|
|
352
353
|
}
|
|
353
354
|
}
|
|
354
355
|
|
|
355
356
|
function validateNoExternalOnInterfaceFields(metadata: FederationMetadata, errorCollector: GraphQLError[]) {
|
|
356
|
-
for (const itf of metadata.schema.
|
|
357
|
+
for (const itf of metadata.schema.interfaceTypes()) {
|
|
357
358
|
for (const field of itf.fields()) {
|
|
358
359
|
if (metadata.isFieldExternal(field)) {
|
|
359
360
|
errorCollector.push(ERRORS.EXTERNAL_ON_INTERFACE.err({
|
|
@@ -365,10 +366,6 @@ function validateNoExternalOnInterfaceFields(metadata: FederationMetadata, error
|
|
|
365
366
|
}
|
|
366
367
|
}
|
|
367
368
|
|
|
368
|
-
function isFieldSatisfyingInterface(field: FieldDefinition<ObjectType | InterfaceType>): boolean {
|
|
369
|
-
return field.parent.interfaces().some(itf => itf.field(field.name));
|
|
370
|
-
}
|
|
371
|
-
|
|
372
369
|
/**
|
|
373
370
|
* Register errors when, for an interface field, some of the implementations of that field are @external
|
|
374
371
|
* _and_ not all of those field implementation have the same type (which otherwise allowed because field
|
|
@@ -379,7 +376,7 @@ function isFieldSatisfyingInterface(field: FieldDefinition<ObjectType | Interfac
|
|
|
379
376
|
*/
|
|
380
377
|
function validateInterfaceRuntimeImplementationFieldsTypes(
|
|
381
378
|
itf: InterfaceType,
|
|
382
|
-
metadata: FederationMetadata,
|
|
379
|
+
metadata: FederationMetadata,
|
|
383
380
|
errorCollector: GraphQLError[],
|
|
384
381
|
): void {
|
|
385
382
|
const requiresDirective = federationMetadata(itf.schema())?.requiresDirective();
|
|
@@ -417,19 +414,10 @@ function formatFieldsToReturnType([type, implems]: [string, FieldDefinition<Obje
|
|
|
417
414
|
return `${joinStrings(implems.map(printFieldCoordinate))} ${implems.length == 1 ? 'has' : 'have'} type "${type}"`;
|
|
418
415
|
}
|
|
419
416
|
|
|
420
|
-
function checkIfFed2Schema(schema: Schema): boolean {
|
|
421
|
-
const core = schema.coreFeatures;
|
|
422
|
-
if (!core) {
|
|
423
|
-
return false
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
const federationFeature = core.getByIdentity(federationSpec.identity);
|
|
427
|
-
return !!federationFeature && federationFeature.url.version.satisfies(new FeatureVersion(2, 0));
|
|
428
|
-
}
|
|
429
|
-
|
|
430
417
|
export class FederationMetadata {
|
|
431
418
|
private _externalTester?: ExternalTester;
|
|
432
419
|
private _sharingPredicate?: (field: FieldDefinition<CompositeType>) => boolean;
|
|
420
|
+
private _fieldUsedPredicate?: (field: FieldDefinition<CompositeType>) => boolean;
|
|
433
421
|
private _isFed2Schema?: boolean;
|
|
434
422
|
|
|
435
423
|
constructor(readonly schema: Schema) {
|
|
@@ -439,15 +427,21 @@ export class FederationMetadata {
|
|
|
439
427
|
this._externalTester = undefined;
|
|
440
428
|
this._sharingPredicate = undefined;
|
|
441
429
|
this._isFed2Schema = undefined;
|
|
430
|
+
this._fieldUsedPredicate = undefined;
|
|
442
431
|
}
|
|
443
432
|
|
|
444
433
|
isFed2Schema(): boolean {
|
|
445
434
|
if (!this._isFed2Schema) {
|
|
446
|
-
|
|
435
|
+
const feature = this.federationFeature();
|
|
436
|
+
this._isFed2Schema = !!feature && feature.url.version.satisfies(new FeatureVersion(2, 0))
|
|
447
437
|
}
|
|
448
438
|
return this._isFed2Schema;
|
|
449
439
|
}
|
|
450
440
|
|
|
441
|
+
federationFeature(): CoreFeature | undefined {
|
|
442
|
+
return this.schema.coreFeatures?.getByIdentity(federationSpec.identity);
|
|
443
|
+
}
|
|
444
|
+
|
|
451
445
|
private externalTester(): ExternalTester {
|
|
452
446
|
if (!this._externalTester) {
|
|
453
447
|
this._externalTester = new ExternalTester(this.schema);
|
|
@@ -462,6 +456,18 @@ export class FederationMetadata {
|
|
|
462
456
|
return this._sharingPredicate;
|
|
463
457
|
}
|
|
464
458
|
|
|
459
|
+
private fieldUsedPredicate(): (field: FieldDefinition<CompositeType>) => boolean {
|
|
460
|
+
if (!this._fieldUsedPredicate) {
|
|
461
|
+
const usedFields = collectUsedFields(this);
|
|
462
|
+
this._fieldUsedPredicate = (field: FieldDefinition<CompositeType>) => !!usedFields.has(field);
|
|
463
|
+
}
|
|
464
|
+
return this._fieldUsedPredicate;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
isFieldUsed(field: FieldDefinition<CompositeType>): boolean {
|
|
468
|
+
return this.fieldUsedPredicate()(field);
|
|
469
|
+
}
|
|
470
|
+
|
|
465
471
|
isFieldExternal(field: FieldDefinition<any> | InputFieldDefinition) {
|
|
466
472
|
return this.externalTester().isExternal(field);
|
|
467
473
|
}
|
|
@@ -533,11 +539,15 @@ export class FederationMetadata {
|
|
|
533
539
|
return this.getFederationDirective(keyDirectiveSpec.name);
|
|
534
540
|
}
|
|
535
541
|
|
|
542
|
+
overrideDirective(): DirectiveDefinition<{from: string}> {
|
|
543
|
+
return this.getFederationDirective(overrideDirectiveSpec.name);
|
|
544
|
+
}
|
|
545
|
+
|
|
536
546
|
extendsDirective(): DirectiveDefinition<Record<string, never>> {
|
|
537
547
|
return this.getFederationDirective(extendsDirectiveSpec.name);
|
|
538
548
|
}
|
|
539
549
|
|
|
540
|
-
externalDirective(): DirectiveDefinition<
|
|
550
|
+
externalDirective(): DirectiveDefinition<{reason: string}> {
|
|
541
551
|
return this.getFederationDirective(externalDirectiveSpec.name);
|
|
542
552
|
}
|
|
543
553
|
|
|
@@ -554,11 +564,17 @@ export class FederationMetadata {
|
|
|
554
564
|
}
|
|
555
565
|
|
|
556
566
|
tagDirective(): DirectiveDefinition<{name: string}> {
|
|
557
|
-
return this.getFederationDirective(tagDirectiveSpec.name);
|
|
567
|
+
return this.getFederationDirective(tagSpec.tagDirectiveSpec.name);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
inaccessibleDirective(): DirectiveDefinition<{}> {
|
|
571
|
+
return this.getFederationDirective(
|
|
572
|
+
inaccessibleSpec.inaccessibleDirectiveSpec.name
|
|
573
|
+
);
|
|
558
574
|
}
|
|
559
575
|
|
|
560
576
|
allFederationDirectives(): DirectiveDefinition[] {
|
|
561
|
-
const baseDirectives = [
|
|
577
|
+
const baseDirectives: DirectiveDefinition[] = [
|
|
562
578
|
this.keyDirective(),
|
|
563
579
|
this.externalDirective(),
|
|
564
580
|
this.requiresDirective(),
|
|
@@ -567,7 +583,7 @@ export class FederationMetadata {
|
|
|
567
583
|
this.extendsDirective(),
|
|
568
584
|
];
|
|
569
585
|
return this.isFed2Schema()
|
|
570
|
-
? baseDirectives.concat(this.shareableDirective())
|
|
586
|
+
? baseDirectives.concat(this.shareableDirective(), this.inaccessibleDirective(), this.overrideDirective())
|
|
571
587
|
: baseDirectives;
|
|
572
588
|
}
|
|
573
589
|
|
|
@@ -603,6 +619,10 @@ export class FederationMetadata {
|
|
|
603
619
|
}
|
|
604
620
|
|
|
605
621
|
export class FederationBlueprint extends SchemaBlueprint {
|
|
622
|
+
constructor(private readonly withRootTypeRenaming: boolean) {
|
|
623
|
+
super();
|
|
624
|
+
}
|
|
625
|
+
|
|
606
626
|
onAddedCoreFeature(schema: Schema, feature: CoreFeature) {
|
|
607
627
|
super.onAddedCoreFeature(schema, feature);
|
|
608
628
|
if (feature.url.identity === federationIdentity) {
|
|
@@ -613,19 +633,21 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
613
633
|
}
|
|
614
634
|
}
|
|
615
635
|
|
|
616
|
-
onMissingDirectiveDefinition(schema: Schema, name: string): DirectiveDefinition | undefined {
|
|
636
|
+
onMissingDirectiveDefinition(schema: Schema, name: string, args?: {[key: string]: any}): DirectiveDefinition | GraphQLError[] | undefined {
|
|
617
637
|
if (name === linkDirectiveDefaultName) {
|
|
618
|
-
|
|
619
|
-
|
|
638
|
+
const url = args && (args['url'] as string | undefined);
|
|
639
|
+
const as = url && url.startsWith(linkSpec.identity) ? (args['as'] as string | undefined) : undefined;
|
|
640
|
+
const errors = linkSpec.addDefinitionsToSchema(schema, as);
|
|
641
|
+
return errors.length > 0 ? errors : schema.directive(name);
|
|
620
642
|
}
|
|
621
|
-
return super.onMissingDirectiveDefinition(schema, name);
|
|
643
|
+
return super.onMissingDirectiveDefinition(schema, name, args);
|
|
622
644
|
}
|
|
623
645
|
|
|
624
646
|
ignoreParsedField(type: NamedType, fieldName: string): boolean {
|
|
625
647
|
// Historically, federation 1 has accepted invalid schema, including some where the Query type included
|
|
626
648
|
// the definition of `_entities` (so `_entities(representations: [_Any!]!): [_Entity]!`) but _without_
|
|
627
649
|
// defining the `_Any` or `_Entity` type. So while we want to be stricter for fed2 (so this kind of
|
|
628
|
-
// really weird case can be fixed), we want fed2 to accept as much fed1 schema as possible.
|
|
650
|
+
// really weird case can be fixed), we want fed2 to accept as much fed1 schema as possible.
|
|
629
651
|
//
|
|
630
652
|
// So, to avoid this problem, we ignore the _entities and _service fields if we parse them from
|
|
631
653
|
// a fed1 input schema. Those will be added back anyway (along with the proper types) post-parsing.
|
|
@@ -643,8 +665,8 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
643
665
|
}
|
|
644
666
|
}
|
|
645
667
|
|
|
646
|
-
onDirectiveDefinitionAndSchemaParsed(schema: Schema) {
|
|
647
|
-
completeSubgraphSchema(schema);
|
|
668
|
+
onDirectiveDefinitionAndSchemaParsed(schema: Schema): GraphQLError[] {
|
|
669
|
+
return completeSubgraphSchema(schema);
|
|
648
670
|
}
|
|
649
671
|
|
|
650
672
|
onInvalidation(schema: Schema) {
|
|
@@ -659,22 +681,24 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
659
681
|
|
|
660
682
|
// We rename all root type to their default names (we do here rather than in `prepareValidation` because
|
|
661
683
|
// that can actually fail).
|
|
662
|
-
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
684
|
+
if (this.withRootTypeRenaming) {
|
|
685
|
+
for (const k of allSchemaRootKinds) {
|
|
686
|
+
const type = schema.schemaDefinition.root(k)?.type;
|
|
687
|
+
const defaultName = defaultRootName(k);
|
|
688
|
+
if (type && type.name !== defaultName) {
|
|
689
|
+
// We first ensure there is no other type using the default root name. If there is, this is a
|
|
690
|
+
// composition error.
|
|
691
|
+
const existing = schema.type(defaultName);
|
|
692
|
+
if (existing) {
|
|
693
|
+
errors.push(ERROR_CATEGORIES.ROOT_TYPE_USED.get(k).err({
|
|
694
|
+
message: `The schema has a type named "${defaultName}" but it is not set as the ${k} root type ("${type.name}" is instead): `
|
|
672
695
|
+ 'this is not supported by federation. '
|
|
673
696
|
+ 'If a root type does not use its default name, there should be no other type with that default name.',
|
|
674
|
-
|
|
675
|
-
|
|
697
|
+
nodes: sourceASTs(type, existing),
|
|
698
|
+
}));
|
|
699
|
+
}
|
|
700
|
+
type.rename(defaultName);
|
|
676
701
|
}
|
|
677
|
-
type.rename(defaultName);
|
|
678
702
|
}
|
|
679
703
|
}
|
|
680
704
|
|
|
@@ -759,7 +783,7 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
759
783
|
}
|
|
760
784
|
}
|
|
761
785
|
|
|
762
|
-
for (const itf of schema.
|
|
786
|
+
for (const itf of schema.interfaceTypes()) {
|
|
763
787
|
validateInterfaceRuntimeImplementationFieldsTypes(itf, metadata, errors);
|
|
764
788
|
}
|
|
765
789
|
|
|
@@ -769,9 +793,52 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
769
793
|
validationRules(): readonly SDLValidationRule[] {
|
|
770
794
|
return FEDERATION_VALIDATION_RULES;
|
|
771
795
|
}
|
|
772
|
-
}
|
|
773
796
|
|
|
774
|
-
|
|
797
|
+
onUnknownDirectiveValidationError(schema: Schema, unknownDirectiveName: string, error: GraphQLError): GraphQLError {
|
|
798
|
+
const metadata = federationMetadata(schema);
|
|
799
|
+
assert(metadata, `This method should only have been called on a subgraph schema`)
|
|
800
|
+
if (ALL_FEDERATION_DIRECTIVES_DEFAULT_NAMES.includes(unknownDirectiveName)) {
|
|
801
|
+
// The directive name is "unknown" but it is a default federation directive name. So it means one of a few things
|
|
802
|
+
// happened:
|
|
803
|
+
// 1. it's a fed1 schema but the directive is a fed2 only one (only possible case for fed1 schema).
|
|
804
|
+
// 2. the directive has not been imported at all (so needs to be prefixed for it to work).
|
|
805
|
+
// 3. the directive has an `import`, but it's been aliased to another name.
|
|
806
|
+
if (metadata.isFed2Schema()) {
|
|
807
|
+
const federationFeature = metadata.federationFeature();
|
|
808
|
+
assert(federationFeature, 'Fed2 subgraph _must_ link to the federation feature')
|
|
809
|
+
const directiveNameInSchema = federationFeature.directiveNameInSchema(unknownDirectiveName);
|
|
810
|
+
if (directiveNameInSchema.startsWith(federationFeature.nameInSchema + '__')) {
|
|
811
|
+
// There is no import for that directive
|
|
812
|
+
return withModifiedErrorMessage(
|
|
813
|
+
error,
|
|
814
|
+
`${error.message} If you meant the "@${unknownDirectiveName}" federation directive, you should use fully-qualified name "@${directiveNameInSchema}" or add "@${unknownDirectiveName}" to the \`import\` argument of the @link to the federation specification.`
|
|
815
|
+
);
|
|
816
|
+
} else {
|
|
817
|
+
// There's an import, but it's renamed
|
|
818
|
+
return withModifiedErrorMessage(
|
|
819
|
+
error,
|
|
820
|
+
`${error.message} If you meant the "@${unknownDirectiveName}" federation directive, you should use "@${directiveNameInSchema}" as it is imported under that name in the @link to the federation specification of this schema.`
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
} else {
|
|
824
|
+
return withModifiedErrorMessage(
|
|
825
|
+
error,
|
|
826
|
+
`${error.message} If you meant the "@${unknownDirectiveName}" federation 2 directive, note that this schema is a federation 1 schema. To be a federation 2 schema, it needs to @link to the federation specifcation v2.`
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
} else if (!metadata.isFed2Schema()) {
|
|
830
|
+
// We could get here in the case where a fed1 schema has tried to use a fed2 directive but mispelled it.
|
|
831
|
+
const suggestions = suggestionList(unknownDirectiveName, FEDERATION2_ONLY_SPEC_DIRECTIVES.map((spec) => spec.name));
|
|
832
|
+
if (suggestions.length > 0) {
|
|
833
|
+
return withModifiedErrorMessage(
|
|
834
|
+
error,
|
|
835
|
+
`${error.message}${didYouMean(suggestions.map((s) => '@' + s))} If so, note that ${suggestions.length === 1 ? 'it is a federation 2 directive' : 'they are federation 2 directives'} but this schema is a federation 1 one. To be a federation 2 schema, it needs to @link to the federation specifcation v2.`
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return error;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
775
842
|
|
|
776
843
|
function findUnusedNamedForLinkDirective(schema: Schema): string | undefined {
|
|
777
844
|
if (!schema.directive(linkSpec.url.name)) {
|
|
@@ -800,7 +867,10 @@ export function setSchemaAsFed2Subgraph(schema: Schema) {
|
|
|
800
867
|
assert(spec.url.version.satisfies(linkSpec.version), `Fed2 schema must use @link with version >= 1.0, but schema uses ${spec.url}`);
|
|
801
868
|
} else {
|
|
802
869
|
const alias = findUnusedNamedForLinkDirective(schema);
|
|
803
|
-
linkSpec.addToSchema(schema, alias);
|
|
870
|
+
const errors = linkSpec.addToSchema(schema, alias);
|
|
871
|
+
if (errors.length > 0) {
|
|
872
|
+
throw ErrGraphQLValidationFailed(errors);
|
|
873
|
+
}
|
|
804
874
|
spec = linkSpec;
|
|
805
875
|
core = schema.coreFeatures;
|
|
806
876
|
assert(core, 'Schema should now be a core schema');
|
|
@@ -814,9 +884,16 @@ export function setSchemaAsFed2Subgraph(schema: Schema) {
|
|
|
814
884
|
import: FEDERATION2_SPEC_DIRECTIVES.map((spec) => `@${spec.name}`),
|
|
815
885
|
}
|
|
816
886
|
);
|
|
817
|
-
completeSubgraphSchema(schema);
|
|
887
|
+
const errors = completeSubgraphSchema(schema);
|
|
888
|
+
if (errors.length > 0) {
|
|
889
|
+
throw ErrGraphQLValidationFailed(errors);
|
|
890
|
+
}
|
|
818
891
|
}
|
|
819
892
|
|
|
893
|
+
// This is the full @link declaration as added by `asFed2SubgraphDocument`. It's here primarily for uses by tests that print and match
|
|
894
|
+
// subgraph schema to avoid having to update 20+ tests every time we use a new directive or the order of import changes ...
|
|
895
|
+
export const FEDERATION2_LINK_WTH_FULL_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override"])';
|
|
896
|
+
|
|
820
897
|
export function asFed2SubgraphDocument(document: DocumentNode): DocumentNode {
|
|
821
898
|
const fed2LinkExtension: SchemaExtensionNode = {
|
|
822
899
|
kind: Kind.SCHEMA_EXTENSION,
|
|
@@ -878,10 +955,11 @@ export function isEntityType(type: NamedType): boolean {
|
|
|
878
955
|
export function buildSubgraph(
|
|
879
956
|
name: string,
|
|
880
957
|
url: string,
|
|
881
|
-
source: DocumentNode | string
|
|
958
|
+
source: DocumentNode | string,
|
|
959
|
+
withRootTypeRenaming: boolean = true,
|
|
882
960
|
): Subgraph {
|
|
883
961
|
const buildOptions = {
|
|
884
|
-
blueprint:
|
|
962
|
+
blueprint: new FederationBlueprint(withRootTypeRenaming),
|
|
885
963
|
validate: false,
|
|
886
964
|
};
|
|
887
965
|
let subgraph: Subgraph;
|
|
@@ -891,7 +969,7 @@ export function buildSubgraph(
|
|
|
891
969
|
: buildSchemaFromAST(source, buildOptions)
|
|
892
970
|
subgraph = new Subgraph(name, url, schema);
|
|
893
971
|
} catch (e) {
|
|
894
|
-
if (e instanceof GraphQLError) {
|
|
972
|
+
if (e instanceof GraphQLError && name !== FEDERATION_UNNAMED_SUBGRAPH_NAME) {
|
|
895
973
|
throw addSubgraphToError(e, name, ERRORS.INVALID_GRAPHQL);
|
|
896
974
|
} else {
|
|
897
975
|
throw e;
|
|
@@ -901,27 +979,30 @@ export function buildSubgraph(
|
|
|
901
979
|
}
|
|
902
980
|
|
|
903
981
|
export function newEmptyFederation2Schema(): Schema {
|
|
904
|
-
const schema = new Schema(
|
|
982
|
+
const schema = new Schema(new FederationBlueprint(true));
|
|
905
983
|
setSchemaAsFed2Subgraph(schema);
|
|
906
984
|
return schema;
|
|
907
985
|
}
|
|
908
986
|
|
|
909
|
-
function completeSubgraphSchema(schema: Schema) {
|
|
987
|
+
function completeSubgraphSchema(schema: Schema): GraphQLError[] {
|
|
910
988
|
const coreFeatures = schema.coreFeatures;
|
|
911
989
|
if (coreFeatures) {
|
|
912
990
|
const fedFeature = coreFeatures.getByIdentity(federationIdentity);
|
|
913
991
|
if (fedFeature) {
|
|
914
|
-
completeFed2SubgraphSchema(schema);
|
|
992
|
+
return completeFed2SubgraphSchema(schema);
|
|
915
993
|
} else {
|
|
916
|
-
completeFed1SubgraphSchema(schema);
|
|
994
|
+
return completeFed1SubgraphSchema(schema);
|
|
917
995
|
}
|
|
918
996
|
} else {
|
|
919
997
|
const fedLink = schema.schemaDefinition.appliedDirectivesOf(linkDirectiveDefaultName).find(isFedSpecLinkDirective);
|
|
920
998
|
if (fedLink) {
|
|
921
|
-
linkSpec.addToSchema(schema);
|
|
922
|
-
|
|
999
|
+
const errors = linkSpec.addToSchema(schema);
|
|
1000
|
+
if (errors.length > 0) {
|
|
1001
|
+
return errors;
|
|
1002
|
+
}
|
|
1003
|
+
return completeFed2SubgraphSchema(schema);
|
|
923
1004
|
} else {
|
|
924
|
-
completeFed1SubgraphSchema(schema);
|
|
1005
|
+
return completeFed1SubgraphSchema(schema);
|
|
925
1006
|
}
|
|
926
1007
|
}
|
|
927
1008
|
}
|
|
@@ -931,15 +1012,16 @@ function isFedSpecLinkDirective(directive: Directive<SchemaDefinition>): directi
|
|
|
931
1012
|
return directive.name === linkDirectiveDefaultName && args['url'] && (args['url'] as string).startsWith(federationIdentity);
|
|
932
1013
|
}
|
|
933
1014
|
|
|
934
|
-
function completeFed1SubgraphSchema(schema: Schema) {
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1015
|
+
function completeFed1SubgraphSchema(schema: Schema): GraphQLError[] {
|
|
1016
|
+
return [
|
|
1017
|
+
fieldSetTypeSpec.checkOrAdd(schema, '_' + fieldSetTypeSpec.name),
|
|
1018
|
+
keyDirectiveSpec.checkOrAdd(schema),
|
|
1019
|
+
requiresDirectiveSpec.checkOrAdd(schema),
|
|
1020
|
+
providesDirectiveSpec.checkOrAdd(schema),
|
|
1021
|
+
extendsDirectiveSpec.checkOrAdd(schema),
|
|
1022
|
+
externalDirectiveSpec.checkOrAdd(schema),
|
|
1023
|
+
tagSpec.tagDirectiveSpec.checkOrAdd(schema),
|
|
1024
|
+
].flat();
|
|
943
1025
|
}
|
|
944
1026
|
|
|
945
1027
|
function completeFed2SubgraphSchema(schema: Schema) {
|
|
@@ -951,13 +1033,13 @@ function completeFed2SubgraphSchema(schema: Schema) {
|
|
|
951
1033
|
|
|
952
1034
|
const spec = FEDERATION_VERSIONS.find(fedFeature.url.version);
|
|
953
1035
|
if (!spec) {
|
|
954
|
-
|
|
1036
|
+
return [ERRORS.UNKNOWN_FEDERATION_LINK_VERSION.err({
|
|
955
1037
|
message: `Invalid version ${fedFeature.url.version} for the federation feature in @link direction on schema`,
|
|
956
1038
|
nodes: fedFeature.directive.sourceAST
|
|
957
|
-
});
|
|
1039
|
+
})];
|
|
958
1040
|
}
|
|
959
1041
|
|
|
960
|
-
spec.addElementsToSchema(schema);
|
|
1042
|
+
return spec.addElementsToSchema(schema);
|
|
961
1043
|
}
|
|
962
1044
|
|
|
963
1045
|
export function parseFieldSetArgument({
|
|
@@ -1178,7 +1260,7 @@ export const serviceTypeSpec = createObjectTypeSpecification({
|
|
|
1178
1260
|
export const entityTypeSpec = createUnionTypeSpecification({
|
|
1179
1261
|
name: '_Entity',
|
|
1180
1262
|
membersFct: (schema) => {
|
|
1181
|
-
return schema.
|
|
1263
|
+
return schema.objectTypes().filter(isEntityType).map((t) => t.name);
|
|
1182
1264
|
},
|
|
1183
1265
|
});
|
|
1184
1266
|
|
|
@@ -1239,7 +1321,7 @@ export class Subgraph {
|
|
|
1239
1321
|
}
|
|
1240
1322
|
|
|
1241
1323
|
if (!queryType.field(serviceFieldName)) {
|
|
1242
|
-
queryType.addField(serviceFieldName, metadata.serviceType());
|
|
1324
|
+
queryType.addField(serviceFieldName, new NonNullType(metadata.serviceType()));
|
|
1243
1325
|
}
|
|
1244
1326
|
}
|
|
1245
1327
|
|