@apollo/federation-internals 2.1.0-alpha.0 → 2.1.0-alpha.1
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/dist/buildSchema.d.ts.map +1 -1
- package/dist/buildSchema.js +11 -10
- package/dist/buildSchema.js.map +1 -1
- package/dist/coreSpec.d.ts.map +1 -1
- package/dist/coreSpec.js +15 -42
- package/dist/coreSpec.js.map +1 -1
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +49 -98
- package/dist/definitions.js.map +1 -1
- package/dist/directiveAndTypeSpecification.js +14 -48
- package/dist/directiveAndTypeSpecification.js.map +1 -1
- package/dist/error.d.ts +19 -17
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +40 -7
- package/dist/error.js.map +1 -1
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +70 -123
- package/dist/federation.js.map +1 -1
- package/dist/inaccessibleSpec.js +43 -65
- package/dist/inaccessibleSpec.js.map +1 -1
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +5 -4
- package/dist/operations.js.map +1 -1
- package/dist/schemaUpgrader.js +2 -8
- package/dist/schemaUpgrader.js.map +1 -1
- package/dist/supergraphs.d.ts.map +1 -1
- package/dist/supergraphs.js +4 -4
- package/dist/supergraphs.js.map +1 -1
- package/dist/tagSpec.d.ts.map +1 -1
- package/dist/tagSpec.js +1 -3
- package/dist/tagSpec.js.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +26 -22
- package/dist/validate.js.map +1 -1
- package/dist/validation/KnownTypeNamesInFederationRule.js +1 -1
- package/dist/validation/KnownTypeNamesInFederationRule.js.map +1 -1
- package/dist/values.d.ts.map +1 -1
- package/dist/values.js +19 -18
- package/dist/values.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/subgraphValidation.test.ts +75 -0
- package/src/buildSchema.ts +11 -18
- package/src/coreSpec.ts +47 -43
- package/src/definitions.ts +64 -96
- package/src/directiveAndTypeSpecification.ts +50 -48
- package/src/error.ts +87 -41
- package/src/federation.ts +142 -131
- package/src/inaccessibleSpec.ts +207 -191
- package/src/operations.ts +5 -5
- package/src/schemaUpgrader.ts +8 -8
- package/src/supergraphs.ts +5 -4
- package/src/tagSpec.ts +2 -3
- package/src/validate.ts +60 -52
- package/src/validation/KnownTypeNamesInFederationRule.ts +1 -1
- package/src/values.ts +19 -18
- package/tsconfig.test.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
package/src/coreSpec.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ASTNode, DirectiveLocation, GraphQLError, StringValueNode } from "graphql";
|
|
2
2
|
import { URL } from "url";
|
|
3
|
-
import { CoreFeature, Directive, DirectiveDefinition, EnumType, ErrGraphQLAPISchemaValidationFailed, ErrGraphQLValidationFailed, InputType, ListType, NamedType, NonNullType, ScalarType, Schema, SchemaDefinition, SchemaElement } from "./definitions";
|
|
3
|
+
import { CoreFeature, Directive, DirectiveDefinition, EnumType, ErrGraphQLAPISchemaValidationFailed, ErrGraphQLValidationFailed, InputType, ListType, NamedType, NonNullType, ScalarType, Schema, SchemaDefinition, SchemaElement, sourceASTs } from "./definitions";
|
|
4
4
|
import { sameType } from "./types";
|
|
5
5
|
import { err } from '@apollo/core-schema';
|
|
6
6
|
import { assert, firstOf } from './utils';
|
|
@@ -185,10 +185,10 @@ export function extractCoreFeatureImports(url: FeatureUrl, directive: Directive<
|
|
|
185
185
|
continue;
|
|
186
186
|
}
|
|
187
187
|
if (typeof elt !== 'object') {
|
|
188
|
-
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
189
|
-
|
|
190
|
-
nodes: directive.sourceAST
|
|
191
|
-
|
|
188
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
189
|
+
`Invalid sub-value ${valueToString(elt)} for @link(import:) argument: values should be either strings or input object values of the form { name: "<importedElement>", as: "<alias>" }.`,
|
|
190
|
+
{ nodes: directive.sourceAST },
|
|
191
|
+
));
|
|
192
192
|
continue;
|
|
193
193
|
}
|
|
194
194
|
let name: string | undefined;
|
|
@@ -196,28 +196,28 @@ export function extractCoreFeatureImports(url: FeatureUrl, directive: Directive<
|
|
|
196
196
|
switch (key) {
|
|
197
197
|
case 'name':
|
|
198
198
|
if (typeof value !== 'string') {
|
|
199
|
-
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
200
|
-
|
|
201
|
-
nodes: directive.sourceAST
|
|
202
|
-
|
|
199
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
200
|
+
`Invalid value for the "name" field for sub-value ${valueToString(elt)} of @link(import:) argument: must be a string.`,
|
|
201
|
+
{ nodes: directive.sourceAST },
|
|
202
|
+
));
|
|
203
203
|
continue importArgLoop;
|
|
204
204
|
}
|
|
205
205
|
name = value;
|
|
206
206
|
break;
|
|
207
207
|
case 'as':
|
|
208
208
|
if (typeof value !== 'string') {
|
|
209
|
-
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
210
|
-
|
|
211
|
-
nodes: directive.sourceAST
|
|
212
|
-
|
|
209
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
210
|
+
`Invalid value for the "as" field for sub-value ${valueToString(elt)} of @link(import:) argument: must be a string.`,
|
|
211
|
+
{ nodes: directive.sourceAST },
|
|
212
|
+
));
|
|
213
213
|
continue importArgLoop;
|
|
214
214
|
}
|
|
215
215
|
break;
|
|
216
216
|
default:
|
|
217
|
-
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
218
|
-
|
|
219
|
-
nodes: directive.sourceAST
|
|
220
|
-
|
|
217
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
218
|
+
`Unknown field "${key}" for sub-value ${valueToString(elt)} of @link(import:) argument.`,
|
|
219
|
+
{ nodes: directive.sourceAST },
|
|
220
|
+
));
|
|
221
221
|
continue importArgLoop;
|
|
222
222
|
}
|
|
223
223
|
}
|
|
@@ -226,24 +226,24 @@ export function extractCoreFeatureImports(url: FeatureUrl, directive: Directive<
|
|
|
226
226
|
imports.push(i);
|
|
227
227
|
if (i.as) {
|
|
228
228
|
if (i.name.charAt(0) === '@' && i.as.charAt(0) !== '@') {
|
|
229
|
-
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
230
|
-
|
|
231
|
-
nodes: directive.sourceAST
|
|
232
|
-
|
|
229
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
230
|
+
`Invalid @link import renaming: directive "${i.name}" imported name should start with a '@' character, but got "${i.as}".`,
|
|
231
|
+
{ nodes: directive.sourceAST },
|
|
232
|
+
));
|
|
233
233
|
}
|
|
234
234
|
else if (i.name.charAt(0) !== '@' && i.as.charAt(0) === '@') {
|
|
235
|
-
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
236
|
-
|
|
237
|
-
nodes: directive.sourceAST
|
|
238
|
-
|
|
235
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
236
|
+
`Invalid @link import renaming: type "${i.name}" imported name should not start with a '@' character, but got "${i.as}" (or, if @${i.name} is a directive, then it should be referred to with a '@').`,
|
|
237
|
+
{ nodes: directive.sourceAST },
|
|
238
|
+
));
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
241
|
validateImportedName(name, knownElements, errors, directive);
|
|
242
242
|
} else {
|
|
243
|
-
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
244
|
-
|
|
245
|
-
nodes: directive.sourceAST
|
|
246
|
-
|
|
243
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
244
|
+
`Invalid sub-value ${valueToString(elt)} for @link(import:) argument: missing mandatory "name" field.`,
|
|
245
|
+
{ nodes: directive.sourceAST },
|
|
246
|
+
));
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
249
|
|
|
@@ -264,10 +264,10 @@ function validateImportedName(name: string, knownElements: string[] | undefined,
|
|
|
264
264
|
details = didYouMean(suggestions);
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
|
-
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
268
|
-
|
|
269
|
-
nodes: directive.sourceAST
|
|
270
|
-
|
|
267
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
268
|
+
`Cannot import unknown element "${name}".${details}`,
|
|
269
|
+
{ nodes: directive.sourceAST },
|
|
270
|
+
));
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
273
|
|
|
@@ -419,9 +419,9 @@ export class CoreSpecDefinition extends FeatureDefinition {
|
|
|
419
419
|
// Already exists with the same version, let it be.
|
|
420
420
|
return [];
|
|
421
421
|
} else {
|
|
422
|
-
return [ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
423
|
-
|
|
424
|
-
|
|
422
|
+
return [ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
423
|
+
`Cannot add feature ${this} to the schema, it already uses ${existingCore.coreItself.url}`
|
|
424
|
+
)];
|
|
425
425
|
}
|
|
426
426
|
}
|
|
427
427
|
|
|
@@ -555,7 +555,7 @@ export class FeatureVersion {
|
|
|
555
555
|
public static parse(input: string): FeatureVersion {
|
|
556
556
|
const match = input.match(this.VERSION_RE)
|
|
557
557
|
if (!match) {
|
|
558
|
-
throw
|
|
558
|
+
throw ERRORS.INVALID_LINK_IDENTIFIER.err(`Expected a version string (of the form v1.2), got ${input}`);
|
|
559
559
|
}
|
|
560
560
|
return new this(+match[1], +match[2])
|
|
561
561
|
}
|
|
@@ -663,17 +663,17 @@ export class FeatureUrl {
|
|
|
663
663
|
public static parse(input: string, node?: ASTNode): FeatureUrl {
|
|
664
664
|
const url = new URL(input)
|
|
665
665
|
if (!url.pathname || url.pathname === '/') {
|
|
666
|
-
throw
|
|
666
|
+
throw ERRORS.INVALID_LINK_IDENTIFIER.err(`Missing path in feature url '${url}'`, { nodes: node })
|
|
667
667
|
}
|
|
668
668
|
const path = url.pathname.split('/')
|
|
669
669
|
const verStr = path.pop()
|
|
670
670
|
if (!verStr) {
|
|
671
|
-
throw
|
|
671
|
+
throw ERRORS.INVALID_LINK_IDENTIFIER.err(`Missing version component in feature url '${url}'`, { nodes: node })
|
|
672
672
|
}
|
|
673
673
|
const version = FeatureVersion.parse(verStr)
|
|
674
674
|
const name = path[path.length - 1]
|
|
675
675
|
if (!name) {
|
|
676
|
-
throw
|
|
676
|
+
throw ERRORS.INVALID_LINK_IDENTIFIER.err(`Missing feature name component in feature url '${url}'`, { nodes: node })
|
|
677
677
|
}
|
|
678
678
|
const element = url.hash ? url.hash.slice(1): undefined
|
|
679
679
|
url.hash = ''
|
|
@@ -804,11 +804,15 @@ export function removeAllCoreFeatures(schema: Schema) {
|
|
|
804
804
|
for (const { feature, type, references } of typeReferences) {
|
|
805
805
|
const referencesInSchema = references.filter(r => r.isAttached());
|
|
806
806
|
if (referencesInSchema.length > 0) {
|
|
807
|
-
|
|
807
|
+
// Note: using REFERENCED_INACCESSIBLE is slightly abusive because the reference element is not marked
|
|
808
|
+
// @inacessible exactly. Instead, it is inacessible due to core elements being removed, but that's very
|
|
809
|
+
// very close semantically. Overall, adding a publicly documented error code just to minor difference
|
|
810
|
+
// doesn't feel worth it, especially since that case is super unlikely in the first place (and, as
|
|
811
|
+
// the prior comment says, may one day be removed too).
|
|
812
|
+
errors.push(ERRORS.REFERENCED_INACCESSIBLE.err(
|
|
808
813
|
`Cannot remove elements of feature ${feature} as feature type ${type}` +
|
|
809
814
|
` is referenced by elements: ${referencesInSchema.join(', ')}`,
|
|
810
|
-
|
|
811
|
-
.filter(n => n !== undefined) as ASTNode[]
|
|
815
|
+
{ nodes: sourceASTs(...references) },
|
|
812
816
|
));
|
|
813
817
|
}
|
|
814
818
|
}
|
package/src/definitions.ts
CHANGED
|
@@ -43,7 +43,7 @@ import { specifiedSDLRules } from "graphql/validation/specifiedRules";
|
|
|
43
43
|
import { validateSchema } from "./validate";
|
|
44
44
|
import { createDirectiveSpecification, createScalarTypeSpecification, DirectiveSpecification, TypeSpecification } from "./directiveAndTypeSpecification";
|
|
45
45
|
import { didYouMean, suggestionList } from "./suggestions";
|
|
46
|
-
import { withModifiedErrorMessage } from "./error";
|
|
46
|
+
import { ERRORS, withModifiedErrorMessage } from "./error";
|
|
47
47
|
|
|
48
48
|
const validationErrorCode = 'GraphQLValidationFailed';
|
|
49
49
|
const DEFAULT_VALIDATION_ERROR_MESSAGE = 'The schema is not a valid GraphQL schema.';
|
|
@@ -325,7 +325,7 @@ export function typeFromAST(schema: Schema, node: TypeNode): Type {
|
|
|
325
325
|
default:
|
|
326
326
|
const type = schema.type(node.name.value);
|
|
327
327
|
if (!type) {
|
|
328
|
-
throw
|
|
328
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Unknown type "${node.name.value}"`, { nodes: node });
|
|
329
329
|
}
|
|
330
330
|
return type;
|
|
331
331
|
}
|
|
@@ -477,9 +477,7 @@ abstract class Element<TParent extends SchemaElement<any, any> | Schema | Direct
|
|
|
477
477
|
// to a schema could bring a whole hierarchy of types and directives for instance). If they are attached, it only work if
|
|
478
478
|
// it's to the same schema, but you have to check.
|
|
479
479
|
// Overall, it's simpler to force attaching elements before you add other elements to them.
|
|
480
|
-
|
|
481
|
-
throw error(`Cannot modify detached element ${this}`);
|
|
482
|
-
}
|
|
480
|
+
assert(this.isAttached(), () => `Cannot modify detached element ${this}`);
|
|
483
481
|
}
|
|
484
482
|
}
|
|
485
483
|
|
|
@@ -534,7 +532,7 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
|
|
|
534
532
|
if (!def) {
|
|
535
533
|
throw this.schema().blueprint.onGraphQLJSValidationError(
|
|
536
534
|
this.schema(),
|
|
537
|
-
|
|
535
|
+
ERRORS.INVALID_GRAPHQL.err(`Unknown directive "@${nameOrDef}".`)
|
|
538
536
|
);
|
|
539
537
|
}
|
|
540
538
|
if (Array.isArray(def)) {
|
|
@@ -588,9 +586,7 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
|
|
|
588
586
|
protected abstract removeTypeReference(type: NamedType): void;
|
|
589
587
|
|
|
590
588
|
protected checkRemoval() {
|
|
591
|
-
|
|
592
|
-
throw error(`Cannot modify built-in ${this}`);
|
|
593
|
-
}
|
|
589
|
+
assert(!this.isElementBuiltIn() || Schema.prototype['canModifyBuiltIn'].call(this.schema()), () => `Cannot modify built-in ${this}`);
|
|
594
590
|
// We allow removals even on detached element because that doesn't particularly create issues (and we happen to do such
|
|
595
591
|
// removals on detached internally; though of course we could refactor the code if we wanted).
|
|
596
592
|
}
|
|
@@ -601,17 +597,13 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
|
|
|
601
597
|
// Ensure this element (the modified one), is not a built-in, or part of one.
|
|
602
598
|
let thisElement: SchemaElement<TOwnType, any> | Schema | undefined = this;
|
|
603
599
|
while (thisElement && thisElement instanceof SchemaElement) {
|
|
604
|
-
|
|
605
|
-
throw error(`Cannot modify built-in (or part of built-in) ${this}`);
|
|
606
|
-
}
|
|
600
|
+
assert(!thisElement.isElementBuiltIn(), () => `Cannot modify built-in (or part of built-in) ${this}`);
|
|
607
601
|
thisElement = thisElement.parent;
|
|
608
602
|
}
|
|
609
603
|
}
|
|
610
604
|
if (addedElement && addedElement.isAttached()) {
|
|
611
605
|
const thatSchema = addedElement.schema();
|
|
612
|
-
|
|
613
|
-
throw error(`Cannot add element ${addedElement} to ${this} as it is attached to another schema`);
|
|
614
|
-
}
|
|
606
|
+
assert(!thatSchema || thatSchema === this.schema(), () => `Cannot add element ${addedElement} to ${this} as it is attached to another schema`);
|
|
615
607
|
}
|
|
616
608
|
}
|
|
617
609
|
}
|
|
@@ -677,9 +669,7 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
|
|
|
677
669
|
if (this._extensions.has(extension)) {
|
|
678
670
|
return extension;
|
|
679
671
|
}
|
|
680
|
-
|
|
681
|
-
throw error(`Cannot add extension to type ${this}: it is already added to another type`);
|
|
682
|
-
}
|
|
672
|
+
assert(!extension.extendedElement, () => `Cannot add extension to type ${this}: it is already added to another type`);
|
|
683
673
|
this._extensions.add(extension);
|
|
684
674
|
Extension.prototype['setExtendedElement'].call(extension, this);
|
|
685
675
|
this.onModification();
|
|
@@ -830,10 +820,6 @@ export abstract class NamedSchemaElementWithType<TType extends Type, TOwnType ex
|
|
|
830
820
|
}
|
|
831
821
|
}
|
|
832
822
|
|
|
833
|
-
function error(message: string): GraphQLError {
|
|
834
|
-
return new GraphQLError(message);
|
|
835
|
-
}
|
|
836
|
-
|
|
837
823
|
abstract class BaseExtensionMember<TExtended extends ExtendableElement> extends Element<TExtended> {
|
|
838
824
|
private _extension?: Extension<TExtended>;
|
|
839
825
|
|
|
@@ -848,9 +834,7 @@ abstract class BaseExtensionMember<TExtended extends ExtendableElement> extends
|
|
|
848
834
|
setOfExtension(extension: Extension<TExtended> | undefined) {
|
|
849
835
|
this.checkUpdate();
|
|
850
836
|
// See similar comment on FieldDefinition.setOfExtension for why we have to cast.
|
|
851
|
-
|
|
852
|
-
throw error(`Cannot set object as part of the provided extension: it is not an extension of parent ${this.parent}`);
|
|
853
|
-
}
|
|
837
|
+
assert(!extension || this._parent?.extensions().has(extension as any), () => `Cannot set object as part of the provided extension: it is not an extension of parent ${this.parent}`);
|
|
854
838
|
this._extension = extension;
|
|
855
839
|
}
|
|
856
840
|
|
|
@@ -980,7 +964,7 @@ export class CoreFeatures {
|
|
|
980
964
|
this.add(coreItself);
|
|
981
965
|
const coreDef = findCoreSpecVersion(coreItself.url);
|
|
982
966
|
if (!coreDef) {
|
|
983
|
-
throw
|
|
967
|
+
throw ERRORS.UNKNOWN_LINK_VERSION.err(`Schema uses unknown version ${coreItself.url.version} of the ${coreItself.url.name} spec`);
|
|
984
968
|
}
|
|
985
969
|
this.coreDefinition = coreDef;
|
|
986
970
|
}
|
|
@@ -1010,7 +994,8 @@ export class CoreFeatures {
|
|
|
1010
994
|
const url = this.coreDefinition.extractFeatureUrl(args);
|
|
1011
995
|
const existing = this.byIdentity.get(url.identity);
|
|
1012
996
|
if (existing) {
|
|
1013
|
-
|
|
997
|
+
// TODO: we may want to lossen that limitation at some point. Including the same feature for 2 different major versions should be ok.
|
|
998
|
+
throw ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(`Duplicate inclusion of feature ${url.identity}`);
|
|
1014
999
|
}
|
|
1015
1000
|
const imports = extractCoreFeatureImports(url, typedDirective);
|
|
1016
1001
|
const feature = new CoreFeature(url, args.as ?? url.name, directive, imports, args.for);
|
|
@@ -1335,24 +1320,16 @@ export class Schema {
|
|
|
1335
1320
|
const existing = this.type(type.name);
|
|
1336
1321
|
if (existing) {
|
|
1337
1322
|
// Like for directive, we let user shadow built-in types, but the definition must be valid.
|
|
1338
|
-
|
|
1339
|
-
} else {
|
|
1340
|
-
throw error(`Type ${type} already exists in this schema`);
|
|
1341
|
-
}
|
|
1323
|
+
assert(existing.isBuiltIn, () => `Type ${type} already exists in this schema`);
|
|
1342
1324
|
}
|
|
1343
1325
|
if (type.isAttached()) {
|
|
1344
1326
|
// For convenience, let's not error out on adding an already added type.
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
}
|
|
1348
|
-
throw error(`Cannot add type ${type} to this schema; it is already attached to another schema`);
|
|
1327
|
+
assert(type.parent == this, () => `Cannot add type ${type} to this schema; it is already attached to another schema`);
|
|
1328
|
+
return type;
|
|
1349
1329
|
}
|
|
1350
1330
|
if (type.isBuiltIn) {
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
} else {
|
|
1354
|
-
throw error(`Cannot add built-in ${type} to this schema (built-ins can only be added at schema construction time)`);
|
|
1355
|
-
}
|
|
1331
|
+
assert(!this.isConstructed, `Cannot add built-in ${type} to this schema (built-ins can only be added at schema construction time)`);
|
|
1332
|
+
this._builtInTypes.set(type.name, type);
|
|
1356
1333
|
} else {
|
|
1357
1334
|
this._types.set(type.name, type);
|
|
1358
1335
|
}
|
|
@@ -1424,22 +1401,15 @@ export class Schema {
|
|
|
1424
1401
|
const existing = this.directive(definition.name);
|
|
1425
1402
|
// Note that we allow the schema to define a built-in manually (and the manual definition will shadow the
|
|
1426
1403
|
// built-in one). It's just that validation will ensure the definition ends up the one expected.
|
|
1427
|
-
|
|
1428
|
-
throw error(`Directive ${definition} already exists in this schema`);
|
|
1429
|
-
}
|
|
1404
|
+
assert(!existing || existing.isBuiltIn, () => `Directive ${definition} already exists in this schema`);
|
|
1430
1405
|
if (definition.isAttached()) {
|
|
1431
1406
|
// For convenience, let's not error out on adding an already added directive.
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
}
|
|
1435
|
-
throw error(`Cannot add directive ${definition} to this schema; it is already attached to another schema`);
|
|
1407
|
+
assert(definition.parent == this, () => `Cannot add directive ${definition} to this schema; it is already attached to another schema`);
|
|
1408
|
+
return definition;
|
|
1436
1409
|
}
|
|
1437
1410
|
if (definition.isBuiltIn) {
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
} else {
|
|
1441
|
-
throw error(`Cannot add built-in ${definition} to this schema (built-ins can only be added at schema construction time)`);
|
|
1442
|
-
}
|
|
1411
|
+
assert(!this.isConstructed, () => `Cannot add built-in ${definition} to this schema (built-ins can only be added at schema construction time)`);
|
|
1412
|
+
this._builtInDirectives.set(definition.name, definition);
|
|
1443
1413
|
} else {
|
|
1444
1414
|
this._directives.set(definition.name, definition);
|
|
1445
1415
|
}
|
|
@@ -1525,7 +1495,8 @@ export class Schema {
|
|
|
1525
1495
|
*/
|
|
1526
1496
|
elementByCoordinate(coordinate: string): NamedSchemaElement<any, any, any> | undefined {
|
|
1527
1497
|
if (!coordinate.match(coordinateRegexp)) {
|
|
1528
|
-
|
|
1498
|
+
// To be fair, graphQL coordinate is not yet officially part of the spec but well...
|
|
1499
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
|
|
1529
1500
|
}
|
|
1530
1501
|
|
|
1531
1502
|
const argStartIdx = coordinate.indexOf('(');
|
|
@@ -1538,7 +1509,7 @@ export class Schema {
|
|
|
1538
1509
|
const isDirective = typeOrDirectiveName.startsWith('@');
|
|
1539
1510
|
if (isDirective) {
|
|
1540
1511
|
if (fieldOrEnumName) {
|
|
1541
|
-
throw
|
|
1512
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
|
|
1542
1513
|
}
|
|
1543
1514
|
const directive = this.directive(typeOrDirectiveName.slice(1));
|
|
1544
1515
|
return argName ? directive?.argument(argName) : directive;
|
|
@@ -1554,16 +1525,16 @@ export class Schema {
|
|
|
1554
1525
|
return argName ? field?.argument(argName) : field;
|
|
1555
1526
|
case 'InputObjectType':
|
|
1556
1527
|
if (argName) {
|
|
1557
|
-
throw
|
|
1528
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
|
|
1558
1529
|
}
|
|
1559
1530
|
return type.field(fieldOrEnumName);
|
|
1560
1531
|
case 'EnumType':
|
|
1561
1532
|
if (argName) {
|
|
1562
|
-
throw
|
|
1533
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
|
|
1563
1534
|
}
|
|
1564
1535
|
return type.value(fieldOrEnumName);
|
|
1565
1536
|
default:
|
|
1566
|
-
throw
|
|
1537
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
|
|
1567
1538
|
}
|
|
1568
1539
|
}
|
|
1569
1540
|
}
|
|
@@ -1603,7 +1574,7 @@ export class SchemaDefinition extends SchemaElement<SchemaDefinition, Schema> {
|
|
|
1603
1574
|
const coreFeatures = schema.coreFeatures;
|
|
1604
1575
|
if (isCoreSpecDirectiveApplication(applied)) {
|
|
1605
1576
|
if (coreFeatures) {
|
|
1606
|
-
throw
|
|
1577
|
+
throw ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(`Invalid duplicate application of @core/@link`);
|
|
1607
1578
|
}
|
|
1608
1579
|
const schemaDirective = applied as Directive<SchemaDefinition, CoreOrLinkDirectiveArgs>;
|
|
1609
1580
|
const args = schemaDirective.arguments();
|
|
@@ -1636,9 +1607,9 @@ export class SchemaDefinition extends SchemaElement<SchemaDefinition, Schema> {
|
|
|
1636
1607
|
this.checkUpdate();
|
|
1637
1608
|
const obj = this.schema().type(nameOrType);
|
|
1638
1609
|
if (!obj) {
|
|
1639
|
-
throw
|
|
1610
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Cannot set schema ${rootKind} root to unknown type ${nameOrType}`);
|
|
1640
1611
|
} else if (obj.kind != 'ObjectType') {
|
|
1641
|
-
throw
|
|
1612
|
+
throw ERRORS.INVALID_GRAPHQL.err(`${defaultRootName(rootKind)} root type must be an Object type${rootKind === 'query' ? '' : ' if provided'}, it cannot be set to ${nameOrType} (an ${obj.kind}).`);
|
|
1642
1613
|
}
|
|
1643
1614
|
toSet = new RootType(rootKind, obj);
|
|
1644
1615
|
} else {
|
|
@@ -1670,9 +1641,7 @@ export class SchemaDefinition extends SchemaElement<SchemaDefinition, Schema> {
|
|
|
1670
1641
|
if (this._extensions.has(extension)) {
|
|
1671
1642
|
return extension;
|
|
1672
1643
|
}
|
|
1673
|
-
|
|
1674
|
-
throw error(`Cannot add extension to this schema: extension is already added to another schema`);
|
|
1675
|
-
}
|
|
1644
|
+
assert(!extension.extendedElement, 'Cannot add extension to this schema: extension is already added to another schema');
|
|
1676
1645
|
this._extensions.add(extension);
|
|
1677
1646
|
Extension.prototype['setExtendedElement'].call(extension, this);
|
|
1678
1647
|
this.onModification();
|
|
@@ -1804,9 +1773,9 @@ abstract class FieldBasedType<T extends (ObjectType | InterfaceType) & NamedSche
|
|
|
1804
1773
|
this.checkUpdate();
|
|
1805
1774
|
const maybeItf = this.schema().type(nameOrItfOrItfImpl);
|
|
1806
1775
|
if (!maybeItf) {
|
|
1807
|
-
throw
|
|
1776
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Cannot implement unknown type ${nameOrItfOrItfImpl}`);
|
|
1808
1777
|
} else if (maybeItf.kind != 'InterfaceType') {
|
|
1809
|
-
throw
|
|
1778
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Cannot implement non-interface type ${nameOrItfOrItfImpl} (of type ${maybeItf.kind})`);
|
|
1810
1779
|
}
|
|
1811
1780
|
itf = maybeItf;
|
|
1812
1781
|
} else {
|
|
@@ -1878,10 +1847,10 @@ abstract class FieldBasedType<T extends (ObjectType | InterfaceType) & NamedSche
|
|
|
1878
1847
|
toAdd = nameOrField;
|
|
1879
1848
|
}
|
|
1880
1849
|
if (this.field(toAdd.name)) {
|
|
1881
|
-
throw
|
|
1850
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Field ${toAdd.name} already exists on ${this}`);
|
|
1882
1851
|
}
|
|
1883
1852
|
if (type && !isOutputType(type)) {
|
|
1884
|
-
throw
|
|
1853
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Invalid input type ${type} for field ${toAdd.name}: object and interface field types should be output types.`);
|
|
1885
1854
|
}
|
|
1886
1855
|
this._fields.set(toAdd.name, toAdd);
|
|
1887
1856
|
this._cachedNonBuiltInFields = undefined;
|
|
@@ -2053,9 +2022,9 @@ export class UnionType extends BaseNamedType<OutputTypeReferencer, UnionType> {
|
|
|
2053
2022
|
this.checkUpdate();
|
|
2054
2023
|
const maybeObj = this.schema().type(nameOrTypeOrMember);
|
|
2055
2024
|
if (!maybeObj) {
|
|
2056
|
-
throw
|
|
2025
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Cannot add unknown type ${nameOrTypeOrMember} as member of union type ${this.name}`);
|
|
2057
2026
|
} else if (maybeObj.kind != 'ObjectType') {
|
|
2058
|
-
throw
|
|
2027
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Cannot add non-object type ${nameOrTypeOrMember} (of type ${maybeObj.kind}) as member of union type ${this.name}`);
|
|
2059
2028
|
}
|
|
2060
2029
|
obj = maybeObj;
|
|
2061
2030
|
} else {
|
|
@@ -2230,10 +2199,10 @@ export class InputObjectType extends BaseNamedType<InputTypeReferencer, InputObj
|
|
|
2230
2199
|
const toAdd = typeof nameOrField === 'string' ? new InputFieldDefinition(nameOrField) : nameOrField;
|
|
2231
2200
|
this.checkUpdate(toAdd);
|
|
2232
2201
|
if (this.field(toAdd.name)) {
|
|
2233
|
-
throw
|
|
2202
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Field ${toAdd.name} already exists on ${this}`);
|
|
2234
2203
|
}
|
|
2235
2204
|
if (type && !isInputType(type)) {
|
|
2236
|
-
throw
|
|
2205
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Invalid output type ${type} for field ${toAdd.name}: input field types should be input types.`);
|
|
2237
2206
|
}
|
|
2238
2207
|
this._fields.set(toAdd.name, toAdd);
|
|
2239
2208
|
this._cachedFieldsArray = undefined;
|
|
@@ -2385,15 +2354,15 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
|
|
|
2385
2354
|
// For some reason (bad codegen, maybe?), some users have field where a arg is defined more than one. And this doesn't seem rejected by
|
|
2386
2355
|
// graphQL (?). So we accept it, but ensure the types/default values are the same.
|
|
2387
2356
|
if (type && existing.type && !sameType(type, existing.type)) {
|
|
2388
|
-
throw
|
|
2357
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Argument ${toAdd.name} already exists on field ${this.name} with a different type (${existing.type})`);
|
|
2389
2358
|
}
|
|
2390
2359
|
if (defaultValue && (!existing.defaultValue || !valueEquals(defaultValue, existing.defaultValue))) {
|
|
2391
|
-
throw
|
|
2360
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Argument ${toAdd.name} already exists on field ${this.name} with a different default value (${valueToString(existing.defaultValue)})`);
|
|
2392
2361
|
}
|
|
2393
2362
|
return existing;
|
|
2394
2363
|
}
|
|
2395
2364
|
if (type && !isInputType(type)) {
|
|
2396
|
-
throw
|
|
2365
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Invalid output type ${type} for argument ${toAdd.name} of ${this}: arguments should be input types.`);
|
|
2397
2366
|
}
|
|
2398
2367
|
this._args.set(toAdd.name, toAdd);
|
|
2399
2368
|
Element.prototype['setParent'].call(toAdd, this);
|
|
@@ -2416,9 +2385,10 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
|
|
|
2416
2385
|
this.checkUpdate();
|
|
2417
2386
|
// It seems typescript "expand" `TParent` below into `ObjectType | Interface`, so it essentially lose the context that
|
|
2418
2387
|
// the `TParent` in `Extension<TParent>` will always match. Hence the `as any`.
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2388
|
+
assert(
|
|
2389
|
+
!extension || this._parent?.extensions().has(extension as any),
|
|
2390
|
+
() => `Cannot mark field ${this.name} as part of the provided extension: it is not an extension of field parent type ${this.parent}`
|
|
2391
|
+
);
|
|
2422
2392
|
this._extension = extension;
|
|
2423
2393
|
this.onModification();
|
|
2424
2394
|
}
|
|
@@ -2527,9 +2497,10 @@ export class InputFieldDefinition extends NamedSchemaElementWithType<InputType,
|
|
|
2527
2497
|
this.checkUpdate();
|
|
2528
2498
|
// It seems typescript "expand" `TParent` below into `ObjectType | Interface`, so it essentially lose the context that
|
|
2529
2499
|
// the `TParent` in `Extension<TParent>` will always match. Hence the `as any`.
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2500
|
+
assert(
|
|
2501
|
+
!extension || this._parent?.extensions().has(extension as any),
|
|
2502
|
+
() => `Cannot mark field ${this.name} as part of the provided extension: it is not an extension of field parent type ${this.parent}`,
|
|
2503
|
+
);
|
|
2533
2504
|
this._extension = extension;
|
|
2534
2505
|
this.onModification();
|
|
2535
2506
|
}
|
|
@@ -2676,9 +2647,10 @@ export class EnumValue extends NamedSchemaElement<EnumValue, EnumType, never> {
|
|
|
2676
2647
|
|
|
2677
2648
|
setOfExtension(extension: Extension<EnumType> | undefined) {
|
|
2678
2649
|
this.checkUpdate();
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2650
|
+
assert(
|
|
2651
|
+
!extension || this._parent?.extensions().has(extension as any),
|
|
2652
|
+
() => `Cannot mark field ${this.name} as part of the provided extension: it is not an extension of enum value parent type ${this.parent}`,
|
|
2653
|
+
);
|
|
2682
2654
|
this._extension = extension;
|
|
2683
2655
|
this.onModification();
|
|
2684
2656
|
}
|
|
@@ -2767,7 +2739,7 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
|
|
|
2767
2739
|
toAdd = nameOrArg;
|
|
2768
2740
|
}
|
|
2769
2741
|
if (this.argument(toAdd.name)) {
|
|
2770
|
-
throw
|
|
2742
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Argument ${toAdd.name} already exists on field ${this.name}`);
|
|
2771
2743
|
}
|
|
2772
2744
|
this._args.set(toAdd.name, toAdd);
|
|
2773
2745
|
Element.prototype['setParent'].call(toAdd, this);
|
|
@@ -2920,9 +2892,7 @@ export class Directive<
|
|
|
2920
2892
|
return this._args;
|
|
2921
2893
|
}
|
|
2922
2894
|
const definition = this.definition;
|
|
2923
|
-
|
|
2924
|
-
throw error(`Cannot include default values for arguments: cannot find directive definition for ${this.name}`);
|
|
2925
|
-
}
|
|
2895
|
+
assert(definition, () => `Cannot include default values for arguments: cannot find directive definition for ${this.name}`);
|
|
2926
2896
|
const updated = Object.create(null);
|
|
2927
2897
|
for (const argDef of definition.arguments()) {
|
|
2928
2898
|
updated[argDef.name] = withDefaultValues(this._args[argDef.name], argDef);
|
|
@@ -2978,13 +2948,11 @@ export class Directive<
|
|
|
2978
2948
|
this.checkUpdate();
|
|
2979
2949
|
if (extension) {
|
|
2980
2950
|
const parent = this.parent;
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
}
|
|
2986
|
-
throw error(`Can only mark directive parts of extensions when directly apply to type or schema definition.`);
|
|
2987
|
-
}
|
|
2951
|
+
assert(
|
|
2952
|
+
parent instanceof SchemaDefinition || parent instanceof BaseNamedType,
|
|
2953
|
+
'Can only mark directive parts of extensions when directly apply to type or schema definition.'
|
|
2954
|
+
);
|
|
2955
|
+
assert(parent.extensions().has(extension), () => `Cannot mark directive ${this.name} as part of the provided extension: it is not an extension of parent ${parent}`);
|
|
2988
2956
|
}
|
|
2989
2957
|
this._extension = extension;
|
|
2990
2958
|
this.onModification();
|
|
@@ -3252,7 +3220,7 @@ export function variableDefinitionsFromAST(schema: Schema, definitionNodes: read
|
|
|
3252
3220
|
for (const definitionNode of definitionNodes) {
|
|
3253
3221
|
if (!definitions.add(variableDefinitionFromAST(schema, definitionNode))) {
|
|
3254
3222
|
const name = definitionNode.variable.name.value;
|
|
3255
|
-
throw
|
|
3223
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Duplicate definition for variable ${name}`, { nodes: definitionNodes.filter(n => n.variable.name.value === name) });
|
|
3256
3224
|
}
|
|
3257
3225
|
}
|
|
3258
3226
|
return definitions;
|
|
@@ -3262,7 +3230,7 @@ export function variableDefinitionFromAST(schema: Schema, definitionNode: Variab
|
|
|
3262
3230
|
const variable = new Variable(definitionNode.variable.name.value);
|
|
3263
3231
|
const type = typeFromAST(schema, definitionNode.type);
|
|
3264
3232
|
if (!isInputType(type)) {
|
|
3265
|
-
throw
|
|
3233
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Invalid type "${type}" for variable $${variable}: not an input type`, { nodes: definitionNode.type });
|
|
3266
3234
|
}
|
|
3267
3235
|
const def = new VariableDefinition(
|
|
3268
3236
|
schema,
|