error-result-guard.ts 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. declare function fail(error?: any): never;
  2. /**
  3. * @description
  4. * Convenience method for creating an {@link ErrorResultGuard}. Takes a predicate function which
  5. * tests whether the input is considered successful (true) or an error result (false).
  6. *
  7. * Note that the resulting variable must _still_ be type annotated in order for the TypeScript
  8. * type inference to work as expected:
  9. *
  10. * @example
  11. * ```TypeScript
  12. * const orderResultGuard: ErrorResultGuard<AddItemToOrderResult>
  13. * = createErrorResultGuard(order => !!order.lines);
  14. * ```
  15. * @docsCategory testing
  16. */
  17. export function createErrorResultGuard<T>(testFn: (input: T) => boolean): ErrorResultGuard<T> {
  18. return new ErrorResultGuard<T>(testFn);
  19. }
  20. /**
  21. * @description
  22. * A utility class which is used to assert the success of an operation
  23. * which returns a union type of `SuccessType | ErrorResponse [ | ErrorResponse ]`.
  24. * The methods of this class are used to:
  25. * 1. assert that the result is a success or error case
  26. * 2. narrow the type so that TypeScript can correctly infer the properties of the result.
  27. *
  28. * @example
  29. * ```TypeScript
  30. * const orderResultGuard: ErrorResultGuard<AddItemToOrderResult>
  31. * = createErrorResultGuard(order => !!order.lines);
  32. *
  33. * it('errors when quantity is negative', async () => {
  34. * const { addItemToOrder } = await shopClient.query<AddItemToOrder.Query, AddItemToOrder.Mutation>(ADD_ITEM_TO_ORDER, {
  35. * productVariantId: 42, quantity: -1,
  36. * });
  37. *
  38. * // The test will fail
  39. * orderResultGuard.assertErrorResult(addItemToOrder);
  40. *
  41. * // the type of `addItemToOrder` has now been
  42. * // narrowed to only include the ErrorResult types.
  43. * expect(addItemToOrder.errorCode).toBe(ErrorCode.NegativeQuantityError);
  44. * }
  45. * ```
  46. * @docsCategory testing
  47. */
  48. export class ErrorResultGuard<T> {
  49. constructor(private testFn: (input: T) => boolean) {}
  50. /**
  51. * @description
  52. * A type guard which returns `true` if the input passes the `testFn` predicate.
  53. */
  54. isSuccess(input: T | any): input is T {
  55. return this.testFn(input as T);
  56. }
  57. /**
  58. * @description
  59. * Asserts (using the testing library's `fail()` function) that the input is
  60. * successful, i.e. it passes the `testFn`.
  61. */
  62. assertSuccess<R>(input: T | R): asserts input is T {
  63. if (!this.isSuccess(input)) {
  64. fail(`Unexpected error: ${JSON.stringify(input)}`);
  65. }
  66. }
  67. /**
  68. * @description
  69. * Asserts (using the testing library's `fail()` function) that the input is
  70. * not successful, i.e. it does not pass the `testFn`.
  71. */
  72. assertErrorResult<R>(input: T | R): asserts input is R {
  73. if (this.isSuccess(input)) {
  74. fail(`Should have errored`);
  75. }
  76. }
  77. }