openapi-backend
openapi-backend copied to clipboard
TypeError: Converting circular structure to JSON
I'm trying to use a YAML file to create a mock. I'm using example "express-ts-mock" (https://github.com/anttiviljami/openapi-backend/blob/master/examples/express-ts-mock/index.ts)
but this is the result...
snakuzzo@laptop:~/workspace/openapi-backend/examples/express-ts-mock$ npm start
> [email protected] start /home/snakuzzo/workspace/openapi-backend/examples/express-ts-mock
> node dist/index.js
api listening at http://localhost:9000
(node:90915) UnhandledPromiseRejectionWarning: TypeError: Converting circular structure to JSON
at stringify (/home/snakuzzo/workspace/openapi-backend/examples/express-ts-mock/node_modules/fast-json-stable-stringify/index.js:42:19)
at stringify (/home/snakuzzo/workspace/openapi-backend/examples/express-ts-mock/node_modules/fast-json-stable-stringify/index.js:50:25)
at stringify (/home/snakuzzo/workspace/openapi-backend/examples/express-ts-mock/node_modules/fast-json-stable-stringify/index.js:50:25)
at stringify (/home/snakuzzo/workspace/openapi-backend/examples/express-ts-mock/node_modules/fast-json-stable-stringify/index.js:50:25)
at stringify (/home/snakuzzo/workspace/openapi-backend/examples/express-ts-mock/node_modules/fast-json-stable-stringify/index.js:50:25)
at stringify (/home/snakuzzo/workspace/openapi-backend/examples/express-ts-mock/node_modules/fast-json-stable-stringify/index.js:50:25)
at stringify (/home/snakuzzo/workspace/openapi-backend/examples/express-ts-mock/node_modules/fast-json-stable-stringify/index.js:50:25)
at stringify (/home/snakuzzo/workspace/openapi-backend/examples/express-ts-mock/node_modules/fast-json-stable-stringify/index.js:50:25)
at stringify (/home/snakuzzo/workspace/openapi-backend/examples/express-ts-mock/node_modules/fast-json-stable-stringify/index.js:50:25)
at stringify (/home/snakuzzo/workspace/openapi-backend/examples/express-ts-mock/node_modules/fast-json-stable-stringify/index.js:50:25)
(node:90915) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:90915) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Any suggestion?
Thanks
I'm also having having an issue when using a recursive structure in a parameter. The definitions compile to valid schema, but if I turn off parameter validation or remove the recursive elements of the parameter itself, I don't have an error. @snakuzzo are you dealing with a recursive structure somewhere?
@snakuzzo are you dealing with a recursive structure somewhere?
Yes I need to create a mock and specs are not mine. I cannot edit files, so I cannot remove recursive elements. How can I remove parameter validation ?
@snakuzzo you can set {validate: false} in your call to the OpenAPIBackend constructior. Note that this will not validate your requests before they execute your handlers.
I've determined the issue comes from validator.buildRequestValidatorsForOperation() wherein the schema is built and passed to ajv to be compiled. We are passing it a de-referenced object form of the schema, not using $refs to handle recursion.
In my case, i'm referring to a #/components/schemas from a #/components/parametersdefinition. In order to use $refs here, we would need to provide AJV access to the definitions we want to reference. My suggestion would to be to dump everything inside of the OpenAPI #/components/ into a new schema inside of 'definitions' and expose this schema to each of the validators. This way we can avoid de-referencing and let Ajv do it.
Useful link for de-referencing
I tried to pass JSON schema to ajv and, as you said, it correctly validate schema.
{validate: false} Is the only way ?
@anttiviljami is there no way to change this behavior?
EDIT: I can't skip validate :( @elby22 do you have an example of your solution ?
@snakuzzo if you have circular refs in your parameters (or probably anywhere Ajv is used for validation) and you absolutely must validate those params upon request, then I'm not sure there is a good solution at the moment. I'll be working on a solution later this week and make a PR eventually. In the mean time, you can can probably hack something so before validator.buildRequestValidatorsForOperation() hits validators.push(paramsValidator.compile(paramsSchema)); on line /src/validation.ts#L537, it does not compile a schema with a circular ref. There are probably a bunch of ways to do this which don't require modifying the source.
Let me know if you want to collaborate; I believe we have the same problem to solve.
I'd like to collaborate...but I don't know how :) I'm not so experienced
Hi @elby22...did you tried something ?!
Hey @snakuzzo, I've been working on other stuff lately. Might give it another shot today, but I will definitely get around to it at some point. Would you mind posting a sample of your schema so I can verify we have the same issue?
here it is...
openapi: 3.0.0
info:
title: 'My example'
version: '2.2'
servers:
- url: https://api.example.com/v3
paths:
'/myexample/{id}':
get:
tags:
- ID 20a
parameters:
- name: id
in: path
schema:
type: string
pattern: '^([A-Z0-9]{16}|[0-9]{11})$'
example: 'XXXXXXXXXXXXXXX'
required: true
- name: fields
in: query
schema:
type: array
items:
type: string
responses:
'200':
description: ok
headers:
datoEstratto:
schema:
type: string
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/myexample'
'500':
description: 'Generic error'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorMessage'
components:
schemas:
otherValues:
type: object
properties:
key:
type: string
nullable: true
value:
type: string
nullable: true
itemDetailValues:
type: object
properties:
name:
type: string
nullable: true
itValue:
type: array
items:
$ref: '#/components/schemas/otherValues'
itemDetail:
type: object
properties:
description:
type: string
nullable: true
itemValues:
type: array
items:
$ref: '#/components/schemas/itemDetailValues'
offer:
type: object
properties:
id:
type: string
nullable: true
myservice:
type: array
items:
$ref: '#/components/schemas/myservice'
myservice:
type: object
properties:
id:
type: string
nullable: true
example: "aaaaa"
item:
type: array
items:
$ref: '#/components/schemas/item'
item:
type: object
properties:
id:
type: string
nullable: true
example: 'aaaaaaaa'
itemDetail:
type: array
items:
$ref: '#/components/schemas/itemDetail'
item:
type: array
items:
$ref: '#/components/schemas/item'
myexample:
type: object
description: 'data'
required: ["id", "offer"]
properties:
id:
type: string
pattern: '^([A-Z0-9]{16}|[0-9]{11}):[0-9]+$'
example: 'XXXXXXXX:XXXXXXXXX'
offer:
type: array
items:
$ref: '#/components/schemas/offer'
ErrorMessage:
type: object
required:
- code
description: 'error 4xx 5xx'
properties:
code:
type: string
description: 'errorcode'
@anttiviljami I noticed we use SwaggerParser.dereference instead of SwaggerParser.bundle, which keeps references in-tact for this.definition. Without changing this, I'm not sure this library can support recursive schema definitions while using Ajv.
If we keep the references, it would then be possible for a user to implement their own ref-resolving using customizeAjv. Are there other considerations I didn't address here?
Hi guys... any news about this issue ?
@snakuzzo working on a PR now - trying to get it out by today.
@snakuzzo I've added a PR which should address this problem without re-writing too much of the library. Until it gets merged however, you may be able to solve the problem by using this in your constructor:
customizeAjv: (ajv, ajvOpts, ValidationContext) => {
const oldCompile = ajv.compile.bind(ajv);
ajv.compile = (schema) => {
const decycledSchema = decycle(schema);
return oldCompile(decycledSchema);
}
return ajv;
}
where decycle is a new function which you can find in the PR.
I will try ASAP. Thank you!!!
Hi @elby22. Am I getting this problem right?
- Ajv can handle circular schemas as long as they're represented with JSON Schema
$refs - We are calling SwaggerParser.dereference before passing the schemas to Ajv, which causes circular
$refs to be converted into circular JavaScript objects, which Ajv cannot handle - Your PR adds a new function
OpenAPIValidator.decycle, which converts any circular javascript structures back into$refs. This function then processes all schemas before passing them to Ajvs.
Just want to double check to make sure I'm understanding the problem correctly
And apologies to both of you @elby22 @snakuzzo for the delay with getting back to you. I've recently joined a new company, which (I hope) understandably is stretching my available time for OSS maintenance work 😔
@anttiviljami no problems with the delays - congrats on your new venture!
You are correct with my solution to the problem. Keeping the document dereferenced was necessary to handle our programatic use of the document. I had earlier attempts to just maintain $refs but there were too many edge cases where I actually wanted to dereference them.
@elby22 @anttiviljami Was this fix deployed? Just tested the above example and getting a range error when mocking?
RangeError: Maximum call stack size exceeded
at Date.valueOf (
Adding the decycle function to the mock operation resolved this
@anttiviljami I think we can close this bug due to the merged PR from above