-
Notifications
You must be signed in to change notification settings - Fork 2k
/
keyFieldsMissingExternal.ts
118 lines (108 loc) 路 3.36 KB
/
keyFieldsMissingExternal.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
117
118
import {
visit,
visitWithTypeInfo,
TypeInfo,
parse,
GraphQLSchema,
GraphQLError,
specifiedDirectives,
} from 'graphql';
import { buildSchemaFromSDL } from 'apollo-graphql';
import { isNotNullOrUndefined } from 'apollo-env';
import { federationDirectives } from '../../../directives';
import { ServiceDefinition } from '../../types';
import {
findDirectivesOnTypeOrField,
isStringValueNode,
logServiceAndType,
errorWithCode,
} from '../../utils';
/**
* For every @key directive, it must reference a field marked as @external
*/
export const keyFieldsMissingExternal = ({
name: serviceName,
typeDefs,
}: ServiceDefinition) => {
const errors: GraphQLError[] = [];
// Build an array that accounts for all key directives on type extensions.
let keyDirectiveInfoOnTypeExtensions: {
typeName: string;
keyArgument: string;
}[] = [];
visit(typeDefs, {
ObjectTypeExtension(node) {
const keyDirectivesOnTypeExtension = findDirectivesOnTypeOrField(
node,
'key',
);
const keyDirectivesInfo = keyDirectivesOnTypeExtension
.map(keyDirective =>
keyDirective.arguments &&
isStringValueNode(keyDirective.arguments[0].value)
? {
typeName: node.name.value,
keyArgument: keyDirective.arguments[0].value.value,
}
: null,
)
.filter(isNotNullOrUndefined);
keyDirectiveInfoOnTypeExtensions.push(...keyDirectivesInfo);
},
});
// this allows us to build a partial schema
let schema = new GraphQLSchema({
query: undefined,
directives: [...specifiedDirectives, ...federationDirectives],
});
try {
schema = buildSchemaFromSDL(typeDefs, schema);
} catch (e) {
errors.push(e);
return errors;
}
const typeInfo = new TypeInfo(schema);
for (const { typeName, keyArgument } of keyDirectiveInfoOnTypeExtensions) {
const keyDirectiveSelectionSet = parse(
`fragment __generated on ${typeName} { ${keyArgument} }`,
);
visit(
keyDirectiveSelectionSet,
visitWithTypeInfo(typeInfo, {
Field() {
const fieldDef = typeInfo.getFieldDef();
const parentType = typeInfo.getParentType();
if (parentType) {
if (!fieldDef) {
// TODO: find all fields that have @external and suggest them / heursitic match
errors.push(
errorWithCode(
'KEY_FIELDS_MISSING_EXTERNAL',
logServiceAndType(serviceName, parentType.name) +
`A @key directive specifies a field which is not found in this service. Add a field to this type with @external.`,
),
);
return;
}
const externalDirectivesOnField = findDirectivesOnTypeOrField(
fieldDef.astNode,
'external',
);
if (externalDirectivesOnField.length === 0) {
errors.push(
errorWithCode(
'KEY_FIELDS_MISSING_EXTERNAL',
logServiceAndType(serviceName, parentType.name) +
`A @key directive specifies the \`${
fieldDef.name
}\` field which has no matching @external field.`,
),
);
}
}
},
}),
);
}
return errors;
};