Fork me on GitHub
#pathom
<
2022-04-19
>
aratare00:04:29

Hi @wilkerlucio, if I have a query like this [{:user/tabs [{:tab/bookmarks [:bookmark/id]}]}] and if the resolver that handles :bookmark/id, or any resolver at any level in the query, throws an error, currently Pathom will handle this by utilising ::p/errors for the failed attribute/field, and everything else will proceed as normal. In this case I don't want that, instead I want the entire query to fail and return some custom error like {:user/tabs {:error true :error-message "failed"}}. There's a :fail-fast? parser option but I can't seem to catch and process it. Previously I can achieve this behaviour by using mutations + ::p/process-error, but since semantically it's wrong to use mutations to fetch data, I'm migrating everything to resolvers but currently stuck. What is the best way to achieve this? Thank you in advance.

wilkerlucio01:04:43

I guess you are using lenient mode right? because on strict mode, any fail will fail everything

aratare01:04:02

Are you referring to Pathom3?

aratare01:04:14

In which case I’m still on P2

aratare01:04:22

Is there a way to do this on P2?

wilkerlucio01:04:41

can you make a working example so we can talk over? with a full example, what happens, and what you would like to happen

aratare01:04:27

I do have a working example, which is my current project but it’s bit bulky. Let me put up a small example with some resolvers.

👍 1
aratare01:04:53

Ok I’m not on my home PC atm so I can’t create a project fast enough. I do have some code to show you what I have.

aratare01:04:28

pretty simple, it gets the user id then retrieves all the tabs from such user

aratare01:04:08

later on in my app flow, I need to fetch all the bookmarks within some tab, so I have another resolver for this: https://github.com/aratare-jp/shinsetsu/blob/main/src/clj/shinsetsu/resolvers/bookmark.clj#L29

aratare01:04:50

there are 2 types of tabs, one protected and one public (unprotected)

aratare01:04:11

imma ignore the public

aratare01:04:36

for protected tab, I need to submit a password to fetch all the bookmarks within such tab

aratare01:04:58

so if I submit the wrong password, you’d expect an error saying it’s the wrong password

aratare01:04:59

but currently if I fetch a tab on some of its fields, and join with the bookmarks, when the password is incorrect only :tab/bookmarks will have a reader-error

aratare01:04:06

all the other tab fields will resolve normally

aratare01:04:01

I want to have the all-or-nothing behaviour of P3 where if I can’t fetch the bookmarks due to wrong password, an error is returned instead

aratare01:04:56

previously I could achieve this by using mutations, i.e. fetch-tabs and fetch-bookmarks.

wilkerlucio01:04:04

if you have multiple tabs, you want to fail all the tabs, or just the tabs which the user is not allowed to access?

aratare01:04:39

all of them

wilkerlucio01:04:43

a way to solve this via query design is to change your query to allow for a layer controlling the access, like: [{:user/tabs [{:tab/allowed-tab [{:tab/bookmarks [:bookmark/id]}]}]}]

wilkerlucio01:04:10

this way, you can fail at :tab/allowed-tab, and them both bookmarks and all the siblings will not attempt to run

wilkerlucio01:04:41

this is how I would do it in this case

aratare01:04:14

interesting. I haven’t thought of that.

wilkerlucio01:04:24

you can make what you want by having a plugin wrapping around the entity process, but feels flacky, would require some very specific code (like checking for a presence of bookmarks on the result) and add unescessary overhead (since it would have to check every entity)

wilkerlucio01:04:39

so I would go with :tab/allowed-tab 🙂

aratare01:04:32

> you can make what you want Are you referring to the custom error?

wilkerlucio01:04:14

yeah, making everything fail given a specific circumstance in the result

wilkerlucio01:04:05

but would be ugly, hehe

aratare01:04:01

I’ll give allowed-tab a go. Also I tried :fail-fast? and it did give me what I wanted, i.e. error thrown -> back track all the way to the top. But where can I catch the exception thrown?

wilkerlucio01:04:34

I'm not sure if fail-fast will work as expected in this case

wilkerlucio01:04:59

its intended to be used at the root, so errors are thrown immediatly (mostly useful for development environments, to see error earlier)

aratare01:04:47

yep which fits perfectly into my usecase interestingly

aratare01:04:59

from the snippet I found on Pathom docs

