/** @category Validation Rules */
import { GraphQLError } from '../../error/GraphQLError.ts';
import type { DirectiveNode } from '../../language/ast.ts';
import { Kind } from '../../language/kinds.ts';
import type { ASTVisitor } from '../../language/visitor.ts';
import {
GraphQLDeferDirective,
GraphQLStreamDirective,
} from '../../type/directives.ts';
import type { ValidationContext } from '../ValidationContext.ts';
/**
* Defer and stream directive labels are unique
*
* A GraphQL document is only valid if defer and stream directives' label argument is static and unique.
* @param context - The validation context used while checking the document.
* @returns A visitor that reports validation errors for this rule.
* @example
* ```ts
* import { parse } from 'graphql/language';
* import { buildSchema } from 'graphql/utilities';
* import { validate, DeferStreamDirectiveLabelRule } from 'graphql/validation';
*
* const schema = buildSchema(`
* type Query {
* friends: [String]
* }
* `);
* const invalidDocument = parse(`
* {
* friends @stream(label: "friends")
* other: friends @stream(label: "friends")
* }
* `);
* const validDocument = parse(`
* {
* friends @stream(label: "friends")
* other: friends @stream(label: "otherFriends")
* }
* `);
*
* validate(schema, invalidDocument, [DeferStreamDirectiveLabelRule]).length; // => 1
* validate(schema, validDocument, [DeferStreamDirectiveLabelRule]); // => []
* ```
*/
export function DeferStreamDirectiveLabelRule(
context: ValidationContext,
): ASTVisitor {
const knownLabels = new Map<string, DirectiveNode>();
return {
Directive(node) {
if (
node.name.value === GraphQLDeferDirective.name ||
node.name.value === GraphQLStreamDirective.name
) {
const labelArgument = node.arguments?.find(
(arg) => arg.name.value === 'label',
);
const labelValue = labelArgument?.value;
if (!labelValue || labelValue.kind === Kind.NULL) {
return;
}
if (labelValue.kind !== Kind.STRING) {
context.reportError(
new GraphQLError(
`Argument "@${node.name.value}(label:)" must be a static string.`,
{ nodes: node },
),
);
return;
}
const knownLabel = knownLabels.get(labelValue.value);
if (knownLabel != null) {
context.reportError(
new GraphQLError(
'Value for arguments "defer(label:)" and "stream(label:)" must be unique across all Defer/Stream directive usages.',
{ nodes: [knownLabel, node] },
),
);
} else {
knownLabels.set(labelValue.value, node);
}
}
},
};
}