Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unevaluatedProperties failed verification when combined with Type.Intersect and Type.Union #889

Closed
eygsoft opened this issue Jun 6, 2024 · 3 comments
Labels

Comments

@eygsoft
Copy link

eygsoft commented Jun 6, 2024

First of all, thank you very much. Recently, there is a requirement similar to { a: string } & ({ b: string } | { c: string }):

TypeBox version: 0.32.31

import { Type, type Static } from '@sinclair/typebox'
import { Value } from '@sinclair/typebox/value'
import Ajv2019 from 'ajv/dist/2019'

export type T = Static<typeof T>
const T = Type.Intersect(
  [
    Type.Object({ a: Type.String() }),
    Type.Union([
      Type.Object({ b: Type.String() }),
      Type.Object({ c: Type.String() })
    ])
  ],
  { unevaluatedProperties: false }
)
const obj: T = { a: 'a', b: 'b' }

console.log(new Ajv2019().validate(T, obj)) // true
// https://www.jsonschemavalidator.net/ Also passed
console.log(Value.Check(T, obj)) // false, "Unexpected property"

/* The generated JsonScheme itself is not a problem
{
  "unevaluatedProperties": false,
  "type": "object",
  "allOf": [
    {
      "type": "object",
      "properties": { "a": { "type": "string" } },
      "required": ["a"]
    },
    {
      "anyOf": [
        {
          "type": "object",
          "properties": { "b": { "type": "string" } },
          "required": ["b"]
        },
        {
          "type": "object",
          "properties": { "c": { "type": "string" } },
          "required": ["c"]
        }
      ]
    }
  ]
}
*/

Finally, thank you again!

@sinclairzx81
Copy link
Owner

sinclairzx81 commented Jun 6, 2024

@eygsoft Hi! Thanks for reporting!

Yes, this looks like a bug. At this stage, I don't think I'll be able to take a look into the issue until the weekend at the earliest (as I am currently tied up with some other work), however you may be able to workaround this issue if you can reshape the schematics into the following.

const A = Type.Object({
 a: Type.String(),
 b: Type.String()
}, { additionalProperties: false })

const B = Type.Object({
 a: Type.String(),
 c: Type.String()
}, { additionalProperties: false })

const T = Type.Union([A, B]) // T = { a: string, b: string } | { a: string, c: string }

This issue actually crosses over with some other areas of the library where I'm looking to implement a distributive evaluate similar to the following....

TypeScript Link Here

type T = (
  { x: number }
) & (
  { y: number } |
  { z: number }
)

// non-distributive: where passing y | z is observed as unevaluated properties

type A = {[K in keyof T]: T[K]} & {} // type A = { x: number }

// distributive: where y | z are distributed with outer property x

type E<T> = {[K in keyof T]: T[K]} & {}

type B = E<T>                       // type B = { x: number; y: number; } | 
                                    //          { x: number; z: number; }

Note that TypeBox observes the validating schematic as A, where it should be observed as B. The validator should apply distributive union rules to the schematics (which has been challenging at a type level, but may be more tenable at a validation level)

Will keep you posted on any changes here.
Cheers
S

@eygsoft
Copy link
Author

eygsoft commented Jun 12, 2024

@eygsoft Hi! Thanks for reporting!

Yes, this looks like a bug. At this stage, I don't think I'll be able to take a look into the issue until the weekend at the earliest (as I am currently tied up with some other work), however you may be able to workaround this issue if you can reshape the schematics into the following.

const A = Type.Object({
 a: Type.String(),
 b: Type.String()
}, { additionalProperties: false })

const B = Type.Object({
 a: Type.String(),
 c: Type.String()
}, { additionalProperties: false })

const T = Type.Union([A, B]) // T = { a: string, b: string } | { a: string, c: string }

This issue actually crosses over with some other areas of the library where I'm looking to implement a distributive evaluate similar to the following....

TypeScript Link Here

type T = (
  { x: number }
) & (
  { y: number } |
  { z: number }
)

// non-distributive: where passing y | z is observed as unevaluated properties

type A = {[K in keyof T]: T[K]} & {} // type A = { x: number }

// distributive: where y | z are distributed with outer property x

type E<T> = {[K in keyof T]: T[K]} & {}

type B = E<T>                       // type B = { x: number; y: number; } | 
                                    //          { x: number; z: number; }

Note that TypeBox observes the validating schematic as A, where it should be observed as B. The validator should apply distributive union rules to the schematics (which has been challenging at a type level, but may be more tenable at a validation level)

Will keep you posted on any changes here. Cheers S

Thank you. The method I used is exactly the solution you mentioned, and it works very well. I just discovered this issue and I hope this great project can be more perfect. Thank you again!

@sinclairzx81
Copy link
Owner

@eygsoft Hiya,

Hey, going to close this one out as the workaround sounded like it solved the issue. A lot of this work fits under the umbrella of evaluated types which is due before the end of the year (hopefully). This should provide better options for distributive union types inline with the types mentioned on this issue. Will be good when it lands :)

Will close up for now.
All the best!
S

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants