Skip to content

Feature request: custom error handling / middleware / hook #106

@mcky

Description

@mcky

Problems:

Context: I use tuyau to communicate from a next.js app (server-side) with my Adonis API. I try to return errors in a consistent shape from my API, using custom exceptions with error codes where needed.

I've been consistently running into these problems

  1. The TuyauHTTPError and { cause: HTTPError } from ky both contain the full response shape. I have to be careful not to log this as it includes sensitive headers, and also generally leads to noisy logs. This is partially a next problem in that overriding the global exception handler isn't possible without some hacks, but it also means any intermediate logs with the errors need sanitizing
  2. On the front-end I have to wrap every tuyau call-site with custom error handling code to extract my error codes

Things I've tried

  1. ky hooks
    Unfortunately the beforeError hook still needs to return a HTTPError otherwise tuyau will fall back to wrapping it in a TuyauNetworkError. Modifying the request shape too much to get the details also caused some errors when tuyau then came to read them

  2. A wrapper around tuyau
    This is my current approach: a custom request() function which takes the same args as tuyau.request and then returns a domain specific error. Not bad, but I have to remember to use the wrapper in all call-sites, and unfortunately the stack trace shows the throwing of my custom error rather than the API call (see examples below).
    This also means I can only use the tuyau.request(path) pattern, without creating some complicated Proxy to enable the fluent interface

Example stack traces
⨯ Error [ApiError]: message from server
    at request (src/api.ts:36:22)
    at async TopicsPage (src/app/(authenticated)/enrichment/topics/page.tsx:15:20)
  34 |   } catch (error) {
  35 |     if (error instanceof TuyauHTTPError) {
> 36 |       throw ApiError.fromTuyauError(error);
     |                      ^
  37 |     }
  38 |     throw error;
  39 |   } {
  status: 409,
  code: 'E_CONCURRENT_SESSION',
  method: 'GET',
  path: '/api/v1/families/8b7f22a8-6436-4856-8146-29226cd36618/enrichment/overview',
  requestId: 'e11fa095-d10c-4de3-b799-244e6c29bbb5',
  digest: '710563856'
}
⨯ Error [TuyauHTTPError]: Request failed with status code 409: GET /api/v1/families/8b7f22a8-6436-4856-8146-29226cd36618/enrichment/overview
    at async TopicsPage (src/app/(authenticated)/enrichment/topics/page.tsx:15:20)
  13 |   const [user, resolvedSearch] = await Promise.all([getUser(), searchParams]);
  14 |
> 15 |   const overview = await tuyau.request('families.enrichment.overview', {
     |                    ^
  16 |     params: { familyId: user.organization.id },
  17 |   });
  18 | {
  1. Wrapping each call-site. Too tedious and error-prone

What would help

Some kind of pattern for constructing custom errors would solve the above, giving me a central way to do redaction and mapping to domain specific errors. Something akin to ky's beforeError hook personally feels like a natural fit here.

If you think this is worth working on and have a direction in mind, I'm happy to raise a PR

Related:
I briefly discussed a similar issue on discord with Harminder, and I think a good DX could involve some inferring of error types based on thrown exceptions (a-la request.validateUsing), but that's obviously a lot more complicated and doesn't solve the above

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions