errors.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. /**
  2. * All errors in ethers include properties to ensure they are both
  3. * human-readable (i.e. ``.message``) and machine-readable (i.e. ``.code``).
  4. *
  5. * The [[isError]] function can be used to check the error ``code`` and
  6. * provide a type guard for the properties present on that error interface.
  7. *
  8. * @_section: api/utils/errors:Errors [about-errors]
  9. */
  10. import { version } from "../_version.js";
  11. import { defineProperties } from "./properties.js";
  12. function stringify(value) {
  13. if (value == null) {
  14. return "null";
  15. }
  16. if (Array.isArray(value)) {
  17. return "[ " + (value.map(stringify)).join(", ") + " ]";
  18. }
  19. if (value instanceof Uint8Array) {
  20. const HEX = "0123456789abcdef";
  21. let result = "0x";
  22. for (let i = 0; i < value.length; i++) {
  23. result += HEX[value[i] >> 4];
  24. result += HEX[value[i] & 0xf];
  25. }
  26. return result;
  27. }
  28. if (typeof (value) === "object" && typeof (value.toJSON) === "function") {
  29. return stringify(value.toJSON());
  30. }
  31. switch (typeof (value)) {
  32. case "boolean":
  33. case "symbol":
  34. return value.toString();
  35. case "bigint":
  36. return BigInt(value).toString();
  37. case "number":
  38. return (value).toString();
  39. case "string":
  40. return JSON.stringify(value);
  41. case "object": {
  42. const keys = Object.keys(value);
  43. keys.sort();
  44. return "{ " + keys.map((k) => `${stringify(k)}: ${stringify(value[k])}`).join(", ") + " }";
  45. }
  46. }
  47. return `[ COULD NOT SERIALIZE ]`;
  48. }
  49. /**
  50. * Returns true if the %%error%% matches an error thrown by ethers
  51. * that matches the error %%code%%.
  52. *
  53. * In TypeScript environments, this can be used to check that %%error%%
  54. * matches an EthersError type, which means the expected properties will
  55. * be set.
  56. *
  57. * @See [ErrorCodes](api:ErrorCode)
  58. * @example
  59. * try {
  60. * // code....
  61. * } catch (e) {
  62. * if (isError(e, "CALL_EXCEPTION")) {
  63. * // The Type Guard has validated this object
  64. * console.log(e.data);
  65. * }
  66. * }
  67. */
  68. export function isError(error, code) {
  69. return (error && error.code === code);
  70. }
  71. /**
  72. * Returns true if %%error%% is a [[CallExceptionError].
  73. */
  74. export function isCallException(error) {
  75. return isError(error, "CALL_EXCEPTION");
  76. }
  77. /**
  78. * Returns a new Error configured to the format ethers emits errors, with
  79. * the %%message%%, [[api:ErrorCode]] %%code%% and additional properties
  80. * for the corresponding EthersError.
  81. *
  82. * Each error in ethers includes the version of ethers, a
  83. * machine-readable [[ErrorCode]], and depending on %%code%%, additional
  84. * required properties. The error message will also include the %%message%%,
  85. * ethers version, %%code%% and all additional properties, serialized.
  86. */
  87. export function makeError(message, code, info) {
  88. let shortMessage = message;
  89. {
  90. const details = [];
  91. if (info) {
  92. if ("message" in info || "code" in info || "name" in info) {
  93. throw new Error(`value will overwrite populated values: ${stringify(info)}`);
  94. }
  95. for (const key in info) {
  96. if (key === "shortMessage") {
  97. continue;
  98. }
  99. const value = (info[key]);
  100. // try {
  101. details.push(key + "=" + stringify(value));
  102. // } catch (error: any) {
  103. // console.log("MMM", error.message);
  104. // details.push(key + "=[could not serialize object]");
  105. // }
  106. }
  107. }
  108. details.push(`code=${code}`);
  109. details.push(`version=${version}`);
  110. if (details.length) {
  111. message += " (" + details.join(", ") + ")";
  112. }
  113. }
  114. let error;
  115. switch (code) {
  116. case "INVALID_ARGUMENT":
  117. error = new TypeError(message);
  118. break;
  119. case "NUMERIC_FAULT":
  120. case "BUFFER_OVERRUN":
  121. error = new RangeError(message);
  122. break;
  123. default:
  124. error = new Error(message);
  125. }
  126. defineProperties(error, { code });
  127. if (info) {
  128. Object.assign(error, info);
  129. }
  130. if (error.shortMessage == null) {
  131. defineProperties(error, { shortMessage });
  132. }
  133. return error;
  134. }
  135. /**
  136. * Throws an EthersError with %%message%%, %%code%% and additional error
  137. * %%info%% when %%check%% is falsish..
  138. *
  139. * @see [[api:makeError]]
  140. */
  141. export function assert(check, message, code, info) {
  142. if (!check) {
  143. throw makeError(message, code, info);
  144. }
  145. }
  146. /**
  147. * A simple helper to simply ensuring provided arguments match expected
  148. * constraints, throwing if not.
  149. *
  150. * In TypeScript environments, the %%check%% has been asserted true, so
  151. * any further code does not need additional compile-time checks.
  152. */
  153. export function assertArgument(check, message, name, value) {
  154. assert(check, message, "INVALID_ARGUMENT", { argument: name, value: value });
  155. }
  156. export function assertArgumentCount(count, expectedCount, message) {
  157. if (message == null) {
  158. message = "";
  159. }
  160. if (message) {
  161. message = ": " + message;
  162. }
  163. assert(count >= expectedCount, "missing argument" + message, "MISSING_ARGUMENT", {
  164. count: count,
  165. expectedCount: expectedCount
  166. });
  167. assert(count <= expectedCount, "too many arguments" + message, "UNEXPECTED_ARGUMENT", {
  168. count: count,
  169. expectedCount: expectedCount
  170. });
  171. }
  172. const _normalizeForms = ["NFD", "NFC", "NFKD", "NFKC"].reduce((accum, form) => {
  173. try {
  174. // General test for normalize
  175. /* c8 ignore start */
  176. if ("test".normalize(form) !== "test") {
  177. throw new Error("bad");
  178. }
  179. ;
  180. /* c8 ignore stop */
  181. if (form === "NFD") {
  182. const check = String.fromCharCode(0xe9).normalize("NFD");
  183. const expected = String.fromCharCode(0x65, 0x0301);
  184. /* c8 ignore start */
  185. if (check !== expected) {
  186. throw new Error("broken");
  187. }
  188. /* c8 ignore stop */
  189. }
  190. accum.push(form);
  191. }
  192. catch (error) { }
  193. return accum;
  194. }, []);
  195. /**
  196. * Throws if the normalization %%form%% is not supported.
  197. */
  198. export function assertNormalize(form) {
  199. assert(_normalizeForms.indexOf(form) >= 0, "platform missing String.prototype.normalize", "UNSUPPORTED_OPERATION", {
  200. operation: "String.prototype.normalize", info: { form }
  201. });
  202. }
  203. /**
  204. * Many classes use file-scoped values to guard the constructor,
  205. * making it effectively private. This facilitates that pattern
  206. * by ensuring the %%givenGaurd%% matches the file-scoped %%guard%%,
  207. * throwing if not, indicating the %%className%% if provided.
  208. */
  209. export function assertPrivate(givenGuard, guard, className) {
  210. if (className == null) {
  211. className = "";
  212. }
  213. if (givenGuard !== guard) {
  214. let method = className, operation = "new";
  215. if (className) {
  216. method += ".";
  217. operation += " " + className;
  218. }
  219. assert(false, `private constructor; use ${method}from* methods`, "UNSUPPORTED_OPERATION", {
  220. operation
  221. });
  222. }
  223. }
  224. //# sourceMappingURL=errors.js.map