inspect.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. // Taken from graphql-js
  2. // https://github.com/graphql/graphql-js/blob/main/src/jsutils/inspect.ts
  3. import { GraphQLError } from 'graphql';
  4. import { isAggregateError } from './AggregateError.js';
  5. const MAX_RECURSIVE_DEPTH = 3;
  6. /**
  7. * Used to print values in error messages.
  8. */
  9. export function inspect(value) {
  10. return formatValue(value, []);
  11. }
  12. function formatValue(value, seenValues) {
  13. switch (typeof value) {
  14. case 'string':
  15. return JSON.stringify(value);
  16. case 'function':
  17. return value.name ? `[function ${value.name}]` : '[function]';
  18. case 'object':
  19. return formatObjectValue(value, seenValues);
  20. default:
  21. return String(value);
  22. }
  23. }
  24. function formatError(value) {
  25. if (value instanceof GraphQLError) {
  26. return value.toString();
  27. }
  28. return `${value.name}: ${value.message};\n ${value.stack}`;
  29. }
  30. function formatObjectValue(value, previouslySeenValues) {
  31. if (value === null) {
  32. return 'null';
  33. }
  34. if (value instanceof Error) {
  35. if (isAggregateError(value)) {
  36. return formatError(value) + '\n' + formatArray(value.errors, previouslySeenValues);
  37. }
  38. return formatError(value);
  39. }
  40. if (previouslySeenValues.includes(value)) {
  41. return '[Circular]';
  42. }
  43. const seenValues = [...previouslySeenValues, value];
  44. if (isJSONable(value)) {
  45. const jsonValue = value.toJSON();
  46. // check for infinite recursion
  47. if (jsonValue !== value) {
  48. return typeof jsonValue === 'string' ? jsonValue : formatValue(jsonValue, seenValues);
  49. }
  50. }
  51. else if (Array.isArray(value)) {
  52. return formatArray(value, seenValues);
  53. }
  54. return formatObject(value, seenValues);
  55. }
  56. function isJSONable(value) {
  57. return typeof value.toJSON === 'function';
  58. }
  59. function formatObject(object, seenValues) {
  60. const entries = Object.entries(object);
  61. if (entries.length === 0) {
  62. return '{}';
  63. }
  64. if (seenValues.length > MAX_RECURSIVE_DEPTH) {
  65. return '[' + getObjectTag(object) + ']';
  66. }
  67. const properties = entries.map(([key, value]) => key + ': ' + formatValue(value, seenValues));
  68. return '{ ' + properties.join(', ') + ' }';
  69. }
  70. function formatArray(array, seenValues) {
  71. if (array.length === 0) {
  72. return '[]';
  73. }
  74. if (seenValues.length > MAX_RECURSIVE_DEPTH) {
  75. return '[Array]';
  76. }
  77. const len = array.length;
  78. const remaining = array.length;
  79. const items = [];
  80. for (let i = 0; i < len; ++i) {
  81. items.push(formatValue(array[i], seenValues));
  82. }
  83. if (remaining === 1) {
  84. items.push('... 1 more item');
  85. }
  86. else if (remaining > 1) {
  87. items.push(`... ${remaining} more items`);
  88. }
  89. return '[' + items.join(', ') + ']';
  90. }
  91. function getObjectTag(object) {
  92. const tag = Object.prototype.toString
  93. .call(object)
  94. .replace(/^\[object /, '')
  95. .replace(/]$/, '');
  96. if (tag === 'Object' && typeof object.constructor === 'function') {
  97. const name = object.constructor.name;
  98. if (typeof name === 'string' && name !== '') {
  99. return name;
  100. }
  101. }
  102. return tag;
  103. }