/
validate-documents.ts
107 lines (89 loc) · 3.08 KB
/
validate-documents.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import {
Kind,
validate,
GraphQLSchema,
GraphQLError,
specifiedRules,
FragmentDefinitionNode,
ValidationContext,
ASTVisitor,
} from 'graphql';
import { Source } from './loaders';
import AggregateError from '@ardatan/aggregate-error';
export type ValidationRule = (context: ValidationContext) => ASTVisitor;
const DEFAULT_EFFECTIVE_RULES = createDefaultRules();
export interface LoadDocumentError {
readonly filePath: string;
readonly errors: ReadonlyArray<GraphQLError>;
}
export async function validateGraphQlDocuments(
schema: GraphQLSchema,
documentFiles: Source[],
effectiveRules: ValidationRule[] = DEFAULT_EFFECTIVE_RULES
): Promise<ReadonlyArray<LoadDocumentError>> {
const allFragments: FragmentDefinitionNode[] = [];
documentFiles.forEach(documentFile => {
if (documentFile.document) {
for (const definitionNode of documentFile.document.definitions) {
if (definitionNode.kind === Kind.FRAGMENT_DEFINITION) {
allFragments.push(definitionNode);
}
}
}
});
const allErrors: LoadDocumentError[] = [];
await Promise.all(
documentFiles.map(async documentFile => {
const documentToValidate = {
kind: Kind.DOCUMENT,
definitions: [...allFragments, ...documentFile.document.definitions].filter((definition, index, list) => {
if (definition.kind === Kind.FRAGMENT_DEFINITION) {
const firstIndex = list.findIndex(
def => def.kind === Kind.FRAGMENT_DEFINITION && def.name.value === definition.name.value
);
const isDuplicated = firstIndex !== index;
if (isDuplicated) {
return false;
}
}
return true;
}),
};
const errors = validate(schema, documentToValidate, effectiveRules);
if (errors.length > 0) {
allErrors.push({
filePath: documentFile.location,
errors,
});
}
})
);
return allErrors;
}
export function checkValidationErrors(loadDocumentErrors: ReadonlyArray<LoadDocumentError>): void | never {
if (loadDocumentErrors.length > 0) {
const errors: Error[] = [];
for (const loadDocumentError of loadDocumentErrors) {
for (const graphQLError of loadDocumentError.errors) {
const error = new Error();
error.name = 'GraphQLDocumentError';
error.message = `${error.name}: ${graphQLError.message}`;
error.stack = error.message;
graphQLError.locations.forEach(
location => (error.stack += `\n at ${loadDocumentError.filePath}:${location.line}:${location.column}`)
);
errors.push(error);
}
}
throw new AggregateError(errors);
}
}
function createDefaultRules() {
const ignored = ['NoUnusedFragmentsRule', 'NoUnusedVariablesRule', 'KnownDirectivesRule'];
// GraphQL v14 has no Rule suffix in function names
// Adding `*Rule` makes validation backwards compatible
ignored.forEach(rule => {
ignored.push(rule.replace(/Rule$/, ''));
});
return specifiedRules.filter((f: (...args: any[]) => any) => !ignored.includes(f.name));
}