Skip to content

allOf lowers to v.intersect([v.strictObject, v.strictObject]), which rejects every value #18

@maxholman

Description

@maxholman

Version: 10.0.5 (also 10.0.3 / 10.0.4)
File: lib/valibot.ts:337-354 and :357-400

Summary

For an OpenAPI allOf whose members are objects, the valibot emitter writes v.intersect([strictObject(A), strictObject(B), …]). Because each strictObject independently rejects unknown keys, any value with properties from one member but not the other fails validation. With two disjoint-property members the schema rejects every possible value.

Minimal repro

OpenAPI (mirrors what @block65/openapi-constructs emits from Schema(..., { schema: { allOf: [...] } })):

components:
  schemas:
    Org:
      type: object
      required: [id, name]
      properties:
        id:   { type: string }
        name: { type: string }
    UserMembership:
      allOf:
        - $ref: '#/components/schemas/Org'
        - type: object
          required: [role]
          properties:
            role: { type: string }

Generated valibot.ts:

export const orgSchema = v.strictObject({ id: v.string(), name: v.string() });
export const userMembershipSchema = v.intersect([
  orgSchema,
  v.strictObject({ role: v.string() }),
]);

Calling v.parse(userMembershipSchema, { id: "o1", name: "Acme", role: "admin" }) throws:

ValiError: Invalid key: Expected never but received "role"
ValiError: Invalid key: Expected never but received "id"

The first strictObject rejects role; the second rejects id and name. There is no input that satisfies both.

Expected

allOf of object schemas should be merged into a single v.strictObject with the union of properties (and union of required). The OpenAPI spec defines allOf as schema composition (logical AND), and every other major OpenAPI→validator toolchain (zod-openapi, openapi-zod-client, redocly, etc.) collapses allOf of objects into one merged object before emitting the closed-object validator.

Actual

lib/valibot.ts:342 unconditionally picks intersect for allOf, then :400 emits each object member as a strictObject. The two are incompatible in valibot — v.intersect([strictObject, strictObject]) is only viable if the property sets are identical.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions