uniqueDirectivesPerLocationRule function
Visitor
uniqueDirectivesPerLocationRule(
- SDLValidationCtx context
Unique directive names per location
A GraphQL document is only valid if all non-repeatable directives at a given location are uniquely named.
See https://spec.graphql.org/draft/#sec-Directives-Are-Unique-Per-Location
Implementation
Visitor uniqueDirectivesPerLocationRule(
SDLValidationCtx context,
) {
final visitor = TypedVisitor();
final uniqueDirectiveMap = <String, bool>{};
final schema = context.schema;
final definedDirectives =
schema != null ? schema.directives : GraphQLDirective.specifiedDirectives;
for (final directive in definedDirectives) {
uniqueDirectiveMap[directive.name] = !directive.isRepeatable;
}
final astDefinitions =
context.document.definitions.whereType<DirectiveDefinitionNode>();
for (final def in astDefinitions) {
uniqueDirectiveMap[def.name.value] = !def.repeatable;
}
final schemaDirectives = <String, DirectiveNode>{};
final typeDirectivesMap = <String, Map<String, DirectiveNode>>{};
visitor.add<Node>(
// Many different AST nodes may contain directives. Rather than listing
// them all, just listen for entering any node, and check to see if it
// defines any directives.
(node) {
final List<DirectiveNode>? directives = node.directivesNodes;
if (directives == null) {
return;
}
Map<String, DirectiveNode>? seenDirectives;
if (node is SchemaDefinitionNode || node is SchemaExtensionNode) {
seenDirectives = schemaDirectives;
} else if (node is TypeDefinitionNode || node is TypeExtensionNode) {
final typeName = (node is TypeDefinitionNode
? node.name
: (node as TypeExtensionNode).name)
.value;
seenDirectives = typeDirectivesMap[typeName];
if (seenDirectives == null) {
typeDirectivesMap[typeName] = seenDirectives = {};
}
} else {
seenDirectives = {};
}
for (final directive in directives) {
final directiveName = directive.name.value;
if (uniqueDirectiveMap[directiveName] == true) {
if (seenDirectives[directiveName] != null) {
context.reportError(
GraphQLError(
'The directive "@${directiveName}" can only be used once at this location.',
locations: List.of(
[
seenDirectives[directiveName],
seenDirectives[directiveName]?.name,
directive,
directive.name
]
.map(
(e) => e?.span?.start == null
? null
: GraphQLErrorLocation.fromSourceLocation(
e!.span!.start),
)
.whereType(),
),
extensions: _uniqueDirectivesPerLocationSpec.extensions(),
),
);
} else {
seenDirectives[directiveName] = directive;
}
}
}
});
return visitor;
}