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

Error: Excessive stack depth comparing types 'ParseMixedSchema<?, T>' and 'ParseMixedSchema<?, T>'.ts(2321) #53

Closed
ffxsam opened this issue Jan 22, 2022 · 9 comments

Comments

@ffxsam
Copy link

ffxsam commented Jan 22, 2022

As of version 1.6.5, I now get this error in several places in my code.

Excessive stack depth comparing types 'ParseMixedSchema<?, T>' and 'ParseMixedSchema<?, T>'.ts(2321)

It comes from this:

import type { FromSchema } from 'json-schema-to-ts';
import createHttpError, { NamedConstructors } from 'http-errors';
import { URLSearchParams } from 'url';

export type ValidatedAPIGatewayProxyEvent<S> = APIGatewayProxyEvent &
  FromSchema<S>;

which I adopted from the Serverless Framework TS template.

I create a JS/JSON API Gateway schema, e.g.:

const schema = {
  type: 'object',
  properties: {
    body: {
      type: 'object',
      properties: {
        coupon_id: { type: 'string' },
        end_of_term: { type: 'boolean' },
        plan_id: { type: 'string' },
        subscription_id: { type: 'string' },
        reactivate: { type: 'boolean' },
        first_payment: { type: 'boolean' },
      },
      required: ['end_of_term', 'plan_id', 'subscription_id'],
      additionalProperties: false,
    },
  },
} as const;

and pass it to Sentry's wrapHandler:

export const handler = Sentry.AWSLambda.wrapHandler<
      ValidatedAPIGatewayProxyEvent<S>,
      APIGatewayProxyResult | undefined
    >(...

This has worked fine up until the latest release.

@ThomasAribart
Copy link
Owner

ThomasAribart commented Jan 22, 2022

Hi @ffxsam and thanks for the issue.

Everything seems fine on json-schema-to-ts side. It's the combination of Sentry and FromSchema that seems to generate too many type computations 🤔 I don't even get why ParseMixedSchema<?, T> is mentioned here, as your schema is not mixed (i.e. you do not provide an array of types but simply "object").

Can you try defining ValidatedAPIGatewayProxyEvent<S> in a separate type ?

type Event = ValidatedAPIGatewayProxyEvent<S>

export const handler = Sentry.AWSLambda.wrapHandler<
      Event,
      APIGatewayProxyResult | undefined
    >(...

...or simply, not providing explicitely the generic types to wrapHandler ? (they can be inferred from the handler itself, I think)

const yourFunc = async (event: ValidatedAPIGatewayProxyEvent<S>) : Promise<APIGatewayProxyResult | undefined> => ...

export const handler = Sentry.AWSLambda.wrapHandler(yourFunc)

What version of json-schema-to-ts did you use before having the error ? Was json-schema-to-ts the only library that you upgraded or did you upgrade Typescript as well ?

@ffxsam
Copy link
Author

ffxsam commented Jan 22, 2022

Hey @ThomasAribart, thanks for the reply!

You'll have to excuse me, as my TypeScript ability is passable at best.

type Event = ValidatedAPIGatewayProxyEvent<S>;

This results in an error, Cannot find name 'S'.

Unfortunately, I can't rely on implicit types here. The code for wrapApiHandler is more complex than illustrated here (it took me hours to figure out). I can post it in its entirety if that helps you.

TypeScript was updated, but I reverted it step by step back to version 4.5.2 and the issue still persisted. Then I reverted from json-schema-to-ts version 1.6.5 to 1.6.4 and that resolved it.

@ThomasAribart
Copy link
Owner

ThomasAribart commented Jan 24, 2022

S is supposed to be the type of your schema, defined with the as const statement:

import { schema } from './schema'

type S = typeof schema

@ffxsam
Copy link
Author

ffxsam commented Jan 25, 2022

Thanks, Thomas! I appreciate the help, but unfortunately, I don't have the bandwidth to try to track down what's going on. I'm guessing it's due to some sloppy TypeScript that json-schema-to-ts was previously more forgiving about. I'll pin my project to version 1.6.4 and call it a wrap.

@ffxsam ffxsam closed this as completed Jan 25, 2022
@daffl
Copy link

daffl commented Jan 25, 2022

I've been seein the same error in the latest version in https://github.com/feathersjs/feathers/tree/dove/packages/schema. The smallest example I could come up with to reproduce looks like this:

class Test<S extends JSONSchema> {
  readonly type: FromSchema<S>;
}

const schemaA = {
  type: 'object',
  additionalProperties: false,
  properties: {
    name: { type: 'string' }
  }
} as const;

const schemaB = {
  type: 'object',
  additionalProperties: false,
  properties: {
    name: { type: 'string' }
  }
} as const;

const t: Test<typeof schemaB> = new Test<typeof schemaA>();

Maybe that helps, I can try and do some more digging if you think it's worth investigating.
Either way, this is a great project!

@ericvicenti
Copy link

Oop, I made a duplicate issue here: #56

Filed here because I haven't seen this error on any other codebases aside from when I use json-schema-to-ts

But now I'm thinking we have exposed some bug in TypeScript.

@psznm
Copy link

psznm commented Jun 2, 2022

Typescript 4.7, which introduced variance annotations, has been released. I believe variance annotations could help solve this issue. And overall speed up type inference. @ThomasAribart, could you please look into that?

@ThomasAribart
Copy link
Owner

ThomasAribart commented Jul 26, 2022

@daffl @psznm @ffxsam @ericvicenti Indeed, it seems like the variance annotation helps a bit. The smallest example you provided is fixed by declaring S as invariant (i.e. with both in and out annotation).

I guess it skips the FromSchema type computation altogether and call it a day if schemaA and schemaB are equal (or not):

class Test<in out S extends JSONSchema> {
  readonly type?: FromSchema<S>;
}

const schemaA = {
  type: "object",
  additionalProperties: false,
  properties: {
    name: { type: "string" },
  },
} as const;

const schemaB = {
  type: "object",
  additionalProperties: false,
  properties: {
    name: { type: "string" },
  },
} as const;

// works fine
const test: Test<typeof schemaB> = new Test<typeof schemaA>();

However, I'm not completely sure this fixes the issue ? 🤔

I've found that defining the expected type as the default value of a second generic type is a functioning work-around though:

// "out" annotation is welcome but not necessary
class Test<S extends JSONSchema, out T = FromSchema<S>> {
  readonly type?: T;
}

const schemaA = {
  type: "object",
  additionalProperties: false,
  properties: {
    name: { type: "string" },
  },
  // this time, you can have different specs between schemas
  required: ['name']
} as const;

const schemaB = {
  type: "object",
  additionalProperties: false,
  properties: {
    name: { type: "string" },
  },
} as const;

// works fine
const test: Test<typeof schemaB> = new Test<typeof schemaA>();

Can you confirm that it work ? Does it close this issue ?

@ericvicenti
Copy link

I think this can be closed. I haven't seen this weirdo error in a while

Now I just need to find workarounds for "Type instantiation is excessively deep and possibly infinite" 😤

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

No branches or pull requests

5 participants