graphql-buddy 0.1.0 → 0.1.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/package.json +1 -1
- package/src/api/commands/validate.test.ts +6 -0
- package/src/api/shared/validateDocuments.ts +68 -16
- package/test_fixtures/documents/fragment_library/schema/Foo.graphql +7 -0
- package/test_fixtures/documents/missing_root_type/documents/AllFoo.graphql +5 -0
- package/test_fixtures/documents/missing_root_type/schema/Foo.graphql +7 -0
- package/test_fixtures/documents/fragment_library/schema/schema.graphql +0 -20
package/package.json
CHANGED
|
@@ -79,6 +79,12 @@ describe(`validate`, () => {
|
|
|
79
79
|
).resolves.toStrictEqual(expect.any(String));
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
+
it(`throws for operations when the schema has no matching root type.`, async () => {
|
|
83
|
+
await expect(
|
|
84
|
+
validate.validate(documentsFixture(`missing_root_type`)),
|
|
85
|
+
).rejects.toThrowError(`Schema does not define a query root type.`);
|
|
86
|
+
});
|
|
87
|
+
|
|
82
88
|
it(`still validates field selections inside fragment-only files.`, async () => {
|
|
83
89
|
await expect(
|
|
84
90
|
validate.validate(documentsFixture(`invalid_fragment`)),
|
|
@@ -22,24 +22,76 @@ export const validateDocuments = (options: ValidateDocumentsOptions): void => {
|
|
|
22
22
|
continue;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
// via `#import`. Validated in isolation they would trip
|
|
27
|
-
// `NoUnusedFragmentsRule`, so we drop that single rule for them. Every
|
|
28
|
-
// other rule, most importantly that each referenced field, argument, and
|
|
29
|
-
// type actually exists, are still enforced.
|
|
30
|
-
const definesOperation = source.document.definitions.some(
|
|
31
|
-
(definition) => definition.kind === graphql.Kind.OPERATION_DEFINITION,
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
const errors = graphql.validate(
|
|
35
|
-
schema,
|
|
36
|
-
source.document,
|
|
37
|
-
graphql.specifiedRules.filter(
|
|
38
|
-
(rule) => definesOperation || rule !== graphql.NoUnusedFragmentsRule,
|
|
39
|
-
),
|
|
40
|
-
);
|
|
25
|
+
const errors = validateDocument(schema, source.document);
|
|
41
26
|
if (errors.length > 0) {
|
|
42
27
|
throw errors[0];
|
|
43
28
|
}
|
|
44
29
|
}
|
|
45
30
|
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Validates a single document against the schema.
|
|
34
|
+
*
|
|
35
|
+
* This deliberately does not use `graphql.validate`. That function begins by
|
|
36
|
+
* asserting the *schema* is valid, which among other things requires a query
|
|
37
|
+
* root type. Fragment libraries are frequently validated against only the
|
|
38
|
+
* type definitions they select from.
|
|
39
|
+
*
|
|
40
|
+
* @param schema - The parsed graphql schema.
|
|
41
|
+
* @param document - The document to validate.
|
|
42
|
+
*
|
|
43
|
+
* @returns Any errors found in the document.
|
|
44
|
+
*/
|
|
45
|
+
const validateDocument = (
|
|
46
|
+
schema: graphql.GraphQLSchema,
|
|
47
|
+
document: graphql.DocumentNode,
|
|
48
|
+
): Array<graphql.GraphQLError> => {
|
|
49
|
+
const errors: Array<graphql.GraphQLError> = [];
|
|
50
|
+
|
|
51
|
+
const operations = document.definitions.filter(
|
|
52
|
+
(definition): definition is graphql.OperationDefinitionNode =>
|
|
53
|
+
definition.kind === graphql.Kind.OPERATION_DEFINITION,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// Ensure each operation matches a root GraphQL type. This is not natively
|
|
57
|
+
// checked by the `ValidationContext` because the validation context only
|
|
58
|
+
// inspects operations whose type information is available.
|
|
59
|
+
for (const operation of operations) {
|
|
60
|
+
if (schema.getRootType(operation.operation) == null) {
|
|
61
|
+
errors.push(
|
|
62
|
+
new graphql.GraphQLError(
|
|
63
|
+
`Schema does not define a ${operation.operation} root type.`,
|
|
64
|
+
{ nodes: operation },
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Fragment-only documents are fragment "libraries" that operations pull in
|
|
71
|
+
// via `#import`. Validated in isolation they would trip
|
|
72
|
+
// `NoUnusedFragmentsRule`, so we drop that single rule for them. Every
|
|
73
|
+
// other rule, most importantly that each referenced field, argument, and
|
|
74
|
+
// type actually exists, are still enforced.
|
|
75
|
+
const rules = graphql.specifiedRules.filter(
|
|
76
|
+
(rule) => operations.length > 0 || rule !== graphql.NoUnusedFragmentsRule,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const typeInfo = new graphql.TypeInfo(schema);
|
|
80
|
+
const context = new graphql.ValidationContext(
|
|
81
|
+
schema,
|
|
82
|
+
document,
|
|
83
|
+
typeInfo,
|
|
84
|
+
(error) => {
|
|
85
|
+
errors.push(error);
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
graphql.visit(
|
|
89
|
+
document,
|
|
90
|
+
graphql.visitWithTypeInfo(
|
|
91
|
+
typeInfo,
|
|
92
|
+
graphql.visitInParallel(rules.map((rule) => rule(context))),
|
|
93
|
+
),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return errors;
|
|
97
|
+
};
|