UniqueFieldDefinitionNamesRule.js.flow 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. // @flow strict
  2. import { GraphQLError } from '../../error/GraphQLError';
  3. import type { ASTVisitor } from '../../language/visitor';
  4. import type {
  5. NameNode,
  6. FieldDefinitionNode,
  7. InputValueDefinitionNode,
  8. } from '../../language/ast';
  9. import type { GraphQLNamedType } from '../../type/definition';
  10. import {
  11. isObjectType,
  12. isInterfaceType,
  13. isInputObjectType,
  14. } from '../../type/definition';
  15. import type { SDLValidationContext } from '../ValidationContext';
  16. /**
  17. * Unique field definition names
  18. *
  19. * A GraphQL complex type is only valid if all its fields are uniquely named.
  20. */
  21. export function UniqueFieldDefinitionNamesRule(
  22. context: SDLValidationContext,
  23. ): ASTVisitor {
  24. const schema = context.getSchema();
  25. const existingTypeMap = schema ? schema.getTypeMap() : Object.create(null);
  26. const knownFieldNames = Object.create(null);
  27. return {
  28. InputObjectTypeDefinition: checkFieldUniqueness,
  29. InputObjectTypeExtension: checkFieldUniqueness,
  30. InterfaceTypeDefinition: checkFieldUniqueness,
  31. InterfaceTypeExtension: checkFieldUniqueness,
  32. ObjectTypeDefinition: checkFieldUniqueness,
  33. ObjectTypeExtension: checkFieldUniqueness,
  34. };
  35. function checkFieldUniqueness(node: {
  36. +name: NameNode,
  37. +fields?: $ReadOnlyArray<InputValueDefinitionNode | FieldDefinitionNode>,
  38. ...
  39. }) {
  40. const typeName = node.name.value;
  41. if (!knownFieldNames[typeName]) {
  42. knownFieldNames[typeName] = Object.create(null);
  43. }
  44. // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
  45. const fieldNodes = node.fields ?? [];
  46. const fieldNames = knownFieldNames[typeName];
  47. for (const fieldDef of fieldNodes) {
  48. const fieldName = fieldDef.name.value;
  49. if (hasField(existingTypeMap[typeName], fieldName)) {
  50. context.reportError(
  51. new GraphQLError(
  52. `Field "${typeName}.${fieldName}" already exists in the schema. It cannot also be defined in this type extension.`,
  53. fieldDef.name,
  54. ),
  55. );
  56. } else if (fieldNames[fieldName]) {
  57. context.reportError(
  58. new GraphQLError(
  59. `Field "${typeName}.${fieldName}" can only be defined once.`,
  60. [fieldNames[fieldName], fieldDef.name],
  61. ),
  62. );
  63. } else {
  64. fieldNames[fieldName] = fieldDef.name;
  65. }
  66. }
  67. return false;
  68. }
  69. }
  70. function hasField(type: GraphQLNamedType, fieldName: string): boolean {
  71. if (isObjectType(type) || isInterfaceType(type) || isInputObjectType(type)) {
  72. return type.getFields()[fieldName] != null;
  73. }
  74. return false;
  75. }