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

"Maximum call stack size exceeded" when using compileAsync + circular references in some schemas #801

Closed
Hirntoasted opened this issue Jun 7, 2018 · 6 comments · Fixed by #804
Labels

Comments

@Hirntoasted
Copy link

Hi,
i'm working on a project where we want to validate json data based using various schema files that are interconnected via $refs.
My plan was to use compileAsync on a starting schema and load the referenced schemas by implementing a loadSchema function. However i run into Maximum call stack size exceeded errors when i execute the validation function.
Below is a (hopefully) minimal example of the schema structure causing the error (see Your code for the full example) .
Some additional notes about this:

  • i also tried manually loading the additional schemas (see validate() in the code example below) which seems to work, the error occured for me only when using compileAsync (see validateAsync())
  • the schemas foo and bar form a circular reference
  • for some reason, the reference to the seemingly unrelated other schema needs to be there to cause the error. Therefore i suspect that the circular reference itself is not the root cause
  • The other reference can also be moved to the bar schema for similar result

What version of Ajv are you using? Does the issue happen if you use the latest version?
6.5.0, yes

Ajv options object

{
    loadSchema: (uri) => {
      switch(uri) {
        case 'foo':
          return Promise.resolve(fooSchema);
        case 'bar':
          return Promise.resolve(barSchema);
        case 'other':
          return Promise.resolve(otherSchema);
      }
    }
  }

JSON Schema

const fooSchema = {
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "foo",
  "type": "object",
  "properties": {
    "bar": {
      "$ref": "bar"
    },
    "other": {
      "$ref": "other"
    }
  }
};

const barSchema = {
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "bar",
  "type": "object",
  "properties": {
    "foo": {
      "$ref": "foo"
    }
  }
};

const otherSchema = {
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "other"
};

const schema = {
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "list",
  "type": "object",
  "properties": {
    "foo": {
      "$ref": "foo"
    }
  }
};

Sample data

{
  "foo": {}
};

Your code

const ajv = require('ajv');

const fooSchema = {
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "foo",
  "type": "object",
  "properties": {
    "bar": {
      "$ref": "bar"
    },
    "other": {
      "$ref": "other"
    }
  }
};

const barSchema = {
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "bar",
  "type": "object",
  "properties": {
    "foo": {
      "$ref": "foo"
    }
  }
};

const otherSchema = {
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "other"
};

const schema = {
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "list",
  "type": "object",
  "properties": {
    "foo": {
      "$ref": "foo"
    }
  }
};

const data = {
  "foo": {}
};

function validate() {
  const validator = new ajv();

  validator.addSchema(fooSchema);
  validator.addSchema(barSchema);
  validator.addSchema(otherSchema);

  const validate = validator.compile(schema);
  console.log('sync', validate(data));
}

async function validateAsync() {
  const validator = new ajv({
    loadSchema: (uri) => {
      switch(uri) {
        case 'foo':
          return Promise.resolve(fooSchema);
        case 'bar':
          return Promise.resolve(barSchema);
        case 'other':
          return Promise.resolve(otherSchema);
      }
    }
  });

  try {
    const validate = await validator.compileAsync(schema);
    console.log('async', validate(data));
  } catch(error) {
    console.error('async', error);
  }
}

validate();
validateAsync();

Validation result, data AFTER validation, error messages

sync true
async RangeError: Maximum call stack size exceeded
    at callValidate (.../ajv/lib/ajv.js:1:1)
    at callValidate (.../ajv/lib/ajv.js:370:28)
    ...

What results did you expect?
compile() and compileAsync() should provide the same result - no call stack exceeded error should occure

Are you going to resolve the issue?
no sure - propably not...

@epoberezkin
Copy link
Member

@epoberezkin
Copy link
Member

@Hirntoasted thank you.

@epoberezkin
Copy link
Member

It does work though if properties that have non-recursive schemas are defined first (see runkit)... Not sure if it helps as a temp workaround in your case, needs to be fixed obviously.

@epoberezkin epoberezkin added the bug label Jun 9, 2018
@epoberezkin epoberezkin changed the title "Maximum call stack size exceeded" when using compileAsync + circular references in schemas "Maximum call stack size exceeded" when using compileAsync + circular references in some schemas Jun 9, 2018
epoberezkin added a commit that referenced this issue Jun 10, 2018
@epoberezkin
Copy link
Member

@Hirntoasted it's fixed in 6.5.1, please test with your real code.

@Hirntoasted
Copy link
Author

Works like a charm! Many thanks!

@epoberezkin
Copy link
Member

Thanks. compileAsync should be optimised as all it does, it tries to compile, blows up, checks what missing schema caused it to blow up, loads it and tries again... It definitely can be better than this.

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

Successfully merging a pull request may close this issue.

2 participants