speccy
speccy copied to clipboard
Passing "resolveInternal" option causes "RangeError" when resolving a spec
Detailed description
This might be a bug with oas-resolver but the front-door we are using to resolve the OpenApi specs is speccy. Resolving refs works fine with the following options
const SPECCY_OPTIONS = {
resolve: true,
jsonSchema: true,
}
The above options cause the first external ref to be resolved, but subsequent external refs to that same file get converted to internal refs.
We have a use case where we need all the refs to be resolved and not have any internal refs, so I used the following options
const SPECCY_OPTIONS = {
resolve: true,
jsonSchema: true,
resolveInternal: true,
}
This causes the following error -
RangeError: Maximum call stack size exceeded
at String.replace (<anonymous>)
at jpescape (/Users/mkothari/git/platform-specs/node_modules/reftools/lib/jptr.js:9:14)
at recurse (/Users/mkothari/git/platform-specs/node_modules/reftools/lib/recurse.js:34:60)
at recurse (/Users/mkothari/git/platform-specs/node_modules/reftools/lib/recurse.js:53:13)
at recurse (/Users/mkothari/git/platform-specs/node_modules/reftools/lib/recurse.js:53:13)
at recurse (/Users/mkothari/git/platform-specs/node_modules/reftools/lib/recurse.js:53:13)
at recurse (/Users/mkothari/git/platform-specs/node_modules/reftools/lib/recurse.js:53:13)
at recurse (/Users/mkothari/git/platform-specs/node_modules/reftools/lib/recurse.js:53:13)
at recurse (/Users/mkothari/git/platform-specs/node_modules/reftools/lib/recurse.js:53:13)
at recurse (/Users/mkothari/git/platform-specs/node_modules/reftools/lib/recurse.js:53:13)
- Node Version: 12.18.0
- Operating system and version (e.g. Ubuntu 16.04, Windows 7): Mac 10.15.5
- Speccy Version: 0.11.0
An additional note - this the schema does have recursion and is structured as given in the example from the json-schema docs - https://json-schema.org/understanding-json-schema/structuring.html#recursion
A quick example test case -
the yaml spec, say temp.yaml -
openapi: 3.0.2
info:
title: api
description: |
description!
contact:
name: 'team'
url: 'https://test.com/'
email: '[email protected]'
version: 0.0.1
servers:
- url: https://test.com
paths: {}
components:
responses:
Request:
description: request
content:
application/json:
schema:
$ref: temp.json#/definitions/person
tags: []
the schema in a file temp.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"person": {
"type": "object",
"properties": {
"name": { "type": "string" },
"children": {
"type": "array",
"items": { "$ref": "#/definitions/person" },
"default": []
}
}
}
},
"type": "object",
"properties": {
"person": { "$ref": "#/definitions/person" }
}
}
The command to run is - speccy resolve ./temp.yaml -j -v -i
Error -
GET ./packages/common-spec/src/specifications/components/temp.yaml
GET /Users/mkothari/git/common-specs/packages/common-spec/src/specifications/components/temp.json #/definitions/person
RangeError: Maximum call stack size exceeded
at String.replace (<anonymous>)
at jpescape (/Users/mkothari/git/common-specs/node_modules/reftools/lib/jptr.js:9:14)
at recurse (/Users/mkothari/git/common-specs/node_modules/reftools/lib/recurse.js:34:60)
at recurse (/Users/mkothari/git/common-specs/node_modules/reftools/lib/recurse.js:53:13)
at recurse (/Users/mkothari/git/common-specs/node_modules/reftools/lib/recurse.js:53:13)
at recurse (/Users/mkothari/git/common-specs/node_modules/reftools/lib/recurse.js:53:13)
at recurse (/Users/mkothari/git/common-specs/node_modules/reftools/lib/recurse.js:53:13)
at recurse (/Users/mkothari/git/common-specs/node_modules/reftools/lib/recurse.js:53:13)
at recurse (/Users/mkothari/git/common-specs/node_modules/reftools/lib/recurse.js:53:13)
at recurse (/Users/mkothari/git/common-specs/node_modules/reftools/lib/recurse.js:53:13)
Maximum call stack size exceeded
What is your expected (non infinite) output if all internal references are to be replaced?
That's a good question. IMO resolving it to the top level would suffice with a reference back to the object itself further down the tree.
This would be the desired output -
openapi: 3.0.2
info:
title: api
description: |
description!
contact:
name: team
url: https://test.com/
email: [email protected]
version: 0.0.1
servers:
- url: https://test.com
paths: {}
components:
responses:
Request:
description: request
content:
application/json:
schema:
type: object
properties:
name:
type: string
children:
type: array
items:
$ref: "#/components/responses/Request/content/application~1json/schema"
default: []
tags: []
That is exactly the output if you don't specifiy resolveInternal: true.
I think I simplified my test yaml too much without showcasing the actual use case I am looking to solve. If you refer to the same schema multiple times in the spec (which is what we are doing), as shown in the example below -
openapi: 3.0.2
info:
title: api
description: |
description!
contact:
name: 'team'
url: 'https://test.com/'
email: '[email protected]'
version: 0.0.1
servers:
- url: https://test.com
paths: {}
components:
responses:
Request:
description: request
content:
application/json:
schema:
$ref: temp.json#/definitions/person
Request2:
description: request
content:
application/json:
schema:
$ref: temp.json#/definitions/person
tags: []
if I don't specify resolveInternal: true then it results in this -
openapi: 3.0.2
info:
title: api
description: |
description!
contact:
name: team
url: https://test.com/
email: [email protected]
version: 0.0.1
servers:
- url: https://test.com
paths: {}
components:
responses:
Request:
description: request
content:
application/json:
schema:
type: object
properties:
name:
type: string
children:
type: array
items:
type: object
properties:
name:
type: string
children:
type: array
items:
$ref: "#/components/responses/Request/content/application~1json/schema/properti\
es/children/items"
default: []
default: []
Request2:
description: request
content:
application/json:
schema:
$ref: "#/components/responses/Request/content/application~1json/schema"
tags: []
It resolves it the first time, and then any future references point to the first place it was resolved.
Our goal was to have it resolve in all spots, which is why we added resovleInternal: true, which then resulted in the RangeError.