Skip to content
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

How to perform asynchronous validation? #48

Closed
szhangpitt opened this issue Dec 9, 2017 · 3 comments
Closed

How to perform asynchronous validation? #48

szhangpitt opened this issue Dec 9, 2017 · 3 comments
Labels
question Issues that are usage questions

Comments

@szhangpitt
Copy link
Contributor

szhangpitt commented Dec 9, 2017

Hello, I want to get your thoughts on potentially supporting async validation?

My use case: validating an object by querying a service that some key actually exists in backend.

const Config = struct.async({
  name: val => val === 'a' || val === 'b',
  key: k => axios.get(`/query-service/${k}`),
  //              ^ assume this Promise resolves 'abc' or rejects 'not found'
});

try {
  const config = await Config({ name : 'a', key: 'some-existing-key' }) // {name: 'a', key: 'abc'}
} catch (err) {
  console.log(err) // 'not found'
}
@ianstormtaylor ianstormtaylor added the question Issues that are usage questions label Dec 10, 2017
@ianstormtaylor
Copy link
Owner

Hey @szhangpitt, good question.

I'm not explicitly opposed right now, but I think there are some big issues that would need to be solved before async validators can be considered, to design them in a way that doesn't result in way to much complexity. Some thoughts...


In general I like APIs that offer a single way to do things. So my first instinct is that if async validators are to be supported, then the API itself should be async. Meaning that the generic const result = Struct(data) would need to actually be const result = await Struct(data). But this doesn't actually work for us, because superstruct is designed to make it easy to add data validation to any function, and if we forced async on people, they'd be forced to make their functions async to validate. So that solution is a no-go.

Which means we'd have to have a "sync mode" and an "async mode". Which leaves us with two other potential options, one is to implicitly try to handle any async validator functions, by auto-detecting them and opting into the "async" mode. But this feels strange, because you wouldn't ever really know whether the API is Struct(data) or await Struct(data)—especially as you can compose structs together.

Which means that we'd probably need to do something like you explained with a struct.async opt-in helper, to make it clear what you're getting in return. And if you didn't do struct.async, then it would throw if you give it any async validators.

But that's only the first part of the issue... there are other questions that would need to be solved:

  • Is there a way to handle the async cases while keeping the existing code architecture simple? As-in, can it be done in a smart way that doesn't require splitting into two code paths everywhere? I kind of assume there is a way, but I haven't looked into it.

  • How should the idea of parallel vs. series be handled? Is there a standard that others follow? I assume parallel, but unsure if this is something that people would end up wanting to have control over, which would result in larger API surface.

  • Should you wait for all of the async validations to finish? Or would people want to exit early for the first invalid one to save time? Exiting early is more performant, but it comes with the price of silently dropping the slower validation errors.

  • Should multiple async validations for the same data be batched together? (For example consider a ['email'] struct for lists with repeated data. This seems like something that many use cases would need for performance, but is super complex.

These are all hard problems to design around, and seem like they open a big can of worms in terms of flexibility. So, like I mentioned above, I'm not explicitly anti-async, but someone would need to write up a well thought out proposal that addresses these issues well.


My hunch is that async validation logic should be outside the goals of superstruct.

Instead it should concern itself with quick, synchronous checks on the existing data. And then use cases that need additional async validation can use a layered approach, where they do a quick synchronous validation on the shape and types of the data, and handle the slower, more application-specific async validation after that. This allows people to retain full control over the exact async logic that works best for their use case—performance, ordering, etc.

Many of the existing cases that people might be tempted to do async validation of data would probably be better served by just making the async operation normally, and handling an error at that point, instead of trying to validate it beforehand. The only real use case I can think of that needs async validation before the event itself occurs is front-end UI validation errors.

I'll leave this issue open though so that others can weigh in. Thanks!

@szhangpitt
Copy link
Contributor Author

Thanks for the detailed explanation! I too like the simplicity of a sync and consistent API.

@ianstormtaylor ianstormtaylor changed the title async validation? How to perform asynchronous validation? Dec 21, 2017
@doodlewind
Copy link

In our use case, we perform asynchronous transforming of data shape. We build a library based on superstruct named bumpover that allows async validating and data transforming. Feel free to give it a try if it helps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Issues that are usage questions
Projects
None yet
Development

No branches or pull requests

3 participants