visitResult.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import { getOperationASTFromRequest } from './getOperationASTFromRequest.js';
  2. import { Kind, isListType, getNullableType, isAbstractType, isObjectType, TypeNameMetaFieldDef, SchemaMetaFieldDef, } from 'graphql';
  3. import { collectFields, collectSubFields } from './collectFields.js';
  4. export function visitData(data, enter, leave) {
  5. if (Array.isArray(data)) {
  6. return data.map(value => visitData(value, enter, leave));
  7. }
  8. else if (typeof data === 'object') {
  9. const newData = enter != null ? enter(data) : data;
  10. if (newData != null) {
  11. for (const key in newData) {
  12. const value = newData[key];
  13. Object.defineProperty(newData, key, {
  14. value: visitData(value, enter, leave),
  15. });
  16. }
  17. }
  18. return leave != null ? leave(newData) : newData;
  19. }
  20. return data;
  21. }
  22. export function visitErrors(errors, visitor) {
  23. return errors.map(error => visitor(error));
  24. }
  25. export function visitResult(result, request, schema, resultVisitorMap, errorVisitorMap) {
  26. const fragments = request.document.definitions.reduce((acc, def) => {
  27. if (def.kind === Kind.FRAGMENT_DEFINITION) {
  28. acc[def.name.value] = def;
  29. }
  30. return acc;
  31. }, {});
  32. const variableValues = request.variables || {};
  33. const errorInfo = {
  34. segmentInfoMap: new Map(),
  35. unpathedErrors: new Set(),
  36. };
  37. const data = result.data;
  38. const errors = result.errors;
  39. const visitingErrors = errors != null && errorVisitorMap != null;
  40. const operationDocumentNode = getOperationASTFromRequest(request);
  41. if (data != null && operationDocumentNode != null) {
  42. result.data = visitRoot(data, operationDocumentNode, schema, fragments, variableValues, resultVisitorMap, visitingErrors ? errors : undefined, errorInfo);
  43. }
  44. if (errors != null && errorVisitorMap) {
  45. result.errors = visitErrorsByType(errors, errorVisitorMap, errorInfo);
  46. }
  47. return result;
  48. }
  49. function visitErrorsByType(errors, errorVisitorMap, errorInfo) {
  50. const segmentInfoMap = errorInfo.segmentInfoMap;
  51. const unpathedErrors = errorInfo.unpathedErrors;
  52. const unpathedErrorVisitor = errorVisitorMap['__unpathed'];
  53. return errors.map(originalError => {
  54. const pathSegmentsInfo = segmentInfoMap.get(originalError);
  55. const newError = pathSegmentsInfo == null
  56. ? originalError
  57. : pathSegmentsInfo.reduceRight((acc, segmentInfo) => {
  58. const typeName = segmentInfo.type.name;
  59. const typeVisitorMap = errorVisitorMap[typeName];
  60. if (typeVisitorMap == null) {
  61. return acc;
  62. }
  63. const errorVisitor = typeVisitorMap[segmentInfo.fieldName];
  64. return errorVisitor == null ? acc : errorVisitor(acc, segmentInfo.pathIndex);
  65. }, originalError);
  66. if (unpathedErrorVisitor && unpathedErrors.has(originalError)) {
  67. return unpathedErrorVisitor(newError);
  68. }
  69. return newError;
  70. });
  71. }
  72. function getOperationRootType(schema, operationDef) {
  73. switch (operationDef.operation) {
  74. case 'query':
  75. return schema.getQueryType();
  76. case 'mutation':
  77. return schema.getMutationType();
  78. case 'subscription':
  79. return schema.getSubscriptionType();
  80. }
  81. }
  82. function visitRoot(root, operation, schema, fragments, variableValues, resultVisitorMap, errors, errorInfo) {
  83. const operationRootType = getOperationRootType(schema, operation);
  84. const collectedFields = collectFields(schema, fragments, variableValues, operationRootType, operation.selectionSet, new Map(), new Set());
  85. return visitObjectValue(root, operationRootType, collectedFields, schema, fragments, variableValues, resultVisitorMap, 0, errors, errorInfo);
  86. }
  87. function visitObjectValue(object, type, fieldNodeMap, schema, fragments, variableValues, resultVisitorMap, pathIndex, errors, errorInfo) {
  88. var _a;
  89. const fieldMap = type.getFields();
  90. const typeVisitorMap = resultVisitorMap === null || resultVisitorMap === void 0 ? void 0 : resultVisitorMap[type.name];
  91. const enterObject = typeVisitorMap === null || typeVisitorMap === void 0 ? void 0 : typeVisitorMap.__enter;
  92. const newObject = enterObject != null ? enterObject(object) : object;
  93. let sortedErrors;
  94. let errorMap = null;
  95. if (errors != null) {
  96. sortedErrors = sortErrorsByPathSegment(errors, pathIndex);
  97. errorMap = sortedErrors.errorMap;
  98. for (const error of sortedErrors.unpathedErrors) {
  99. errorInfo.unpathedErrors.add(error);
  100. }
  101. }
  102. for (const [responseKey, subFieldNodes] of fieldNodeMap) {
  103. const fieldName = subFieldNodes[0].name.value;
  104. let fieldType = (_a = fieldMap[fieldName]) === null || _a === void 0 ? void 0 : _a.type;
  105. if (fieldType == null) {
  106. switch (fieldName) {
  107. case '__typename':
  108. fieldType = TypeNameMetaFieldDef.type;
  109. break;
  110. case '__schema':
  111. fieldType = SchemaMetaFieldDef.type;
  112. break;
  113. }
  114. }
  115. const newPathIndex = pathIndex + 1;
  116. let fieldErrors;
  117. if (errorMap) {
  118. fieldErrors = errorMap[responseKey];
  119. if (fieldErrors != null) {
  120. delete errorMap[responseKey];
  121. }
  122. addPathSegmentInfo(type, fieldName, newPathIndex, fieldErrors, errorInfo);
  123. }
  124. const newValue = visitFieldValue(object[responseKey], fieldType, subFieldNodes, schema, fragments, variableValues, resultVisitorMap, newPathIndex, fieldErrors, errorInfo);
  125. updateObject(newObject, responseKey, newValue, typeVisitorMap, fieldName);
  126. }
  127. const oldTypename = newObject.__typename;
  128. if (oldTypename != null) {
  129. updateObject(newObject, '__typename', oldTypename, typeVisitorMap, '__typename');
  130. }
  131. if (errorMap) {
  132. for (const errorsKey in errorMap) {
  133. const errors = errorMap[errorsKey];
  134. for (const error of errors) {
  135. errorInfo.unpathedErrors.add(error);
  136. }
  137. }
  138. }
  139. const leaveObject = typeVisitorMap === null || typeVisitorMap === void 0 ? void 0 : typeVisitorMap.__leave;
  140. return leaveObject != null ? leaveObject(newObject) : newObject;
  141. }
  142. function updateObject(object, responseKey, newValue, typeVisitorMap, fieldName) {
  143. if (typeVisitorMap == null) {
  144. object[responseKey] = newValue;
  145. return;
  146. }
  147. const fieldVisitor = typeVisitorMap[fieldName];
  148. if (fieldVisitor == null) {
  149. object[responseKey] = newValue;
  150. return;
  151. }
  152. const visitedValue = fieldVisitor(newValue);
  153. if (visitedValue === undefined) {
  154. delete object[responseKey];
  155. return;
  156. }
  157. object[responseKey] = visitedValue;
  158. }
  159. function visitListValue(list, returnType, fieldNodes, schema, fragments, variableValues, resultVisitorMap, pathIndex, errors, errorInfo) {
  160. return list.map(listMember => visitFieldValue(listMember, returnType, fieldNodes, schema, fragments, variableValues, resultVisitorMap, pathIndex + 1, errors, errorInfo));
  161. }
  162. function visitFieldValue(value, returnType, fieldNodes, schema, fragments, variableValues, resultVisitorMap, pathIndex, errors = [], errorInfo) {
  163. if (value == null) {
  164. return value;
  165. }
  166. const nullableType = getNullableType(returnType);
  167. if (isListType(nullableType)) {
  168. return visitListValue(value, nullableType.ofType, fieldNodes, schema, fragments, variableValues, resultVisitorMap, pathIndex, errors, errorInfo);
  169. }
  170. else if (isAbstractType(nullableType)) {
  171. const finalType = schema.getType(value.__typename);
  172. const collectedFields = collectSubFields(schema, fragments, variableValues, finalType, fieldNodes);
  173. return visitObjectValue(value, finalType, collectedFields, schema, fragments, variableValues, resultVisitorMap, pathIndex, errors, errorInfo);
  174. }
  175. else if (isObjectType(nullableType)) {
  176. const collectedFields = collectSubFields(schema, fragments, variableValues, nullableType, fieldNodes);
  177. return visitObjectValue(value, nullableType, collectedFields, schema, fragments, variableValues, resultVisitorMap, pathIndex, errors, errorInfo);
  178. }
  179. const typeVisitorMap = resultVisitorMap === null || resultVisitorMap === void 0 ? void 0 : resultVisitorMap[nullableType.name];
  180. if (typeVisitorMap == null) {
  181. return value;
  182. }
  183. const visitedValue = typeVisitorMap(value);
  184. return visitedValue === undefined ? value : visitedValue;
  185. }
  186. function sortErrorsByPathSegment(errors, pathIndex) {
  187. var _a;
  188. const errorMap = Object.create(null);
  189. const unpathedErrors = new Set();
  190. for (const error of errors) {
  191. const pathSegment = (_a = error.path) === null || _a === void 0 ? void 0 : _a[pathIndex];
  192. if (pathSegment == null) {
  193. unpathedErrors.add(error);
  194. continue;
  195. }
  196. if (pathSegment in errorMap) {
  197. errorMap[pathSegment].push(error);
  198. }
  199. else {
  200. errorMap[pathSegment] = [error];
  201. }
  202. }
  203. return {
  204. errorMap,
  205. unpathedErrors,
  206. };
  207. }
  208. function addPathSegmentInfo(type, fieldName, pathIndex, errors = [], errorInfo) {
  209. for (const error of errors) {
  210. const segmentInfo = {
  211. type,
  212. fieldName,
  213. pathIndex,
  214. };
  215. const pathSegmentsInfo = errorInfo.segmentInfoMap.get(error);
  216. if (pathSegmentsInfo == null) {
  217. errorInfo.segmentInfoMap.set(error, [segmentInfo]);
  218. }
  219. else {
  220. pathSegmentsInfo.push(segmentInfo);
  221. }
  222. }
  223. }