(parser {::p/fail-fast? true}
        [{:go [:key {:nest [:trigger-error :other]}
               :trigger-error]}])
; => CompilerException clojure.lang.ExceptionInfo: Error triggered {:foo "bar"}, ...

aratare01:04:47

I want that exception but even with a try-catch around the parser it just does the dont-mind-me-im-just-passing-by move on me didn’t get caught for some reason 😅

wilkerlucio01:04:49

not sure if I get what you saying now, if you wrap on a try/catch you can't get this exception?

wilkerlucio01:04:17

I think I replicated the issue, see if looks like what you see:

wilkerlucio01:04:19

(ns test.demo-fail-sublings
  (:require [com.wsscode.pathom.core :as p]
            [com.wsscode.pathom.connect :as pc]))

(pc/defresolver user-tabs []
  {:user/tabs
   [{:tab/id          1
     :tab/accessible? true}
    {:tab/id          2
     :tab/accessible? false}]})

(pc/defresolver bookmarks [{:tab/keys [id accessible?]}]
  {:tab/bookmarks
   (if accessible?
     [{:bookmark/id id}]
     (throw (ex-info "Fail here" {})))})

(pc/defresolver tab-stuff [{:tab/keys [id]}]
  {:tab/name (str "Tab " id)})

(def registry
  [user-tabs
   bookmarks
   tab-stuff])

