SingleFieldSubscriptionsRule.mjs 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. import { GraphQLError } from '../../error/GraphQLError.mjs';
  2. import { Kind } from '../../language/kinds.mjs';
  3. import { collectFields } from '../../execution/collectFields.mjs';
  4. /**
  5. * Subscriptions must only include a non-introspection field.
  6. *
  7. * A GraphQL subscription is valid only if it contains a single root field and
  8. * that root field is not an introspection field.
  9. *
  10. * See https://spec.graphql.org/draft/#sec-Single-root-field
  11. */
  12. export function SingleFieldSubscriptionsRule(context) {
  13. return {
  14. OperationDefinition(node) {
  15. if (node.operation === 'subscription') {
  16. const schema = context.getSchema();
  17. const subscriptionType = schema.getSubscriptionType();
  18. if (subscriptionType) {
  19. const operationName = node.name ? node.name.value : null;
  20. const variableValues = Object.create(null);
  21. const document = context.getDocument();
  22. const fragments = Object.create(null);
  23. for (const definition of document.definitions) {
  24. if (definition.kind === Kind.FRAGMENT_DEFINITION) {
  25. fragments[definition.name.value] = definition;
  26. }
  27. }
  28. const fields = collectFields(
  29. schema,
  30. fragments,
  31. variableValues,
  32. subscriptionType,
  33. node.selectionSet,
  34. );
  35. if (fields.size > 1) {
  36. const fieldSelectionLists = [...fields.values()];
  37. const extraFieldSelectionLists = fieldSelectionLists.slice(1);
  38. const extraFieldSelections = extraFieldSelectionLists.flat();
  39. context.reportError(
  40. new GraphQLError(
  41. operationName != null
  42. ? `Subscription "${operationName}" must select only one top level field.`
  43. : 'Anonymous Subscription must select only one top level field.',
  44. {
  45. nodes: extraFieldSelections,
  46. },
  47. ),
  48. );
  49. }
  50. for (const fieldNodes of fields.values()) {
  51. const field = fieldNodes[0];
  52. const fieldName = field.name.value;
  53. if (fieldName.startsWith('__')) {
  54. context.reportError(
  55. new GraphQLError(
  56. operationName != null
  57. ? `Subscription "${operationName}" must not select an introspection top level field.`
  58. : 'Anonymous Subscription must not select an introspection top level field.',
  59. {
  60. nodes: fieldNodes,
  61. },
  62. ),
  63. );
  64. }
  65. }
  66. }
  67. }
  68. },
  69. };
  70. }