error-result-guard.ts 2.7 KB

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