(def parser
  (p/parser
    {::p/env     {::p/reader               [p/map-reader
                                            pc/reader2
                                            pc/open-ident-reader
                                            p/env-placeholder-reader]
                  ::p/placeholder-prefixes #{">"}}
     ::p/mutate  pc/mutate
     ::p/plugins [(pc/connect-plugin {::pc/register registry})
                  p/error-handler-plugin
                  p/trace-plugin]}))

(comment
  (parser {}
    [{:user/tabs
      [{:tab/bookmarks
        [:bookmark/id]}
       :tab/name]}])
  ; =>
  ; {:user/tabs [{:tab/bookmarks [{:bookmark/id 1}], :tab/name "Tab 1"}
  ;             {:tab/bookmarks :com.wsscode.pathom.core/reader-error, :tab/name "Tab 2"}],
  ; :com.wsscode.pathom.core/errors {[:user/tabs 1 :tab/bookmarks] "class clojure.lang.ExceptionInfo: Fail here - {}"}}
  )

aratare01:04:29

Yep it’s perfect.

wilkerlucio01:04:46

solution using :tab/accessible:

(ns test.demo-fail-sublings
  (:require [com.wsscode.pathom.core :as p]
            [com.wsscode.pathom.connect :as pc]))

(pc/defresolver user-tabs []
  {:user/tabs
   [{:tab/id          1
     :tab/accessible? true}
    {:tab/id          2
     :tab/accessible? false}]})

(pc/defresolver bookmarks [{:tab/keys [id accessible?]}]
  {:tab/bookmarks
   (if accessible?
     [{:bookmark/id id}]
     (throw (ex-info "Fail here" {})))})

(pc/defresolver tab-accessible [env {:tab/keys [accessible?]}]
  {::pc/input #{:tab/id :tab/accessible?}}
  {:tab/accessible
   (if accessible?
     (p/entity env)
     (throw (ex-info "Fail here" {})))})

(pc/defresolver tab-stuff [{:tab/keys [id]}]
  {:tab/name (str "Tab " id)})

(def registry
  [user-tabs
   bookmarks
   tab-stuff
   tab-accessible])

(def parser
  (p/parser
    {::p/env     {::p/reader               [p/map-reader
                                            pc/reader2
                                            pc/open-ident-reader
                                            p/env-placeholder-reader]
                  ::p/placeholder-prefixes #{">"}}
     ::p/mutate  pc/mutate
     ::p/plugins [(pc/connect-plugin {::pc/register registry})
                  p/error-handler-plugin
                  p/trace-plugin]}))

(comment
  (parser {}
    [{:user/tabs
      [{:tab/accessible
        [{:tab/bookmarks
          [:bookmark/id]}
         :tab/name]}]}])
  ; =>
  ; {:user/tabs [{:tab/accessible {:tab/bookmarks [{:bookmark/id 1}], :tab/name "Tab 1"}}
  ;              {:tab/accessible :com.wsscode.pathom.core/reader-error}],
  ; :com.wsscode.pathom.core/errors {[:user/tabs 1 :tab/accessible] "class clojure.lang.ExceptionInfo: Fail here - {}"}}
  )

aratare01:04:00

OK will give it a go. May be i’ve been approaching this problem from a completely wrong angle all along.

wilkerlucio01:04:59

this is all very novel stuff, we are all still figuring it out 😅

wilkerlucio01:04:33

no books on information modelling though attributes out there, yet 😛

aratare01:04:16

👍:skin-tone-3:

aratare01:04:33

This problem has made me want to migrate to P3 all the more 😅

wilkerlucio01:04:00

I don't think it would solve this one for you, because strict mode will break the whole query all the time, like fail-fast in this sense

wilkerlucio01:04:12

no way to apply this strictness to a sub-part

aratare01:04:42

So for this project I’m looking for absolute all-or-nothing

aratare01:04:00

If one part fails, nothing gets returned except an error

wilkerlucio01:04:19

then what about:

(parser {::p/fail-fast? true}
    [{:user/tabs
      [{:tab/bookmarks
        [:bookmark/id]}
       :tab/name]}])
Execution error (ExceptionInfo) at test.demo-fail-sublings/bookmarks (demo_fail_sublings.clj:16).
Fail here

wilkerlucio01:04:15

what reader are you using?

aratare01:04:27

give me a sec

aratare01:04:15

[pc/open-ident-reader p/map-reader pc/reader2 pc/index-reader]

wilkerlucio01:04:46

so the fail-fast should work

wilkerlucio01:04:04

what dont-mind-me-im-just-passing-by move on me means?

wilkerlucio01:04:40

(try
   (parser {::p/fail-fast? true}
     [{:user/tabs
       [{:tab/bookmarks
         [:bookmark/id]}
        :tab/name]}])
   (catch Throwable ex
     (println "Gotcha" (ex-message ex))))
Gotcha Fail here
=> nil

aratare01:04:53

Yep it does work as expected, but when I put the try-catch around it, the catch part didn’t get fired somehow. That’s what I meant.

aratare01:04:20

Perhaps I had some wanky code in there at the time

aratare01:04:25

Imma try it again.

wilkerlucio01:04:51

full demo with catch:

wilkerlucio01:04:53

(ns test.demo-fail-sublings
  (:require [com.wsscode.pathom.core :as p]
            [com.wsscode.pathom.connect :as pc]))

(pc/defresolver user-tabs []
  {:user/tabs
   [{:tab/id          1
     :tab/accessible? true}
    {:tab/id          2
     :tab/accessible? false}]})

(pc/defresolver bookmarks [{:tab/keys [id accessible?]}]
  {:tab/bookmarks
   (if accessible?
     [{:bookmark/id id}]
     (throw (ex-info "Fail here" {})))})

(pc/defresolver tab-stuff [{:tab/keys [id]}]
  {:tab/name (str "Tab " id)})

(def registry
  [user-tabs
   bookmarks
   tab-stuff])

(def parser
  (p/parser
    {::p/env     {::p/reader               [p/map-reader
                                            pc/reader2
                                            pc/open-ident-reader
                                            p/env-placeholder-reader]
                  ::p/placeholder-prefixes #{">"}}
     ::p/mutate  pc/mutate
     ::p/plugins [(pc/connect-plugin {::pc/register registry})
                  p/error-handler-plugin
                  p/trace-plugin
                  {::p/wrap-parser
                   (fn [parser]
                     (fn [env tx]
                       (tap> tx)
                       (parser env tx)))}]}))

(comment
  (try
   (parser {::p/fail-fast? true}
     [{:user/tabs
       [{:tab/bookmarks
         [:bookmark/id]}
        :tab/name]}])
   (catch Throwable ex
     (println "Gotcha" (ex-message ex))))
  ; Gotcha Fail here
  ; => nil
  )

wilkerlucio01:04:05

cause not being able to catch sounds strange

aratare01:04:53

Then I definitely had some wanky code

aratare01:04:31

I’m glad this is just me messing up all along 😅

aratare01:04:00

Thanks for the extensive help @wilkerlucio Will definitely try both of them 🙂

wilkerlucio01:04:54

have fun 🙂

aratare01:04:09

fun is mandatory 😛

👁️ 1