-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add runtime type validation #195
base: master
Are you sure you want to change the base?
Conversation
Hey, great work on it. Even with cryptic messages sometimes MST always points to the right spot and validates things. It helped a lot. Sometimes I miss it in keystone. The only concern I have right away agains the current approach you took - the whole point of runtime type checks in MST was to be able to produce TS types from it. No need to write twice same thing. I suppose mobx-keystone is trying to follow this mantra. |
Thanks! 🙂 I share your concern about needing to specify prop types twice now, although they are not identical. To clarify the difference between (a) the runtime type provided via the model decorator and (b) the I think the main difference between what both MST and mobx-keystone are doing with runtime types and what I'm proposing is the difference between "parsing" and "validation". MST and mobx-keystone focus on parsing model props (i.e. decoding/checking props individually) whereas I'm aiming at validating entire models (including constraints across multiple model props, class instance props, and computed props). |
A minor breaking change is introduced in the `TypeError` error path which now contains model interim data object path segments (`$`) explicitly. This is also more consistent with runtime types defined via `tProp` because type-checks using those types are applied to the interim data object props (i.e. before prop transforms are applied, if applicable). The new runtime validation type specified via the `@model` decorator targets model instance props unless the `types.object` type describing the model instance contains a key `$`.
BREAKING CHANGE: The type-check error representation is extended to support logical expressions of errors to acturately express their relationships. Now, type-checking returns all errors instead of only the first error.
I've completed a draft of how I think runtime type validation could be integrated into mobx-keystone. @xaviergonz What do you think? |
@xaviergonz I know this PR is quite large and I'm not sure whether I've convinced you of the benefits I see in this feature yet. But I'm curious what you think about the current implementation. I'm quite happy with it at this point. Any feedback is much appreciated. Something that's still missing is support for functional models. But I haven't found a consistent way of adding the validation type to a runtime model yet. I thought it might be better to get some feedback from you first before addressing functional models. |
@xaviergonz Any chance you could check this PR and give feedback? I think it's such a great feature. 🙂 |
This feature looks great, anything blocking getting it merged? |
Thanks, @AndrewMorsillo! 🙂 I think some of it is not bad, but there are some flaws in its approach that I have come to realize. For instance, type validation cannot simply reuse I think this PR requires some more conceptual work, but it could be a good starting point. If you have ideas how to improve it, I'd be happy to discuss and exchange ideas. I think #271 is relevant, too, but I haven't found the time to finish a first draft yet. |
How about adding a flag to @model("Child", types.object(() => ({ value: types.integer })))
class Child extends Model({
value: prop<number>(),
}) {}
@model("Parent", types.object(() => ({ child: types.model<Child>(Child, true) })))
class Parent extends Model({
child: prop<Child>(),
}) {} @xaviergonz Some feedback would be very helpful, so we can go forward here.
|
@sisp I like that this approach allows for both data and validation checking cases. The redundancy of specifying types in both the decorator and the model might become tedious however. Imagine a model with 75+ properties, nested objects, etc. It would be quite easy to forget one and it likely wouldn't be easy to read. This is just a quibble about the shape of the API however, not the functionality. Maybe there is a way to make it more ergonomic? I don't have a thorough enough understanding of the mobx-keystone code yet to know if it's possible. Would it be possible to specify the validation type along with the model type such as:
If an API like this is possible it would eliminate the need to declare the fields/shape of the model twice. |
Yes, with a large number of props the redundancy is suboptimal. At least TS checks that the validation types are compatible with the data types, so some errors are caught that way. Your suggested API is unfortunately not sufficient. One of the test cases I added in this PR, "union - complex", tests validation of a discriminated union (without a discriminator though) which wouldn't work with per-prop validation types. Also, in my opinion the validation type should be able to check non-model props like computed props. That's why I propose to set the validation type via the |
Ah I understand now. I think your approach is a good one. It seems like having this level of "redundancy" is likely not avoidable if we want full validation of complex types and non model props. I think it probably would be just fine like this in practice, the slightly increased verbosity would greatly pay off in having good validation capabilities. |
This is an attempt at making a step forward towards supporting comprehensive runtime type validation. Closes #160.
So far, I've implemented support for specifying a (possibly complex) runtime type that can be passed to the model decorator. This approach, in contrast to specifying runtime types as model props, allows for more complex runtime type specifications, e.g. union types for a model (see #160 (comment) and the added test for a union of objects), as well as runtime type-checking of class instance properties and computed properties.
All changes are intentionally non-breaking for now at the cost of having an additional way of defining runtime types. If this idea receives positive feedback, I envision it to replace
tProp
props long-term. Then, model classes would useprop
for all model props (so no runtime type-checking) and runtime type-checking could be added by passing a runtime type to the model decorator.@xaviergonz and possibly others: What do you think?
TODO
types.model
(i.e. model composition) - 86c1972