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

Type 'undefined' is not assignable to type 'Schema'.ts(2345) error with allOf+if/then+different properties #170

Closed
jason-curtis opened this issue Nov 22, 2023 · 9 comments · Fixed by #180

Comments

@jason-curtis
Copy link

jason-curtis commented Nov 22, 2023

Hi, it seems to me that the typing for Schemas is too strict in a particular case. Please let me know if this is a bug in my code or yours:

Repro

There are a lot of things going on in my schema but here is a reduced example. If propertyA is 1, then propertyB must be null. If propertyA is 2, then propertyC must be null. There are other properties present and validated at a higher level, but I'm leaving them out for this minimal example.

side note: I know this particular rule set can be converted to use oneOf, but there are other rules in the real schema that make allOf more straightforward.

{
  "type": "object",
  "allOf": [
    {
      "if": {
        "properties": {
          "propertyA": {
            "const": 1
          }
        }
      },
      "then": {
        "properties": {
          "propertyB": {
            "const": null
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "propertyA": {
            "const": 2
          }
        }
      },
      "then": {
        "properties": {
          "propertyC": {
            "const": null
          }
        }
      }
    }
  ]
}

From there, I'm just importing it and using validator:

import mySchema from '@/schemas/mySchema.json'
import { validator } from '@exodus/schemasafe';
const myValidator = validator(mySchema) // Typescript error here

here's the full typescript error:

Argument of type '{ type: string; allOf: ({ if: { properties: { propertyA: { const: number; }; }; }; then: { properties: { propertyB: { const: null; }; propertyC?: undefined; }; }; } | { if: { properties: { propertyA: { const: number; }; }; }; then: { ...; }; })[]; }' is not assignable to parameter of type 'Schema'.
  Type '{ type: string; allOf: ({ if: { properties: { propertyA: { const: number; }; }; }; then: { properties: { propertyB: { const: null; }; propertyC?: undefined; }; }; } | { if: { properties: { propertyA: { const: number; }; }; }; then: { ...; }; })[]; }' is not assignable to type '{ $schema?: string | undefined; $vocabulary?: string | undefined; id?: string | undefined; $id?: string | undefined; $anchor?: string | undefined; $ref?: string | undefined; definitions?: { ...; } | undefined; ... 54 more ...; discriminator?: { ...; } | undefined; }'.
    Types of property 'allOf' are incompatible.
      Type '({ if: { properties: { propertyA: { const: number; }; }; }; then: { properties: { propertyB: { const: null; }; propertyC?: undefined; }; }; } | { if: { properties: { propertyA: { const: number; }; }; }; then: { properties: { ...; }; }; })[]' is not assignable to type 'Schema[]'.
        Type '{ if: { properties: { propertyA: { const: number; }; }; }; then: { properties: { propertyB: { const: null; }; propertyC?: undefined; }; }; } | { if: { properties: { propertyA: { const: number; }; }; }; then: { properties: { ...; }; }; }' is not assignable to type 'Schema'.
          Type '{ if: { properties: { propertyA: { const: number; }; }; }; then: { properties: { propertyB: { const: null; }; propertyC?: undefined; }; }; }' is not assignable to type 'Schema'.
            Type '{ if: { properties: { propertyA: { const: number; }; }; }; then: { properties: { propertyB: { const: null; }; propertyC?: undefined; }; }; }' is not assignable to type '{ $schema?: string | undefined; $vocabulary?: string | undefined; id?: string | undefined; $id?: string | undefined; $anchor?: string | undefined; $ref?: string | undefined; definitions?: { ...; } | undefined; ... 54 more ...; discriminator?: { ...; } | undefined; }'.
              Types of property 'then' are incompatible.
                Type '{ properties: { propertyB: { const: null; }; propertyC?: undefined; }; }' is not assignable to type 'Schema | undefined'.
                  Type '{ properties: { propertyB: { const: null; }; propertyC?: undefined; }; }' is not assignable to type '{ $schema?: string | undefined; $vocabulary?: string | undefined; id?: string | undefined; $id?: string | undefined; $anchor?: string | undefined; $ref?: string | undefined; definitions?: { ...; } | undefined; ... 54 more ...; discriminator?: { ...; } | undefined; }'.
                    Types of property 'properties' are incompatible.
                      Type '{ propertyB: { const: null; }; propertyC?: undefined; }' is not assignable to type '{ [id: string]: Schema; }'.
                        Property '"propertyC"' is incompatible with index signature.
                          Type 'undefined' is not assignable to type 'Schema'.ts(2345)

It seems like schemasafe expects the same properties to be named in each sub-schema, but I don't see why that would be the case.

@ChALkeR
Copy link
Contributor

ChALkeR commented Nov 24, 2023

Hm. Explicitly specifying const mySchema: Schema = {...literal json...} works though.

I.e. this works:

const a: Schema = { ... }
const b: Schema = a

And this errors:

const a = { ... }
const b: Schema = a

@ChALkeR
Copy link
Contributor

ChALkeR commented Nov 24, 2023

Isolated example:

type A = B[]

type B = { [id: string]: boolean }

const x: A = [
  { "X": true },
  { "Y": false }
]

const y = [
  { "X": true },
  { "Y": false }
]

const z: A = y // fails

@jason-curtis
Copy link
Author

If I'm reading this correctly, it seems like a typescript bug, then?

As a workaround, it looks like I can declare my schema in a .ts file like so:

mySchema.ts (instead of mySchema.json)

import {Schema} from '@exodus/schemasafe';

const mySchema: Schema = {...schema goes here...};
export default mySchema;

then...

import mySchema from '@/schemas/mySchema'
import { validator } from '@exodus/schemasafe';
const myValidator = validator(mySchema); // no errors

@ChALkeR
Copy link
Contributor

ChALkeR commented Nov 25, 2023

I'm uncertain if this can be fixed on schemasafe side, yes.
And that workaround should work. I'll also test a JSON.parse variant shortly. see below

@ChALkeR
Copy link
Contributor

ChALkeR commented Nov 25, 2023

const s: Schema = JSON.parse("{...schema goes here...}'); also works, but typescript doesn't seem to provide any type validation in that case (at least by default).

@jason-curtis
Copy link
Author

jason-curtis commented Nov 27, 2023

Thanks. I'd really like to be able to just copy over my schema .json file over from elsewhere and import it directly. For now I have this working with ajv.

@ChALkeR
Copy link
Contributor

ChALkeR commented Nov 27, 2023

@jason-curtis Hm. I'm unsure if disabling ts typechecks for schemas would be a good solution to this.

@jason-curtis
Copy link
Author

yeah I don't think that would be a good option either. I'm able to work around the issue by casting to unknown but I'm hoping to have type checking support.

@ChALkeR
Copy link
Contributor

ChALkeR commented Nov 27, 2023

Afaik ajv just works this around by not typechecking schemas except for certain top-level properties like $schema...

I'll take another look

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

Successfully merging a pull request may close this issue.

2 participants