Strict mode #65
Replies: 12 comments 70 replies
-
Casual user here: agree that fail fast would have been more intuitive for me when I was learning Pathom. Enabling this by default would have made me a happier new user unless there's risk of really bad things happening in prod if it's not turned off. |
Beta Was this translation helpful? Give feedback.
-
In pathom2 I use failfast just on mutations. If I have a mutation with a join read, i dont want to have a fail from the read. |
Beta Was this translation helpful? Give feedback.
-
I've been thinking about this, and I'm now thinking that would be great to have a more "strict" version pathom, and that should be the default. When I built the previous versions, my use cases tend to need more tolerant modes, and I still want to support them. Now after hearing more community cases and doing some of those myself, I believe a version that "fails soon" will be beneficial. This is what I'm thinking to change from With tolerant mode as false, these are the changes:
Would love to hear what you all think about those changes. |
Beta Was this translation helpful? Give feedback.
-
Working on things at branch #67 in case anyone wants to give it a try. |
Beta Was this translation helpful? Give feedback.
-
Status update, most of it seems to work fine. I'm excited, its really fun to use this mode, with all the errors surfacing early. One new thing is that now you can use optional inputs at the (p.eql/process (pci/register
[(pco/resolver 'foo
{::pco/output [:foo]}
(fn [_ _] {:foo "bar"}))])
{}
[:foo :not-available])
; throws => Pathom can't find a path for the following elements in the query: [:not-available] at path []
; now this one doesn't throw:
(p.eql/process (pci/register
[(pco/resolver 'foo
{::pco/output [:foo]}
(fn [_ _] {:foo "bar"}))])
{}
[:foo (pco/? :not-available)]) There is one problem I'm trying to get around though. The issue is when a resolver is a leaf resolver (no nodes after) and it doesn't return the expected key (but also didn't throw any errors), for example: (p.eql/process (pci/register
(pco/resolver 'foo
{::pco/output [:foo]}
(fn [_ _] {})))
{}
[:foo]) With the current code, this returns an empty map. But I like to make the strict to be really about "required", so I think the better behavior is for that to throw. My current solution is to run a check after the entity is ready to verify if all required attributes are present in the output. This works in almost every case, except for nested optional inputs. In this scenario, Pathom throws an error when that optional attribute is not there. Getting this to work is being a bit harder than I anticipated, but it's the current thing in progress. |
Beta Was this translation helpful? Give feedback.
-
Also happy to hear feedback about it, you can use it by pointing to the branch directly. Good time to check and see if you find any issues with the approach. |
Beta Was this translation helpful? Give feedback.
-
Another important change to mention, I'll remove the |
Beta Was this translation helpful? Give feedback.
-
So here are some thoughts: Inside of the logic of a resolver that can be many reasons I don’t want to return a requested property: Current access rights might cause me to elide certain information, the data might be temporarily unavailable (think of async cache updates, like Elasticsearch), etc. I don’t want the resolver’s choice or inability to resolve data to generate errors. That just doesn’t make sense in those use-cases. Elision is an acceptable choice of implementation, and transient conditions in my infrastructure should also not generate errors. Most people do not thing about things like transient infrastructure during development, and this leads to subtle bugs and faulty behavior. That said, having an app completely fail on a request that has a subtle bug is probably fine. Just pointing out trade-offs. Having a client define what might be optional is very counter-intuitive. The client can’t actually dictate what the server is capable of, and so you’re asking for a mismatch to be accidentally written, or evolve over time. I declare it isn’t optional in the UI at some point when that is true. I change the server. It is very easy for the tests and dev to still pass (they happen to supply data), and then fail later in production. This kind of thing happens all the time. The core problem is that the synchronization of the truth of the optionality is duplicated. It’s codified on the server, and the copied to the client. This is why RAD centralizes these kinds of things in the actual definition of attributes (e.g. ao/required? true ) in CLJC so the front/back end have to agree on it by definition. Generating resolvers and UI from this common ground means that the two cannot possibly mismatch. This is a much saner approach IMO, but of course that is more of what RAD does as opposed to Pathom. In terms of the actual usefulness of what you’re talking about, be careful about demarcating things that are primarily useful in development mode vs production. I can see the temptation in adding these things as development convenience for catching errors. That said, think about these cases you care about for that:
Declaring something non-optional and having that throw an error when a resolver doesn't return it has a lot of possible downsides, and I think if you run through the above 3 items you get most (all?) of the benefit without needing this extra syntax. Disclaimer...I entered this convo via DM with Wilker on Slack, so I didn't read the entire thread because I think I had the full context. This may not be true :) |
Beta Was this translation helpful? Give feedback.
-
Documentation changes related to strict mode wilkerlucio/pathom3-docs#25 |
Beta Was this translation helpful? Give feedback.
-
Would it be possible to work in strict mode most of the time, and mark some returned attributes as optional? I.e., similar to |
Beta Was this translation helpful? Give feedback.
-
Hello Reading strict-mode section, I missed a programmatic description of the error. Inspired by clojure REPL error i tried to categorize each exception with a cause, phase, and an anomaly. ; Attribute doesn't exist in the indexes
(p.eql/process
(pci/register
(pbir/constantly-resolver :a "foo"))
[:b])
; => EXCEPTION: Pathom can't find a path for the following elements in the query: [:b] at path []
#_{:cognitect.anomalies/category :cognitect.anomalies/unsupported
::p/phase ::p/plan
::p/cause ::p/find-attribute}
; Attribute exists in the indexes, but the available data isn't enough to reach it
(p.eql/process
(pci/register
(pbir/single-attr-resolver :a :b "foo"))
[:b])
; => EXCEPTION: Pathom can't find a path for the following elements in the query: [:b] at path []
#_{:cognitect.anomalies/category :cognitect.anomalies/unavailable
::p/phase ::p/plan
::p/cause ::p/find-path}
; An extended version of this is also true in case of nested inputs, if Pathom can't see
; a Pathom for a nested input requirement, it will consider it a plan failure.
(p.eql/process
(pci/register
[(pco/resolver 'nested-provider
{::pco/output [{:a [:b]}]}
(fn [_ _]
{:a {:b 1}}))
(pco/resolver 'nested-requires
{::pco/input [{:a [:c]}]
::pco/output [:d]}
(fn [_ _]
{:d 10}))])
[:d])
; => EXCEPTION: Pathom can't find a path for the following elements in the query: [:c] at path [:a]
#_{:cognitect.anomalies/category :cognitect.anomalies/unavailable
::p/phase ::p/plan
::p/cause ::p/find-path}
; Attribute wasn't in the final output after all the process
(p.eql/process
(pci/register
(pco/resolver 'data
{::pco/output [:a :b :c]}
(fn [_ _]
{:a 10})))
[:c])
; => EXCEPTION: Required attributes missing: [:c] at path []
#_{:cognitect.anomalies/category :cognitect.anomalies/unavailable
::p/phase ::p/execute
::p/cause ::p/find-path}
; A resolver throw an exception
(p.eql/process
(pci/register
(pco/resolver 'error
{::pco/output [:error]}
(fn [_ _]
(throw (ex-info "Deu ruim." {})))))
[:error])
; => EXCEPTION: Deu ruim.
#_"do not wrap, just let it throw" It would help to develop a programmatic way to customize all these new behaviors |
Beta Was this translation helpful? Give feedback.
-
Hi all.
This is an interesting discussion. Do you think that issues of optionality
in the client are orthogonal to navigating a graph?
I will give an example. In my graph, I have resolvers that run from a
patient to an encounter to the hospital site at which that encounter was
held to many more nodes off that. As you might imagine, that graph connects
multiple disparate sources of information including a range of reference
data.
There will be encounters that are based in the community. The absence of
hospital is a feature of the domain - it is optional. But I am now forcing
client applications and the EQL to not only traverse a graph but impose
their own schema by declaring that optionality up-front.
So it is unclear to me why you new 'strict' mode. Surely you compose
strictness by using a schema together with your graph navigation? I could
use clojure.spec to vouch for the return data?
Handling exceptions in attribute resolution is important; for some we can
continue without that attribute - but for many circumstances,
domain-specific, you might want to pretend nothing was resolved.
Mark
PS Obviously I can use lenient mode for my set-up, but pathom helps me handling real-life messy data and one of the attractions of clojure, and pathom, has been not worrying quite as much about optionality and types as I used to have to do in java - so I write code that handles nils more transparently.
|
Beta Was this translation helpful? Give feedback.
-
Overview: Pathom moved to strict mode by default. If you want to keep the old behavior set the attribute
:com.wsscode.pathom3.error/lenient-mode? true
in your env. Docs with more details will be available soon.Previous content:
Pathom 3 now has a configuration to fail immediately upon exceptions.
Setting the key
::pcr/fail-fast? true
will make exceptions on resolvers and mutations to flow all the way up, instead of being captured as per default behavior.This an experimental feature and I like to gather feedback from the community about it.
Considering that fail fast is more intuitive for new users, and also generally a better response in dev, should it be enabled by default?
Any other comments or suggestions, please enter the discussion here.
Beta Was this translation helpful? Give feedback.
All reactions