11# Errors Plugin
2+ ---
3+ title: Errors plugin
4+ description: Errors plugin docs for Pothos
5+ ---
26
37A plugin for easily including error types in your GraphQL schema and hooking up error types to
48resolvers
@@ -8,7 +12,7 @@ resolvers
812### Install
913
1014``` bash
11- yarn add @pothos/plugin-errors
15+ npm install --save @pothos/plugin-errors
1216```
1317
1418### Setup
@@ -239,74 +243,47 @@ builder.queryType({
239243
240244### With validation plugin
241245
242- To use this in combination with the validation plugin, ensure that that errors plugin is listed
243- BEFORE the validation plugin in your plugin list.
246+ To handle validation errors you will need to enable the ` unsafelyHandleInputErrors ` option in the
247+ errors plugin options. This will allow the errors plugin to catch errors thrown by the validation plugin.
248+ This setting is unsafe because it wraps and catches errors at a higher level which will allow you to
249+ bypass other plugin hooks like the ` auth ` plugin. This enables you to return structured error responses for
250+ validation issues which happen BEFORE auth checks are executed, but this also means that those auth checks won't be run.
244251
245- Once your plugins are set up , you can define types for a ZodError, the same way you would for any
246- other error type. Below is a simple example of how this can be done, but the specifics of how you
247- structure your error types are left up to you.
252+ Once you enable the ` unsafelyHandleInputErrors ` option , you can define types for an InputValidationError
253+ (or any custom error you use in the validation plugin), the same way you would for any other error type. Below
254+ is a simple example of how this can be done, but the specifics of how you structure your error types are left up to you.
248255
249256``` typescript
250- // Util for flattening zod errors into something easier to represent in your Schema.
251- function flattenErrors(
252- error : ZodFormattedError <unknown >,
253- path : string [],
254- ): { path: string []; message: string }[] {
255- const errors = error ._errors .map ((message ) => ({
256- path ,
257- message ,
258- }));
259-
260- Object .keys (error ).forEach ((key ) => {
261- if (key !== ' _errors' ) {
262- errors .push (
263- ... flattenErrors ((error as Record <string , unknown >)[key ] as ZodFormattedError <unknown >, [
264- ... path ,
265- key ,
266- ]),
267- );
268- }
269- });
270-
271- return errors ;
272- }
273-
274- // A type for the individual validation issues
275- const ZodFieldError = builder
276- .objectRef <{
277- message: string ;
278- path: string [];
279- }>(' ZodFieldError' )
257+ const InputValidationIssue = builder
258+ .objectRef <StandardSchemaV1 .Issue >(' InputValidationIssue' )
280259 .implement ({
281260 fields : (t ) => ({
282261 message: t .exposeString (' message' ),
283- path: t .exposeStringList (' path' ),
262+ path: t .stringList ({
263+ resolve : (issue ) => issue .path ?.map ((p ) => String (p )),
264+ }),
284265 }),
285266 });
286267
287- // The actual error type
288- builder .objectType (ZodError , {
289- name: ' ZodError' ,
268+ builder .objectType (InputValidationError , {
269+ name: ' InputValidationError' ,
290270 interfaces: [ErrorInterface ],
291271 fields : (t ) => ({
292- fieldErrors : t .field ({
293- type: [ZodFieldError ],
294- resolve : (err ) => flattenErrors ( err .format (), []) ,
272+ issues : t .field ({
273+ type: [InputValidationIssue ],
274+ resolve : (err ) => err .issues ,
295275 }),
296276 }),
297277});
298278
299- builder .queryField (' fieldWIthValidation ' , (t ) =>
279+ builder .queryField (' fieldWithValidation ' , (t ) =>
300280 t .boolean ({
301281 errors: {
302- types: [ZodError ],
282+ types: [InputValidationError ],
303283 },
304284 args: {
305285 string: t .arg .string ({
306- validate: {
307- type: ' string' ,
308- minLength: 3 ,
309- },
286+ validate: z .string ().min (3 , ' Too short' ),
310287 }),
311288 },
312289 resolve : () => true ,
@@ -323,8 +300,8 @@ query {
323300 ... on QueryValidationSuccess {
324301 data
325302 }
326- ... on ZodError {
327- fieldErrors {
303+ ... on InputValidationError {
304+ issues {
328305 message
329306 path
330307 }
@@ -336,7 +313,7 @@ query {
336313### With the dataloader plugin
337314
338315To use this in combination with the dataloader plugin, ensure that that errors plugin is listed
339- BEFORE the validation plugin in your plugin list.
316+ BEFORE the dataloader plugin in your plugin list.
340317
341318If a field with ` errors ` returns a ` loadableObject ` , or ` loadableNode ` the errors plugin will now
342319catch errors thrown when loading ids returned by the ` resolve ` function.
@@ -350,15 +327,119 @@ handle these types of errors.
350327### With the prisma plugin
351328
352329To use this in combination with the prisma plugin, ensure that that errors plugin is listed BEFORE
353- the validation plugin in your plugin list. This will enable ` errors ` option to work work correctly
354- with any field builder method from the prisma plugin.
330+ the prisma plugin in your plugin list. This will enable ` errors ` option to work correctly with any
331+ field builder method from the prisma plugin.
355332
356333` errors ` can be configured for any field, but if there is an error pre-loading a relation the error
357334will always surfaced at the field that executed the query. Because there are cases that fall back to
358335executing queries for relation fields, these fields may still have errors if the relation was not
359336pre-loaded. Detection of nested relations will continue to work if those relations use the ` errors `
360337plugin
361338
339+ ### List item errors
340+
341+ For fields that return lists, you can specify ` itemErrors ` to wrap the list items in a union type so
342+ that errors can be handled per-item rather than replacing the whole list with an error.
343+
344+ The ` itemErrors ` options are exactly the same as the ` errors ` options, but they are applied to each
345+ item in the list rather than the whole list.
346+
347+ ``` typescript
348+ builder .queryType ({
349+ fields : (t ) => ({
350+ listWithErrors: t .string ({
351+ itemErrors: {},
352+ resolve : (parent , { name }) => {
353+ return [
354+ 1 ,
355+ 2 ,
356+ new Error (' Boom' ),
357+ 3 ,
358+ ]
359+ },
360+ }),
361+ }),
362+ });
363+ ```
364+
365+ This will produce a GraphQL schema that looks like:
366+
367+ ``` graphql
368+ type Query {
369+ listWithErrors : [QueryListWithErrorsItemResult ! ]!
370+ }
371+
372+ union QueryListWithErrorsItemResult = Error | QueryListWithErrorsItemSuccess
373+
374+ type QueryListWithErrorsItemSuccess {
375+ data : Int !
376+ }
377+ ```
378+
379+ Item errors also works with both sync and async iterators (in graphql@>=17, or other executors that support the @stream directive):
380+
381+ ```typescript
382+ builder .queryType ({
383+ fields : (t ) => ({
384+ asyncListWithErrors : t .string ({
385+ itemErrors : {},
386+ resolve : async function * () {
387+ yield 1;
388+ yield 2;
389+ yield new Error ('Boom');
390+ yield 4;
391+ throw new Error ('Boom');
392+ },
393+ }),
394+ }),
395+ });
396+ ```
397+
398+ When an error is yielded, an error result will be added into the list, if the generator throws an error,
399+ the error will be added to the list, and no more results will be returned for that field
400+
401+
402+ You can also use the ` errors ` and ` itemErrors ` options together:
403+
404+ ``` typescript
405+
406+ builder .queryType ({
407+ fields : (t ) => ({
408+ listWithErrors: t .string ({
409+ itemErrors: {},
410+ errors: {},
411+ resolve : (parent , { name }) => {
412+ return [
413+ 1 ,
414+ new Error (' Boom' ),
415+ 3 ,
416+ ]
417+ }),
418+ }),
419+ });
420+ ` ` `
421+
422+ This will produce a GraphQL schema that looks like:
423+
424+ ` ` ` graphql
425+
426+ type Query {
427+ listWithErrors: [QueryListWithErrorsResult ! ]!
428+ }
429+
430+ union QueryListWithErrorsResult = Error | QueryListWithErrorsSuccess
431+
432+ type QueryListWithErrorsSuccess {
433+ data: [QueryListWithErrorsItemResult ! ]!
434+ }
435+
436+ union QueryListWithErrorsItemResult = Error | QueryListWithErrorsItemSuccess
437+
438+ type QueryListWithErrorsItemSuccess {
439+ data: Int !
440+ }
441+ ` ` `
442+
362443### Custom error union fields
363444
364445Use ` t .errorUnionField ` and ` t .errorUnionListField ` to directly specify all members of the returned union type,
@@ -437,3 +518,57 @@ t.errorUnionField({
437518 resolve: ...
438519});
439520` ` `
521+
522+ ### Using ` builder .errorUnion `
523+
524+ You can use ` builder .errorUnion ` to manually construct an error union type that can be used with any field. Fields returning an error union will automatically handle returned or thrown errors.
525+
526+ ` ` ` typescript
527+ builder .objectType (NotFoundError , {
528+ name: ' NotFoundError' ,
529+ interfaces: [ErrorInterface ],
530+ });
531+
532+ builder .objectType (ValidationError , {
533+ name: ' ValidationError' ,
534+ interfaces: [ErrorInterface ],
535+ isTypeOf : (value ) => value instanceof ValidationError ,
536+ fields : (t ) => ({
537+ field: t .exposeString (' field' ),
538+ }),
539+ });
540+
541+ const UserType = builder .objectRef <{ id: string ; name: string }>(' User' ).implement ({
542+ isTypeOf : (obj ) => ' id' in obj && ' name' in obj ,
543+ fields : (t ) => ({
544+ id: t .exposeString (' id' ),
545+ name: t .exposeString (' name' ),
546+ }),
547+ });
548+
549+ const UserResult = builder .errorUnion (' UserResult' , {
550+ types: [UserType , NotFoundError , ValidationError ],
551+ });
552+
553+ builder .queryField (' getUser' , (t ) =>
554+ t .field ({
555+ type: UserResult ,
556+ args: { id: t .arg .string ({ required: true }) },
557+ resolve : (_ , { id }) => {
558+ // Handles thrown errors
559+ if (! id ) throw new ValidationError (' ID required' , ' id' );
560+ // Handles returned errors
561+ if (id === ' unknown' ) return new NotFoundError (' User not found' );
562+
563+ return { id , name: ' User' };
564+ },
565+ })
566+ );
567+ ` ` `
568+
569+ #### Options
570+
571+ - ` types ` : Array of member types (object refs, error classes, etc.)
572+ - ` omitDefaultTypes ` : Set to ` true ` to exclude ` defaultTypes ` from the builder options (default: ` false ` )
573+ - ` resolveType ` : Optional custom resolve function. Called after the internal error map check.
574+ - All other standard union type options are supported
0 commit comments