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