restate icon indicating copy to clipboard operation
restate copied to clipboard

Unable to invoke handler in UI with zod-serde that allows optional input

Open mupperton opened this issue 9 months ago • 3 comments

Using restate v1.2.2

Steps to reproduce:

  • Using TS SDK with zod serde module
  • Create handler that has an input serde using zod with type z.string().optional()
  • Register deployment
  • Try using UI Playground to invoke handler with no input

Result

{
  "message": "input validation error: Empty body not allowed"
}

If you do the exact same steps but with a handler using the normal JSON serde, there is no issue

If you instead try invoking the handler with the zod serde with a simple curl command using the GET HTTP verb, it also works (HTTP 200 and can see logs on the handler)

It only fails when doing a POST with a 'Content-Type: application/json' header, then the restate server seems to do some additional validation when the handler uses the zod serde and rejects before invoking the handler

The expected result would be that the handler gets invoked, because the zod schema allows undefined value as input

mupperton avatar Mar 26 '25 11:03 mupperton

On further digging, this actually appears to stem from the JSON Schema property that the zod-to-json-schema lib outputs

And I can reproduce on the standard JSON serde by manually setting the same JSON Schema with restate.serde.json.schema({ ... })

As it is not possible to represent undefined in a JSON Schema, and that it appears the restate server is using the JSON Schema found on discovery of the service to do some pre-validation before the invocation, so when using a JSON Schema at all then we just have to be mindful of optional inputs

So my only option appears to be changing the schema to be z.string().nullable() and then be forced to either provide a string or null, as no input still doesn't pass the JSON Schema pre-validation check, or put it inside an object so z.object({ value: z.string().optional() }) - which may end up being the preferred way for most people as it's more extensible if needed in the future

mupperton avatar Mar 26 '25 14:03 mupperton

Hey @mupperton after further discussion with @nikrooz we came to a similar conclusion. Shall we close this for now?

igalshilman avatar Mar 26 '25 15:03 igalshilman

Yeah let's close this issue as definitely not a restate-server issue

I've just thought of a possible "solution", which is to prevent the usage of optional zod types (the root type) in the zod serde, so TS complains if a dev tries that, to protect against this? Maybe this is overkill - would appreciate your thoughts

Possible implementation is to update the ZodSerde constructor to be

constructor(private readonly schema: T extends z.ZodOptional<z.ZodType> ? never : T) {

Then you get the error:

Image

Maybe there's some DX improvements that can be made to that, but I'll leave that with you if you want to explore, or if you just want that as-is then I could raise a PR, food for thought

mupperton avatar Mar 26 '25 16:03 mupperton