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

FunctionType? #38

Open
mscottnelson opened this issue Mar 25, 2021 · 8 comments
Open

FunctionType? #38

mscottnelson opened this issue Mar 25, 2021 · 8 comments

Comments

@mscottnelson
Copy link
Contributor

mscottnelson commented Mar 25, 2021

Any chance we can get a FunctionType for when your object is an actual js object rather than a json, or is this impossible due to how myzod accepts functions as inputs? For now I have hacked this in like so:

type Fn = ()=>any;
class FunctionType extends myzod.Type<Fn> {
  parse(value: unknown): Fn {
    const func = this.assertFunction(value);
    return func;
  }
  and<K extends AnyType>(schema: K): IntersectionType<this, K> {
    return new IntersectionType(this, schema);
  }

  private assertFunction(func: any): Fn {
    if(typeof func !== 'function') {
      throw this.typeError('expected type “function” but got ' + typeof func);
    }
    return func;
  }
}
const z = {
  ...myzod,
  func: () => new FunctionType()
}
@davidmdm
Copy link
Owner

Hi @mscottnelson

There are a couple types/utilties (function, promise, instanceof, etc...) that I have purposefully omitted from myzod because I do not understand the use case.

To my mind, what type-validation libraries try and type are variable that are unknown or the infamously any. These values generally come from the network or the filesystem, anything that might be JSON.parse-ed.

If there is a value in your code that is a function, I don't know how why it wouldn't already by typed as a function as it would have to have been declared as a function somewhere. The same holds for a promise or an instance of some class.

That being said, I am interested in understanding your use case and we can find a solution for it. Or plainly add a function type to myzod but at this time I'll admit I am not sure if it is necessary.

@mscottnelson
Copy link
Contributor Author

The use case is allowing a configuration js object to be imported at runtime. In this scenario, there are “configuration predicates” that can provided by a trusted admin during runtime in order to affect control-flow of the application in some very narrowly defined scenarios.

At first I simply typed these using myzod.unknown(), but it was a tad irritating needing to repeatedly assert that these “expected to be functions” configuration values were indeed functions.

The little hack I have here is really sufficient for my needs, but I admit this gets into very murky territory.

@davidmdm
Copy link
Owner

I am not necessarily against adding this. However, in an attempt to avoid the slippery slope problem, I want to understand the use case slightly better. Why is the configuration object being imported not typed? Is the problem simply that the configuration object is from JS-land and therefore not typed? What kind of predicates would you like to assert on function types? The function arity?

@mscottnelson
Copy link
Contributor Author

The configuration simply comes from JS-land, and can be updated after the core application is shipped, and thus cannot be truly statically analyzed internally. It would be a quality of life improvement to be able to make type assertions after import and connect those with other validation logic.

Function arity assertion checking would certainly be valuable. Would also be valuable to be able to have the provided function wrapped such that when it is run, its return value must pass through a myzod assertion to validate that the provided function’s return type conforms to the expectation of its usage (in my case, these are exclusively booleans, but I guess if you’re allowing the runtime return type to be wrapped with a myzod parse function, it could probably be run through any myzod parse function).

Maybe it would look something like:

z.function(
  [z.string(),z.string()],
  {
    returnValidator: z.boolean(),
  }
)

Where the first argument is a tuple type and the second is an options object.

or something like that. If you’re doing an (optional?) return validator, I’m not sure if you would also want to have an error handler hook, or arbitrary hook (eg maybe you want to cause the whole validation to fail if it’s taking too long for the function to run).

To be clear, none of these features are necessary for myzod to continue to be a wonderful, light, useful library. But it is interesting to explore this space! Thanks for considering it.

@davidmdm
Copy link
Owner

Ok, I want to sit on this a little bit. I think a standard wrapper utility would be interesting. I am not sure that I want there to be an actual myzod type for a function... Although it could make sense to have that.

what about something like this.

function wrapFunc<Fn: unknown, RT extends AnyType, Args extends AnyType[]>(fn: T, returnType: RT, ...args: Args): (...InferArgs<Args>) => Infer<RT> {
  if (typeof fn !== 'function') {
    throw new ValidationError(...)
  }
  return (...args) => {
  // validate args
    const value = fn();
   // validate return
   return value
  }
}

This almost kind of works without defining a new type. But myzod currently doesn't express Promises so async functions would be difficult.

Any feedback you have is good. Otherwise I am considering adding funtions & promises

@mscottnelson
Copy link
Contributor Author

Where do you imagine wrapFunc sitting in this setup? I guess I’m asking, how does this fit into a parser/validator/schema?

I can’t wrap the function directly since I am not in control of it, so is it like:

z.object({
  isTrue: wrapFunc(_, z.boolean(),[z.string(),z.string()])
})

if that’s sorta what you had in mind (forcing execution to occur at parse time) that seems promising...especially if it can handle Promises. Having a timeout and/or retry control for such a utility function seems really important tho in this context.

@zhaoyao91
Copy link

I am using zod to valid http api params and response data. It is very convenient to use function type to implement an async function, but it is too slow to be practical.
So if myzod supports function type and implement api, it would be perfect.

@davidmdm
Copy link
Owner

@zhaoyao91 Sorry, I hadn't noticed your comment.

Can you please explain your use case? If I could have an example of it, it would be ideal. I am hesitating to move forward on functions because it would add a lot of complexity to intersection type logic. Any arguments for it are welcome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants