sinclairzx81 / typebox

Json Schema Type Builder with Static Type Resolution for TypeScript
Other
4.56k stars 148 forks source link

Nested `Type.Intersect` Errors #887

Open tristal opened 1 month ago

tristal commented 1 month ago

It seems that intersects are not propagating up errors from nested intersects when calling Value.Errors(T, value).

For example:

const A = Type.Intersect([
  Type.Object({
    id: Type.String(),
  }),
  Type.Object({
    x: Type.String(),
  }),
])
const B = Type.Intersect([
  Type.Object({
    id: Type.Number(),
  }),
  Type.Object({
    y: Type.String(),
  }),
])
const C = Type.Intersect([A, B])

Then doing Value.Errors call on both objects, yield:

[...Errors(A, {})].map((r) => console.log(`> ${r.path}: ${r.message}`));
// > : Expected all values to match
// > /id: Required property
// > : Expected all values to match
// > /x: Required property

[...Errors(B, {})].map((r) => console.log(`> ${r.path}: ${r.message}`));
// > : Expected all values to match
// > /id: Required property
// > : Expected all values to match
// > /y: Required property

[...Errors(C, {})].map((r) => console.log(`> ${r.path}: ${r.message}`));
// > : Expected all values to match
// > : Expected all values to match
// > : Expected all values to match
// > : Expected all values to match

Is this expected?

abdel commented 2 weeks ago

This also appears to affect Unions as well, following on from your example:

// Discriminated Union example

const A = Type.Object({
    a: Type.String(),
    b: Type.Optional(Type.Never()),

    c: Type.Number()
});

const B = Type.Object({
    a: Type.Optional(Type.Never()),
    b: Type.String(),

    c: Type.Number()
});

const C = Type.Union([A, B]);

In version 0.30, running Value.Errors on A, B and C gives the following:

# Value.Errors(A, {})
> /a: Expected string
> /c: Expected number
> /a: Expected required property
> /c: Expected required property

# Value.Errors(B, {})
> /b: Expected string
> /c: Expected number
> /b: Expected required property
> /c: Expected required property

# Value.Errors(C, {})
> : Expected value of union
> /a: Expected string
> /c: Expected number
> /a: Expected required property
> /c: Expected required property
> /b: Expected string
> /c: Expected number
> /b: Expected required property
> /c: Expected required property

In version 0.31 onwards, it instead gives the following output where the errors are no longer propagating:

# Value.Errors(A, {})
> /a: Required property
> /c: Required property
> /a: Expected string
> /c: Expected number

# Value.Errors(B, {})
> /b: Required property
> /c: Required property
> /b: Expected string
> /c: Expected number

# Value.Errors(C, {})
> : Expected union value