zod icon indicating copy to clipboard operation
zod copied to clipboard

variadic args function

Open hyusetiawan opened this issue 3 years ago • 2 comments

I am trying to define function of this shape:

return z
    .function()
    .args(...args , makeAPISchema(valueSchema))
    .returns(returns)

where makeAPISChema returns a z.object but it keeps throwing the following error: Variadic element at position 0 in source does not match element at position 0 in target.

Is there a way to make this work?

hyusetiawan avatar Jul 23 '22 21:07 hyusetiawan

You can use the .rest method on ZodTuple to add variadic arguments to a function schema.

z.function()
    .args(z.tuple([z.string()]).rest(z.number()))
    .returns(z.number())
    .implement((args) => {
      return args[1]; // [string, ...number[]]
    });

colinhacks avatar Sep 06 '22 07:09 colinhacks

@colinhacks, I don't really feel like this issue was resolved. Can you let me know what I'm doing wrong?

const foo = z.function()
    .args(z.tuple([z.string()]).rest(z.number()))
    .returns(z.number())
    .implement((args) => {
      return args[1] // [string, ...number[]]
    })
console.log(
    foo('bar',1,2,3)
//      ^^^^^
// Argument of type 'string' is not assignable to parameter of type '[string, ...number[]]'.
)

JacobWeisenburger avatar Dec 15 '22 14:12 JacobWeisenburger

@JacobWeisenburger you're correct - the suggestion with z.tuple() makes it so we expect an array with the original parameters which is likely not what @hyusetiawan intends.

I think here unfortunately there's a "limitation" in the sense that the expected explicit type is '[] | [ZodTypeAny, ...ZodTypeAny[]]' from ZodTuple and seems foundational for the type. The issue here is a spread operator without explicit typing ends up being of the form ZodTypeAny[] and that is not compatible (the other type requires at least one explicit element)

type requiredType = [ZodTypeAny, ...ZodTypeAny[]];
const wellTyped: [z.ZodString, ...z.ZodString[]] = [z.string(), z.string()];
const untyped = [z.string(), z.string()];

const tryFirst: requiredType = wellTyped; // OK
const trySecond: requiredType = untyped; // error: Type 'ZodString[]' is not assignable to type 'requiredType'.

we could try expanding the definitions on ZodTuple but it seems more work than just adding a couple of definitions. I think in theory spread should "just work" but as I see it:

  • a user can use spread, but needs to type the array being spread to guarantee at least one element or none to satisfy the current type
  • we can look at rejigging the types in ZodTuple but seems awkward given the way one has to declare "at least one element in array" specific types
// this works
const possibleInputs: [] | [z.ZodString, ...Array<z.ZodString>] = [
  z.string(),
  z.string(),
];

const foo = z
  .function()
  // .args()
  .args(...possibleInputs)
  .returns(z.string())
  .implement((args) => {
    return args[0];
  });

console.log(foo("bar", "faz"));

Open to other ideas!

maxArturo avatar Jan 07 '23 20:01 maxArturo

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jul 09 '23 04:07 stale[bot]