OverlappingFieldsCanBeMergedRule.js.flow 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833
  1. // @flow strict
  2. import find from '../../polyfills/find';
  3. import objectEntries from '../../polyfills/objectEntries';
  4. import type { ObjMap } from '../../jsutils/ObjMap';
  5. import inspect from '../../jsutils/inspect';
  6. import { GraphQLError } from '../../error/GraphQLError';
  7. import type { ASTVisitor } from '../../language/visitor';
  8. import type {
  9. SelectionSetNode,
  10. FieldNode,
  11. ArgumentNode,
  12. FragmentDefinitionNode,
  13. } from '../../language/ast';
  14. import { Kind } from '../../language/kinds';
  15. import { print } from '../../language/printer';
  16. import type {
  17. GraphQLNamedType,
  18. GraphQLOutputType,
  19. GraphQLCompositeType,
  20. GraphQLField,
  21. } from '../../type/definition';
  22. import {
  23. getNamedType,
  24. isNonNullType,
  25. isLeafType,
  26. isObjectType,
  27. isListType,
  28. isInterfaceType,
  29. } from '../../type/definition';
  30. import { typeFromAST } from '../../utilities/typeFromAST';
  31. import type { ValidationContext } from '../ValidationContext';
  32. function reasonMessage(reason: ConflictReasonMessage): string {
  33. if (Array.isArray(reason)) {
  34. return reason
  35. .map(
  36. ([responseName, subReason]) =>
  37. `subfields "${responseName}" conflict because ` +
  38. reasonMessage(subReason),
  39. )
  40. .join(' and ');
  41. }
  42. return reason;
  43. }
  44. /**
  45. * Overlapping fields can be merged
  46. *
  47. * A selection set is only valid if all fields (including spreading any
  48. * fragments) either correspond to distinct response names or can be merged
  49. * without ambiguity.
  50. */
  51. export function OverlappingFieldsCanBeMergedRule(
  52. context: ValidationContext,
  53. ): ASTVisitor {
  54. // A memoization for when two fragments are compared "between" each other for
  55. // conflicts. Two fragments may be compared many times, so memoizing this can
  56. // dramatically improve the performance of this validator.
  57. const comparedFragmentPairs = new PairSet();
  58. // A cache for the "field map" and list of fragment names found in any given
  59. // selection set. Selection sets may be asked for this information multiple
  60. // times, so this improves the performance of this validator.
  61. const cachedFieldsAndFragmentNames = new Map();
  62. return {
  63. SelectionSet(selectionSet) {
  64. const conflicts = findConflictsWithinSelectionSet(
  65. context,
  66. cachedFieldsAndFragmentNames,
  67. comparedFragmentPairs,
  68. context.getParentType(),
  69. selectionSet,
  70. );
  71. for (const [[responseName, reason], fields1, fields2] of conflicts) {
  72. const reasonMsg = reasonMessage(reason);
  73. context.reportError(
  74. new GraphQLError(
  75. `Fields "${responseName}" conflict because ${reasonMsg}. Use different aliases on the fields to fetch both if this was intentional.`,
  76. fields1.concat(fields2),
  77. ),
  78. );
  79. }
  80. },
  81. };
  82. }
  83. type Conflict = [ConflictReason, Array<FieldNode>, Array<FieldNode>];
  84. // Field name and reason.
  85. type ConflictReason = [string, ConflictReasonMessage];
  86. // Reason is a string, or a nested list of conflicts.
  87. type ConflictReasonMessage = string | Array<ConflictReason>;
  88. // Tuple defining a field node in a context.
  89. type NodeAndDef = [
  90. GraphQLCompositeType,
  91. FieldNode,
  92. ?GraphQLField<mixed, mixed>,
  93. ];
  94. // Map of array of those.
  95. type NodeAndDefCollection = ObjMap<Array<NodeAndDef>>;
  96. /**
  97. * Algorithm:
  98. *
  99. * Conflicts occur when two fields exist in a query which will produce the same
  100. * response name, but represent differing values, thus creating a conflict.
  101. * The algorithm below finds all conflicts via making a series of comparisons
  102. * between fields. In order to compare as few fields as possible, this makes
  103. * a series of comparisons "within" sets of fields and "between" sets of fields.
  104. *
  105. * Given any selection set, a collection produces both a set of fields by
  106. * also including all inline fragments, as well as a list of fragments
  107. * referenced by fragment spreads.
  108. *
  109. * A) Each selection set represented in the document first compares "within" its
  110. * collected set of fields, finding any conflicts between every pair of
  111. * overlapping fields.
  112. * Note: This is the *only time* that a the fields "within" a set are compared
  113. * to each other. After this only fields "between" sets are compared.
  114. *
  115. * B) Also, if any fragment is referenced in a selection set, then a
  116. * comparison is made "between" the original set of fields and the
  117. * referenced fragment.
  118. *
  119. * C) Also, if multiple fragments are referenced, then comparisons
  120. * are made "between" each referenced fragment.
  121. *
  122. * D) When comparing "between" a set of fields and a referenced fragment, first
  123. * a comparison is made between each field in the original set of fields and
  124. * each field in the the referenced set of fields.
  125. *
  126. * E) Also, if any fragment is referenced in the referenced selection set,
  127. * then a comparison is made "between" the original set of fields and the
  128. * referenced fragment (recursively referring to step D).
  129. *
  130. * F) When comparing "between" two fragments, first a comparison is made between
  131. * each field in the first referenced set of fields and each field in the the
  132. * second referenced set of fields.
  133. *
  134. * G) Also, any fragments referenced by the first must be compared to the
  135. * second, and any fragments referenced by the second must be compared to the
  136. * first (recursively referring to step F).
  137. *
  138. * H) When comparing two fields, if both have selection sets, then a comparison
  139. * is made "between" both selection sets, first comparing the set of fields in
  140. * the first selection set with the set of fields in the second.
  141. *
  142. * I) Also, if any fragment is referenced in either selection set, then a
  143. * comparison is made "between" the other set of fields and the
  144. * referenced fragment.
  145. *
  146. * J) Also, if two fragments are referenced in both selection sets, then a
  147. * comparison is made "between" the two fragments.
  148. *
  149. */
  150. // Find all conflicts found "within" a selection set, including those found
  151. // via spreading in fragments. Called when visiting each SelectionSet in the
  152. // GraphQL Document.
  153. function findConflictsWithinSelectionSet(
  154. context: ValidationContext,
  155. cachedFieldsAndFragmentNames,
  156. comparedFragmentPairs: PairSet,
  157. parentType: ?GraphQLNamedType,
  158. selectionSet: SelectionSetNode,
  159. ): Array<Conflict> {
  160. const conflicts = [];
  161. const [fieldMap, fragmentNames] = getFieldsAndFragmentNames(
  162. context,
  163. cachedFieldsAndFragmentNames,
  164. parentType,
  165. selectionSet,
  166. );
  167. // (A) Find find all conflicts "within" the fields of this selection set.
  168. // Note: this is the *only place* `collectConflictsWithin` is called.
  169. collectConflictsWithin(
  170. context,
  171. conflicts,
  172. cachedFieldsAndFragmentNames,
  173. comparedFragmentPairs,
  174. fieldMap,
  175. );
  176. if (fragmentNames.length !== 0) {
  177. // (B) Then collect conflicts between these fields and those represented by
  178. // each spread fragment name found.
  179. for (let i = 0; i < fragmentNames.length; i++) {
  180. collectConflictsBetweenFieldsAndFragment(
  181. context,
  182. conflicts,
  183. cachedFieldsAndFragmentNames,
  184. comparedFragmentPairs,
  185. false,
  186. fieldMap,
  187. fragmentNames[i],
  188. );
  189. // (C) Then compare this fragment with all other fragments found in this
  190. // selection set to collect conflicts between fragments spread together.
  191. // This compares each item in the list of fragment names to every other
  192. // item in that same list (except for itself).
  193. for (let j = i + 1; j < fragmentNames.length; j++) {
  194. collectConflictsBetweenFragments(
  195. context,
  196. conflicts,
  197. cachedFieldsAndFragmentNames,
  198. comparedFragmentPairs,
  199. false,
  200. fragmentNames[i],
  201. fragmentNames[j],
  202. );
  203. }
  204. }
  205. }
  206. return conflicts;
  207. }
  208. // Collect all conflicts found between a set of fields and a fragment reference
  209. // including via spreading in any nested fragments.
  210. function collectConflictsBetweenFieldsAndFragment(
  211. context: ValidationContext,
  212. conflicts: Array<Conflict>,
  213. cachedFieldsAndFragmentNames,
  214. comparedFragmentPairs: PairSet,
  215. areMutuallyExclusive: boolean,
  216. fieldMap: NodeAndDefCollection,
  217. fragmentName: string,
  218. ): void {
  219. const fragment = context.getFragment(fragmentName);
  220. if (!fragment) {
  221. return;
  222. }
  223. const [fieldMap2, fragmentNames2] = getReferencedFieldsAndFragmentNames(
  224. context,
  225. cachedFieldsAndFragmentNames,
  226. fragment,
  227. );
  228. // Do not compare a fragment's fieldMap to itself.
  229. if (fieldMap === fieldMap2) {
  230. return;
  231. }
  232. // (D) First collect any conflicts between the provided collection of fields
  233. // and the collection of fields represented by the given fragment.
  234. collectConflictsBetween(
  235. context,
  236. conflicts,
  237. cachedFieldsAndFragmentNames,
  238. comparedFragmentPairs,
  239. areMutuallyExclusive,
  240. fieldMap,
  241. fieldMap2,
  242. );
  243. // (E) Then collect any conflicts between the provided collection of fields
  244. // and any fragment names found in the given fragment.
  245. for (let i = 0; i < fragmentNames2.length; i++) {
  246. collectConflictsBetweenFieldsAndFragment(
  247. context,
  248. conflicts,
  249. cachedFieldsAndFragmentNames,
  250. comparedFragmentPairs,
  251. areMutuallyExclusive,
  252. fieldMap,
  253. fragmentNames2[i],
  254. );
  255. }
  256. }
  257. // Collect all conflicts found between two fragments, including via spreading in
  258. // any nested fragments.
  259. function collectConflictsBetweenFragments(
  260. context: ValidationContext,
  261. conflicts: Array<Conflict>,
  262. cachedFieldsAndFragmentNames,
  263. comparedFragmentPairs: PairSet,
  264. areMutuallyExclusive: boolean,
  265. fragmentName1: string,
  266. fragmentName2: string,
  267. ): void {
  268. // No need to compare a fragment to itself.
  269. if (fragmentName1 === fragmentName2) {
  270. return;
  271. }
  272. // Memoize so two fragments are not compared for conflicts more than once.
  273. if (
  274. comparedFragmentPairs.has(
  275. fragmentName1,
  276. fragmentName2,
  277. areMutuallyExclusive,
  278. )
  279. ) {
  280. return;
  281. }
  282. comparedFragmentPairs.add(fragmentName1, fragmentName2, areMutuallyExclusive);
  283. const fragment1 = context.getFragment(fragmentName1);
  284. const fragment2 = context.getFragment(fragmentName2);
  285. if (!fragment1 || !fragment2) {
  286. return;
  287. }
  288. const [fieldMap1, fragmentNames1] = getReferencedFieldsAndFragmentNames(
  289. context,
  290. cachedFieldsAndFragmentNames,
  291. fragment1,
  292. );
  293. const [fieldMap2, fragmentNames2] = getReferencedFieldsAndFragmentNames(
  294. context,
  295. cachedFieldsAndFragmentNames,
  296. fragment2,
  297. );
  298. // (F) First, collect all conflicts between these two collections of fields
  299. // (not including any nested fragments).
  300. collectConflictsBetween(
  301. context,
  302. conflicts,
  303. cachedFieldsAndFragmentNames,
  304. comparedFragmentPairs,
  305. areMutuallyExclusive,
  306. fieldMap1,
  307. fieldMap2,
  308. );
  309. // (G) Then collect conflicts between the first fragment and any nested
  310. // fragments spread in the second fragment.
  311. for (let j = 0; j < fragmentNames2.length; j++) {
  312. collectConflictsBetweenFragments(
  313. context,
  314. conflicts,
  315. cachedFieldsAndFragmentNames,
  316. comparedFragmentPairs,
  317. areMutuallyExclusive,
  318. fragmentName1,
  319. fragmentNames2[j],
  320. );
  321. }
  322. // (G) Then collect conflicts between the second fragment and any nested
  323. // fragments spread in the first fragment.
  324. for (let i = 0; i < fragmentNames1.length; i++) {
  325. collectConflictsBetweenFragments(
  326. context,
  327. conflicts,
  328. cachedFieldsAndFragmentNames,
  329. comparedFragmentPairs,
  330. areMutuallyExclusive,
  331. fragmentNames1[i],
  332. fragmentName2,
  333. );
  334. }
  335. }
  336. // Find all conflicts found between two selection sets, including those found
  337. // via spreading in fragments. Called when determining if conflicts exist
  338. // between the sub-fields of two overlapping fields.
  339. function findConflictsBetweenSubSelectionSets(
  340. context: ValidationContext,
  341. cachedFieldsAndFragmentNames,
  342. comparedFragmentPairs: PairSet,
  343. areMutuallyExclusive: boolean,
  344. parentType1: ?GraphQLNamedType,
  345. selectionSet1: SelectionSetNode,
  346. parentType2: ?GraphQLNamedType,
  347. selectionSet2: SelectionSetNode,
  348. ): Array<Conflict> {
  349. const conflicts = [];
  350. const [fieldMap1, fragmentNames1] = getFieldsAndFragmentNames(
  351. context,
  352. cachedFieldsAndFragmentNames,
  353. parentType1,
  354. selectionSet1,
  355. );
  356. const [fieldMap2, fragmentNames2] = getFieldsAndFragmentNames(
  357. context,
  358. cachedFieldsAndFragmentNames,
  359. parentType2,
  360. selectionSet2,
  361. );
  362. // (H) First, collect all conflicts between these two collections of field.
  363. collectConflictsBetween(
  364. context,
  365. conflicts,
  366. cachedFieldsAndFragmentNames,
  367. comparedFragmentPairs,
  368. areMutuallyExclusive,
  369. fieldMap1,
  370. fieldMap2,
  371. );
  372. // (I) Then collect conflicts between the first collection of fields and
  373. // those referenced by each fragment name associated with the second.
  374. if (fragmentNames2.length !== 0) {
  375. for (let j = 0; j < fragmentNames2.length; j++) {
  376. collectConflictsBetweenFieldsAndFragment(
  377. context,
  378. conflicts,
  379. cachedFieldsAndFragmentNames,
  380. comparedFragmentPairs,
  381. areMutuallyExclusive,
  382. fieldMap1,
  383. fragmentNames2[j],
  384. );
  385. }
  386. }
  387. // (I) Then collect conflicts between the second collection of fields and
  388. // those referenced by each fragment name associated with the first.
  389. if (fragmentNames1.length !== 0) {
  390. for (let i = 0; i < fragmentNames1.length; i++) {
  391. collectConflictsBetweenFieldsAndFragment(
  392. context,
  393. conflicts,
  394. cachedFieldsAndFragmentNames,
  395. comparedFragmentPairs,
  396. areMutuallyExclusive,
  397. fieldMap2,
  398. fragmentNames1[i],
  399. );
  400. }
  401. }
  402. // (J) Also collect conflicts between any fragment names by the first and
  403. // fragment names by the second. This compares each item in the first set of
  404. // names to each item in the second set of names.
  405. for (let i = 0; i < fragmentNames1.length; i++) {
  406. for (let j = 0; j < fragmentNames2.length; j++) {
  407. collectConflictsBetweenFragments(
  408. context,
  409. conflicts,
  410. cachedFieldsAndFragmentNames,
  411. comparedFragmentPairs,
  412. areMutuallyExclusive,
  413. fragmentNames1[i],
  414. fragmentNames2[j],
  415. );
  416. }
  417. }
  418. return conflicts;
  419. }
  420. // Collect all Conflicts "within" one collection of fields.
  421. function collectConflictsWithin(
  422. context: ValidationContext,
  423. conflicts: Array<Conflict>,
  424. cachedFieldsAndFragmentNames,
  425. comparedFragmentPairs: PairSet,
  426. fieldMap: NodeAndDefCollection,
  427. ): void {
  428. // A field map is a keyed collection, where each key represents a response
  429. // name and the value at that key is a list of all fields which provide that
  430. // response name. For every response name, if there are multiple fields, they
  431. // must be compared to find a potential conflict.
  432. for (const [responseName, fields] of objectEntries(fieldMap)) {
  433. // This compares every field in the list to every other field in this list
  434. // (except to itself). If the list only has one item, nothing needs to
  435. // be compared.
  436. if (fields.length > 1) {
  437. for (let i = 0; i < fields.length; i++) {
  438. for (let j = i + 1; j < fields.length; j++) {
  439. const conflict = findConflict(
  440. context,
  441. cachedFieldsAndFragmentNames,
  442. comparedFragmentPairs,
  443. false, // within one collection is never mutually exclusive
  444. responseName,
  445. fields[i],
  446. fields[j],
  447. );
  448. if (conflict) {
  449. conflicts.push(conflict);
  450. }
  451. }
  452. }
  453. }
  454. }
  455. }
  456. // Collect all Conflicts between two collections of fields. This is similar to,
  457. // but different from the `collectConflictsWithin` function above. This check
  458. // assumes that `collectConflictsWithin` has already been called on each
  459. // provided collection of fields. This is true because this validator traverses
  460. // each individual selection set.
  461. function collectConflictsBetween(
  462. context: ValidationContext,
  463. conflicts: Array<Conflict>,
  464. cachedFieldsAndFragmentNames,
  465. comparedFragmentPairs: PairSet,
  466. parentFieldsAreMutuallyExclusive: boolean,
  467. fieldMap1: NodeAndDefCollection,
  468. fieldMap2: NodeAndDefCollection,
  469. ): void {
  470. // A field map is a keyed collection, where each key represents a response
  471. // name and the value at that key is a list of all fields which provide that
  472. // response name. For any response name which appears in both provided field
  473. // maps, each field from the first field map must be compared to every field
  474. // in the second field map to find potential conflicts.
  475. for (const responseName of Object.keys(fieldMap1)) {
  476. const fields2 = fieldMap2[responseName];
  477. if (fields2) {
  478. const fields1 = fieldMap1[responseName];
  479. for (let i = 0; i < fields1.length; i++) {
  480. for (let j = 0; j < fields2.length; j++) {
  481. const conflict = findConflict(
  482. context,
  483. cachedFieldsAndFragmentNames,
  484. comparedFragmentPairs,
  485. parentFieldsAreMutuallyExclusive,
  486. responseName,
  487. fields1[i],
  488. fields2[j],
  489. );
  490. if (conflict) {
  491. conflicts.push(conflict);
  492. }
  493. }
  494. }
  495. }
  496. }
  497. }
  498. // Determines if there is a conflict between two particular fields, including
  499. // comparing their sub-fields.
  500. function findConflict(
  501. context: ValidationContext,
  502. cachedFieldsAndFragmentNames,
  503. comparedFragmentPairs: PairSet,
  504. parentFieldsAreMutuallyExclusive: boolean,
  505. responseName: string,
  506. field1: NodeAndDef,
  507. field2: NodeAndDef,
  508. ): ?Conflict {
  509. const [parentType1, node1, def1] = field1;
  510. const [parentType2, node2, def2] = field2;
  511. // If it is known that two fields could not possibly apply at the same
  512. // time, due to the parent types, then it is safe to permit them to diverge
  513. // in aliased field or arguments used as they will not present any ambiguity
  514. // by differing.
  515. // It is known that two parent types could never overlap if they are
  516. // different Object types. Interface or Union types might overlap - if not
  517. // in the current state of the schema, then perhaps in some future version,
  518. // thus may not safely diverge.
  519. const areMutuallyExclusive =
  520. parentFieldsAreMutuallyExclusive ||
  521. (parentType1 !== parentType2 &&
  522. isObjectType(parentType1) &&
  523. isObjectType(parentType2));
  524. if (!areMutuallyExclusive) {
  525. // Two aliases must refer to the same field.
  526. const name1 = node1.name.value;
  527. const name2 = node2.name.value;
  528. if (name1 !== name2) {
  529. return [
  530. [responseName, `"${name1}" and "${name2}" are different fields`],
  531. [node1],
  532. [node2],
  533. ];
  534. }
  535. // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
  536. const args1 = node1.arguments ?? [];
  537. // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
  538. const args2 = node2.arguments ?? [];
  539. // Two field calls must have the same arguments.
  540. if (!sameArguments(args1, args2)) {
  541. return [
  542. [responseName, 'they have differing arguments'],
  543. [node1],
  544. [node2],
  545. ];
  546. }
  547. }
  548. // The return type for each field.
  549. const type1 = def1?.type;
  550. const type2 = def2?.type;
  551. if (type1 && type2 && doTypesConflict(type1, type2)) {
  552. return [
  553. [
  554. responseName,
  555. `they return conflicting types "${inspect(type1)}" and "${inspect(
  556. type2,
  557. )}"`,
  558. ],
  559. [node1],
  560. [node2],
  561. ];
  562. }
  563. // Collect and compare sub-fields. Use the same "visited fragment names" list
  564. // for both collections so fields in a fragment reference are never
  565. // compared to themselves.
  566. const selectionSet1 = node1.selectionSet;
  567. const selectionSet2 = node2.selectionSet;
  568. if (selectionSet1 && selectionSet2) {
  569. const conflicts = findConflictsBetweenSubSelectionSets(
  570. context,
  571. cachedFieldsAndFragmentNames,
  572. comparedFragmentPairs,
  573. areMutuallyExclusive,
  574. getNamedType(type1),
  575. selectionSet1,
  576. getNamedType(type2),
  577. selectionSet2,
  578. );
  579. return subfieldConflicts(conflicts, responseName, node1, node2);
  580. }
  581. }
  582. function sameArguments(
  583. arguments1: $ReadOnlyArray<ArgumentNode>,
  584. arguments2: $ReadOnlyArray<ArgumentNode>,
  585. ): boolean {
  586. if (arguments1.length !== arguments2.length) {
  587. return false;
  588. }
  589. return arguments1.every((argument1) => {
  590. const argument2 = find(
  591. arguments2,
  592. (argument) => argument.name.value === argument1.name.value,
  593. );
  594. if (!argument2) {
  595. return false;
  596. }
  597. return sameValue(argument1.value, argument2.value);
  598. });
  599. }
  600. function sameValue(value1, value2) {
  601. return print(value1) === print(value2);
  602. }
  603. // Two types conflict if both types could not apply to a value simultaneously.
  604. // Composite types are ignored as their individual field types will be compared
  605. // later recursively. However List and Non-Null types must match.
  606. function doTypesConflict(
  607. type1: GraphQLOutputType,
  608. type2: GraphQLOutputType,
  609. ): boolean {
  610. if (isListType(type1)) {
  611. return isListType(type2)
  612. ? doTypesConflict(type1.ofType, type2.ofType)
  613. : true;
  614. }
  615. if (isListType(type2)) {
  616. return true;
  617. }
  618. if (isNonNullType(type1)) {
  619. return isNonNullType(type2)
  620. ? doTypesConflict(type1.ofType, type2.ofType)
  621. : true;
  622. }
  623. if (isNonNullType(type2)) {
  624. return true;
  625. }
  626. if (isLeafType(type1) || isLeafType(type2)) {
  627. return type1 !== type2;
  628. }
  629. return false;
  630. }
  631. // Given a selection set, return the collection of fields (a mapping of response
  632. // name to field nodes and definitions) as well as a list of fragment names
  633. // referenced via fragment spreads.
  634. function getFieldsAndFragmentNames(
  635. context: ValidationContext,
  636. cachedFieldsAndFragmentNames,
  637. parentType: ?GraphQLNamedType,
  638. selectionSet: SelectionSetNode,
  639. ): [NodeAndDefCollection, Array<string>] {
  640. let cached = cachedFieldsAndFragmentNames.get(selectionSet);
  641. if (!cached) {
  642. const nodeAndDefs = Object.create(null);
  643. const fragmentNames = Object.create(null);
  644. _collectFieldsAndFragmentNames(
  645. context,
  646. parentType,
  647. selectionSet,
  648. nodeAndDefs,
  649. fragmentNames,
  650. );
  651. cached = [nodeAndDefs, Object.keys(fragmentNames)];
  652. cachedFieldsAndFragmentNames.set(selectionSet, cached);
  653. }
  654. return cached;
  655. }
  656. // Given a reference to a fragment, return the represented collection of fields
  657. // as well as a list of nested fragment names referenced via fragment spreads.
  658. function getReferencedFieldsAndFragmentNames(
  659. context: ValidationContext,
  660. cachedFieldsAndFragmentNames,
  661. fragment: FragmentDefinitionNode,
  662. ) {
  663. // Short-circuit building a type from the node if possible.
  664. const cached = cachedFieldsAndFragmentNames.get(fragment.selectionSet);
  665. if (cached) {
  666. return cached;
  667. }
  668. const fragmentType = typeFromAST(context.getSchema(), fragment.typeCondition);
  669. return getFieldsAndFragmentNames(
  670. context,
  671. cachedFieldsAndFragmentNames,
  672. fragmentType,
  673. fragment.selectionSet,
  674. );
  675. }
  676. function _collectFieldsAndFragmentNames(
  677. context: ValidationContext,
  678. parentType: ?GraphQLNamedType,
  679. selectionSet: SelectionSetNode,
  680. nodeAndDefs,
  681. fragmentNames,
  682. ): void {
  683. for (const selection of selectionSet.selections) {
  684. switch (selection.kind) {
  685. case Kind.FIELD: {
  686. const fieldName = selection.name.value;
  687. let fieldDef;
  688. if (isObjectType(parentType) || isInterfaceType(parentType)) {
  689. fieldDef = parentType.getFields()[fieldName];
  690. }
  691. const responseName = selection.alias
  692. ? selection.alias.value
  693. : fieldName;
  694. if (!nodeAndDefs[responseName]) {
  695. nodeAndDefs[responseName] = [];
  696. }
  697. nodeAndDefs[responseName].push([parentType, selection, fieldDef]);
  698. break;
  699. }
  700. case Kind.FRAGMENT_SPREAD:
  701. fragmentNames[selection.name.value] = true;
  702. break;
  703. case Kind.INLINE_FRAGMENT: {
  704. const typeCondition = selection.typeCondition;
  705. const inlineFragmentType = typeCondition
  706. ? typeFromAST(context.getSchema(), typeCondition)
  707. : parentType;
  708. _collectFieldsAndFragmentNames(
  709. context,
  710. inlineFragmentType,
  711. selection.selectionSet,
  712. nodeAndDefs,
  713. fragmentNames,
  714. );
  715. break;
  716. }
  717. }
  718. }
  719. }
  720. // Given a series of Conflicts which occurred between two sub-fields, generate
  721. // a single Conflict.
  722. function subfieldConflicts(
  723. conflicts: $ReadOnlyArray<Conflict>,
  724. responseName: string,
  725. node1: FieldNode,
  726. node2: FieldNode,
  727. ): ?Conflict {
  728. if (conflicts.length > 0) {
  729. return [
  730. [responseName, conflicts.map(([reason]) => reason)],
  731. conflicts.reduce((allFields, [, fields1]) => allFields.concat(fields1), [
  732. node1,
  733. ]),
  734. conflicts.reduce(
  735. (allFields, [, , fields2]) => allFields.concat(fields2),
  736. [node2],
  737. ),
  738. ];
  739. }
  740. }
  741. /**
  742. * A way to keep track of pairs of things when the ordering of the pair does
  743. * not matter. We do this by maintaining a sort of double adjacency sets.
  744. */
  745. class PairSet {
  746. _data: ObjMap<ObjMap<boolean>>;
  747. constructor(): void {
  748. this._data = Object.create(null);
  749. }
  750. has(a: string, b: string, areMutuallyExclusive: boolean) {
  751. const first = this._data[a];
  752. const result = first && first[b];
  753. if (result === undefined) {
  754. return false;
  755. }
  756. // areMutuallyExclusive being false is a superset of being true,
  757. // hence if we want to know if this PairSet "has" these two with no
  758. // exclusivity, we have to ensure it was added as such.
  759. if (areMutuallyExclusive === false) {
  760. return result === false;
  761. }
  762. return true;
  763. }
  764. add(a: string, b: string, areMutuallyExclusive: boolean) {
  765. _pairSetAdd(this._data, a, b, areMutuallyExclusive);
  766. _pairSetAdd(this._data, b, a, areMutuallyExclusive);
  767. }
  768. }
  769. function _pairSetAdd(data, a, b, areMutuallyExclusive) {
  770. let map = data[a];
  771. if (!map) {
  772. map = Object.create(null);
  773. data[a] = map;
  774. }
  775. map[b] = areMutuallyExclusive;
  776. }