OverlappingFieldsCanBeMerged.js.flow 26 KB

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