From 035040c33af6e6dc5fd309a07c5876ffffc5073d Mon Sep 17 00:00:00 2001 From: Hans Klunder Date: Mon, 13 Nov 2023 19:56:56 +0100 Subject: [PATCH] fix: recursive refs work during validation as well --- CHANGELOG.md | 4 ++ resolve.js | 13 ++-- test/test-validation-refs.js | 7 +++ test/test-validation.js | 6 ++ test/validation/openapi-recursive.v3.json | 76 +++++++++++++++++++++++ 5 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 test/validation/openapi-recursive.v3.json diff --git a/CHANGELOG.md b/CHANGELOG.md index fa24a54..51cabad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] ### Changed + +## [v2.1.4] 13-11-2023 +### Changed + - fix: recursive refs work during validation as well - fix: update index.d.ts to include single string errors on validate ## [v2.1.3] 12-11-2023 diff --git a/resolve.js b/resolve.js index 612e7ed..6077093 100644 --- a/resolve.js +++ b/resolve.js @@ -75,6 +75,10 @@ function resolve(tree, replace) { return undefined; } + if (replace === false) { + treeObj = structuredClone(tree); + } + const pointers = {}; for (const word of pointerWords) { pointers[word] = []; @@ -147,10 +151,7 @@ function resolve(tree, replace) { const { ref, id, path } = item; const decodedRef = decodeURIComponent(ref); const fullRef = decodedRef[0] !== "#" ? decodedRef : `${id}${decodedRef}`; - const uri = resolveUri(fullRef, anchors); - if (replace) { - applyRef(path, uri); - } + applyRef(path, resolveUri(fullRef, anchors)); } for (const item of pointers.$dynamicRef) { @@ -158,9 +159,7 @@ function resolve(tree, replace) { if (!dynamicAnchors[ref]) { throw new Error(`Can't resolve $dynamicAnchor : '${ref}'`); } - if (replace) { - applyRef(path, dynamicAnchors[ref]); - } + applyRef(path, dynamicAnchors[ref]); } return treeObj; diff --git a/test/test-validation-refs.js b/test/test-validation-refs.js index cf79877..7688c94 100644 --- a/test/test-validation-refs.js +++ b/test/test-validation-refs.js @@ -8,6 +8,7 @@ function localFile(fileName) { } const invalidRefsSpec = localFile("./validation/invalid-refs.yaml"); +const recursiveRefsSpec = localFile("./validation/openapi-recursive.v3.json"); test("invalid refs in YAML fail validation", async (t) => { const validator = new Validator(); @@ -19,3 +20,9 @@ test("invalid refs in YAML fail validation", async (t) => { "correct error message", ); }); + +test("recursive refs pass validation", async (t) => { + const validator = new Validator(); + const res = await validator.validate(recursiveRefsSpec); + assert.equal(res.valid, true, "validation succeeds"); +}); diff --git a/test/test-validation.js b/test/test-validation.js index 1af3d0b..cc65f2f 100644 --- a/test/test-validation.js +++ b/test/test-validation.js @@ -26,6 +26,7 @@ const inlinedRefs = "x-inlined-refs"; async function testVersion(version) { test(`version ${version} works`, async (t) => { const petStoreSpec = importJSON(`./v${version}/petstore.json`); + const copyPetStoreSpec = structuredClone(petStoreSpec); const validator = new Validator(); const res = await validator.validate(petStoreSpec); @@ -36,6 +37,11 @@ async function testVersion(version) { version, "petstore spec version matches expected version", ); + assert.deepEqual( + petStoreSpec, + copyPetStoreSpec, + "original petstore spec is not modified", + ); }); } diff --git a/test/validation/openapi-recursive.v3.json b/test/validation/openapi-recursive.v3.json new file mode 100644 index 0000000..06b69a6 --- /dev/null +++ b/test/validation/openapi-recursive.v3.json @@ -0,0 +1,76 @@ +{ + "openapi": "3.0.0", + "servers": [ + { + "url": "http://localhost/v2" + } + ], + "info": { + "title": "Recursive Test specification", + "description": "testing the fastify openapi glue using recursive schema", + "version": "0.1.0" + }, + "paths": { + "/recursive": { + "post": { + "operationId": "postRecursive", + "summary": "Test recursive parameters", + "responses": { + "200": { + "description": "ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bodyObject" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bodyObject" + } + } + }, + "required": true + } + } + } + }, + + "components": { + "schemas": { + "bodyObject": { + "type": "object", + "additionalProperties": false, + "properties": { + "str1": { + "type": "string" + }, + "arr": { + "type": "array", + "items": { + "type": "string" + } + }, + "objRef": { + "$ref": "#/components/schemas/bodyObject" + }, + "arrRef": { + "type": "array", + "items": { + "$ref": "#/components/schemas/bodyObject" + } + }, + "refStr": { + "$ref": "#/components/schemas/bodyObject/properties/arrRef/items/properties/arr/items" + } + }, + "required": ["str1"] + } + } + } +}