Error handling
By default, pathom parser will stop if some exception occurs during the parsing process. This is often undesirable if some node fails you still want to be able to return to the other ones which succeed. You can use the error-handler-plugin
. This plugin will wrap each read call with a try-catch block and in case an error occurs, a value of ::p/reader-error
will be placed in that node, while details of it will go in a separate tree but at the same path. This is better explained through an example:
(ns pathom-docs.error-handling
(:require [com.wsscode.pathom.core :as p]))
(def computed
; create a handle key that will trigger an error when called
{:trigger-error
(fn [_]
(throw (ex-info "Error triggered" {:foo "bar"})))})
; a reader that just flows, until it reaches a leaf
(defn flow-reader [{:keys [query] :as env}]
(if query
(p/join env)
:leaf))
(def parser
(p/parser {::p/plugins [(p/env-plugin {::p/reader [computed flow-reader]})
; add the error handler plugin
p/error-handler-plugin]}))
(parser {} [{:go [:key {:nest [:trigger-error :other]}
:trigger-error]}])
; =>
; {:go {:key :leaf
; :nest {:trigger-error :com.wsscode.pathom.core/reader-error
; :other :leaf}
; :trigger-error :com.wsscode.pathom.core/reader-error}
; :com.wsscode.pathom.core/errors {[:go :nest :trigger-error] "class clojure.lang.ExceptionInfo: Error triggered - {:foo \"bar\"}"
; [:go :trigger-error] "class clojure.lang.ExceptionInfo: Error triggered - {:foo \"bar\"}"}}
As you can see, when an error occurs the key ::p/errors
will be added to the returned map, containing the detailed error message indexed by the error path. You can customize how the error is exported in this map by setting the key ::p/process-error
in your environment:
(ns pathom-docs.error-handling-process
(:require [com.wsscode.pathom.core :as p]))
(def computed
; create a handle key that will trigger an error when called
{:trigger-error
(fn [_]
(throw (ex-info "Error triggered" {:foo "bar"})))})
; a reader that just flows, until it reaches a leaf
(defn flow-reader [{:keys [query] :as env}]
(if query
(p/join env)
:leaf))
; our error processing function
(defn process-error [env err]
; if you use some error reporting service, this is a good place
; to trigger a call to then, here you have the error and the full
; environment of when it ocurred, so you might want to some extra
; information like the query and the current path on it so you can
; replay it for debugging
; we are going to simply return the error message from the error
; if you want to return the same thing as the default, use the
; function (p/error-str err)
(.getMessage err))
(def parser
(p/parser {::p/plugins [(p/env-plugin {::p/reader [computed flow-reader]
; add the error processing to the environment
::p/process-error process-error})
; add the error handler plugin
p/error-handler-plugin]}))
(parser {} [{:go [:key {:nest [:trigger-error :other]}
:trigger-error]}])
; =>
; {:go {:key :leaf
; :nest {:trigger-error :com.wsscode.pathom.core/reader-error
; :other :leaf}
; :trigger-error :com.wsscode.pathom.core/reader-error}
; :com.wsscode.pathom.core/errors {[:go :nest :trigger-error] "Error triggered"
; [:go :trigger-error] "Error triggered"}}
Debugging exceptions
By default, Pathom error handler will just return a short error message about the exception
but to debug, you will want the stack trace. To view the stack trace you can use a custom
process-error
, this is an example of how to do it in Clojure:
(def parser
(p/parser {::p/plugins [(p/env-plugin {::p/reader [computed flow-reader]
; add the error processing to the environment
::p/process-error
(fn [_ err]
; print stack trace
(.printStackTrace err)
; return error str
(p/error-str err)})
; add the error handler plugin
p/error-handler-plugin]}))
In ClojureScript:
(def parser
(p/parser {::p/plugins [(p/env-plugin {::p/reader [computed flow-reader]
; add the error processing to the environment
::p/process-error
(fn [_ err]
; print stack trace on console
(js/console.error err)
; return error str
(p/error-str err)})
; add the error handler plugin
p/error-handler-plugin]}))
Fail fast
Having each node being caught is great for the UI, but not so much for testing. During testing you probably prefer the parser to blow up as fast as possible so you don’t accumulate a bunch of errors which get impossible to read. Having to create a different parser to remove the error-handler-plugin
can be annoying, so there is an option to solve that. Send the key ::p/fail-fast?
as true in the environment and the try/catch will not be done, making it fail as soon as an exception fires. For example, using our previous parser:
(parser {::p/fail-fast? true}
[{:go [:key {:nest [:trigger-error :other]}
:trigger-error]}])
; => CompilerException clojure.lang.ExceptionInfo: Error triggered {:foo "bar"}, ...
Fail fast doesn’t work with parallel parsers |
Raising errors
The default error output format (in a separated tree) is very convenient for direct API
calls because it leaves a clean output on the data part. But if you want to expose those
errors on the UI, pulling them out of the separated tree can be a bit of a pain. To help
with that, there is a p/raise-errors
helper which will lift the errors so they are present
at the same level of the error entry. Let’s take our last error output example and process
it with p/raise-errors
(p/raise-errors {:go {:key :leaf
:nest {:trigger-error :com.wsscode.pathom.core/reader-error
:other :leaf}
:trigger-error :com.wsscode.pathom.core/reader-error}
:com.wsscode.pathom.core/errors {[:go :nest :trigger-error] "Error triggered"
[:go :trigger-error] "Error triggered"}})
; outputs:
{:go {:key :leaf
:nest {:trigger-error :com.wsscode.pathom.core/reader-error
:other :leaf
:com.wsscode.pathom.core/errors {:trigger-error "Error triggered"}}
:trigger-error :com.wsscode.pathom.core/reader-error
:com.wsscode.pathom.core/errors {:trigger-error "Error triggered"}}}
Notice that we don’t have the root ::p/errors
anymore, instead it is placed at the
same level of the error attribute. So the path [::p/errors [:go :nest :trigger-error]]
turns into [:go :nest ::p/errors :trigger-error]
. This makes very easy to pull the
error on the client-side.