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

Cleanest way to achieve something like map where given a parser to type T and function A->B you get a Schema<B>? #2186

Open
jcoveney-anchorzero opened this issue Mar 20, 2024 · 2 comments

Comments

@jcoveney-anchorzero
Copy link

So basically, imagine a simple case where I have a function stringToThing from string->Thing. What I want to do is something like...

type a = {
  field: Yup.string().required().transform((v) => stringtoThing(v));
}

right now, the type of field would still be a string. usually if I want a parser to a custom type I need to use a Yup.mixed, but I'm not sure. I could certainly write one, but that feels like it would be hacky. something else that I could do would be something like...

type a = {
  field: castSchemaToType<Thing>(Yup.string().required().transform((v) => stringtoThing(v)));
}

and have a function that takes a Schema<string, B, C, D> and coerces it to a Schema<Thing, B, C, D>. But I'm not sure if that could have unexpected side-effects, since the underlying class at run time in this case for example would be a StringSchema (I believe)

any thoughts on the best way to achieve this? this sort of composition would be quite useful

@jquense
Copy link
Owner

jquense commented Mar 25, 2024

Schema describe the final type not the input. If you have, write a schema describing Thing and use a transform to turn the input into it

@jcoveney-anchorzero
Copy link
Author

well the thing is that sometimes it'd be nice to leverage a schema you already have...if an arbitrary schema<A> can be thought of as a function from unknown -> A | null, then if you need a schema<B> and have A -> B it'd be nice to leverage schema<A>, even if just as a function that "gets you almost there." essentially...

function yupMap<A, B extends {}>(schema: Yup.Schema<A>, fn: (a: A) => B): Yup.MixedSchema<B> {
  // might throw an exception
  const fn2 = (value: A) => fn(schema.validateSync(value));
  return Yup.mixed<B>((value): value is B => {
    try {
      fn2(value);
      return true;
    } catch (_) {
      return false;
    }
  })
    .transform((value: any, _input, ctx) => (!value || ctx.isType(value) ? value : fn2(value)))
    .required();
}

this is obviously a slightly crude attempt, but gets the point across. there are a number of reasons this isn't ideal, though you'd probably know even more! the main one I can think of is that invoking schema.validateSync() in this way doesn't pick up the configurations to your top level invocation. so like if this yupMap were used, then you use .validate(, {some args}), those args would not be used in that call to schema.validateSync()

still, I think it's pretty natural to want to be able to re-use the pieces in yup, or that we've already built...curious if you have thoughts on best practices for that. at this point we have a ton of yup validators which is why this sort of thing is becoming more and more desirable for us (a testament to yup's usefulness, for which we are very thankful!)

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

2 participants