Skip to content

Accept init shapes in Standard Schema validation#169

Open
Adrastopoulos wants to merge 1 commit into
bufbuild:mainfrom
Adrastopoulos:standard-schema-init-shapes
Open

Accept init shapes in Standard Schema validation#169
Adrastopoulos wants to merge 1 commit into
bufbuild:mainfrom
Adrastopoulos:standard-schema-init-shapes

Conversation

@Adrastopoulos

Copy link
Copy Markdown

Fixes #95.

createStandardSchema() only accepts message instances. A plain object fails before any rule is evaluated, even when the data is valid:

const schema = createStandardSchema(UserSchema);
await schema["~standard"].validate({ email: "test@example.com" });
// { issues: [{ message: "Cannot validate message undefined with schema example.User" }] }

Standard Schema consumers usually hold plain values, not message instances: form state, or deserialized RPC payloads (e.g. TanStack Start's inputValidator).

Changes:

  • validate() now converts plain objects to messages with create() before validating. Message instances are validated directly, as before. Errors thrown by create() become issues, since Standard Schema validators must not throw.
  • createStandardSchema() keeps its declared types. The Standard Schema input parameter is covariant, so widening it to MessageInitShape would break existing consumers, e.g. TanStack Form with defaultValues: create(Schema).
  • New createStandardSchemaInit() shares the runtime but declares MessageInitShape as its input, for consumers where the schema's input type dictates what callers pass. MessageInitShape is not assignable to MessageShape, so one input type cannot serve both.

All pre-existing tests pass unchanged. New tests cover the coercion, instance identity, and both type surfaces.

The Standard Schema validator previously required a message instance
created with create(). Plain objects failed with "Cannot validate
message undefined" before any rule was evaluated, even for valid data.

Standard Schema consumers usually hold plain values, not message
instances: form libraries validate form state, and RPC frameworks
validate deserialized JSON payloads. Requiring create() at every
callsite defeats the interop the interface exists for.

createStandardSchema now converts a plain object to a message with
create() before validating; message instances are still validated
directly. Its declared types are unchanged, so existing consumers,
such as TanStack Form with defaultValues: create(Schema), see no
type-level change.

The new createStandardSchemaInit shares the same runtime but declares
MessageInitShape as its input type, for consumers whose values are
plain objects: form state typed from plain default values, or RPC
input validation where the schema's input type dictates what callers
may pass.

Errors thrown by create() for malformed init values are reported as
issues instead of escaping the validator.

Fixes bufbuild#95.
@CLAassistant

CLAassistant commented Jul 11, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

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

Successfully merging this pull request may close these issues.

[Feature Request] Better integration with forms

2 participants