/
validate-documents.ts
116 lines (100 loc) · 3.35 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
108
109
110
111
112
113
114
115
116
import {
Kind,
validate,
GraphQLSchema,
GraphQLError,
specifiedRules,
FragmentDefinitionNode,
ValidationContext,
ASTVisitor,
DefinitionNode,
concatAST,
DocumentNode,
} from 'graphql';
import { Source } from './loaders';
import { AggregateError } from './AggregateError';
export type ValidationRule = (context: ValidationContext) => ASTVisitor;
export interface LoadDocumentError {
readonly filePath?: string;
readonly errors: ReadonlyArray<GraphQLError>;
}
export async function validateGraphQlDocuments(
schema: GraphQLSchema,
documentFiles: Source[],
effectiveRules?: ValidationRule[]
): Promise<ReadonlyArray<LoadDocumentError>> {
effectiveRules = effectiveRules || createDefaultRules();
const allFragmentMap = new Map<string, FragmentDefinitionNode>();
const documentFileObjectsToValidate: {
location?: string;
document: DocumentNode;
}[] = [];
for (const documentFile of documentFiles) {
if (documentFile.document) {
const definitionsToValidate: DefinitionNode[] = [];
for (const definitionNode of documentFile.document.definitions) {
if (definitionNode.kind === Kind.FRAGMENT_DEFINITION) {
allFragmentMap.set(definitionNode.name.value, definitionNode);
} else {
definitionsToValidate.push(definitionNode);
}
}
documentFileObjectsToValidate.push({
location: documentFile.location,
document: {
kind: Kind.DOCUMENT,
definitions: definitionsToValidate,
},
});
}
}
const allErrors: LoadDocumentError[] = [];
const allFragmentsDocument: DocumentNode = {
kind: Kind.DOCUMENT,
definitions: [...allFragmentMap.values()],
};
await Promise.all(
documentFileObjectsToValidate.map(async documentFile => {
const documentToValidate = concatAST([allFragmentsDocument, documentFile.document]);
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;
if (graphQLError.locations) {
for (const location of graphQLError.locations) {
error.stack += `\n at ${loadDocumentError.filePath}:${location.line}:${location.column}`;
}
}
errors.push(error);
}
}
throw new AggregateError(
errors,
`GraphQL Document Validation failed with ${errors.length} errors;
${errors.map((error, index) => `Error ${index}: ${error.stack}`).join('\n\n')}`
);
}
}
function createDefaultRules() {
const ignored = ['NoUnusedFragmentsRule', 'NoUnusedVariablesRule', 'KnownDirectivesRule'];
const v4ignored = ignored.map(rule => rule.replace(/Rule$/, ''));
return specifiedRules.filter(
(f: (...args: any[]) => any) => !ignored.includes(f.name) && !v4ignored.includes(f.name)
);
}