From 3de026944ad1af2210678da5f3ac1133d3cdf6a7 Mon Sep 17 00:00:00 2001 From: roberthebel Date: Mon, 26 Oct 2026 23:54:55 +0100 Subject: [PATCH 1/8] chore(deps): bump ApiDOM to 1.13.3 and add JSON Schema 2020-12 deps Move every @swagger-api/apidom-* package to 1.13.3, add @swagger-api/apidom-ns-json-schema-2020-12 to dependencies (the JSON Schema 2020-12 resolve strategy imports it) and its two parser adapters to optionalDependencies, mirroring apidom-reference. Co-Authored-By: Claude Opus 5.5 --- package-lock.json | 597 ++++++++++++++++++++++++---------------------- package.json | 57 ++--- 2 files changed, 346 insertions(+), 308 deletions(-) diff --git a/package-lock.json b/package-lock.json index 74fc30fa9..d5763ebe6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,12 +11,13 @@ "dependencies": { "@babel/runtime-corejs3": "^7.22.15", "@scarf/scarf": "=1.4.0", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-error": "^1.12.1", - "@swagger-api/apidom-json-pointer": "^1.12.1", - "@swagger-api/apidom-ns-openapi-3-1": "^1.12.1", - "@swagger-api/apidom-ns-openapi-3-2": "^1.12.1", - "@swagger-api/apidom-reference": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-error": "^1.13.3", + "@swagger-api/apidom-json-pointer": "^1.13.3", + "@swagger-api/apidom-ns-json-schema-2020-12": "^1.13.3", + "@swagger-api/apidom-ns-openapi-3-1": "^1.13.3", + "@swagger-api/apidom-ns-openapi-3-2": "^1.13.3", + "@swagger-api/apidom-reference": "^1.13.3", "@swaggerexpert/cookie": "^2.0.2", "deepmerge": "~4.3.0", "fast-json-patch": "^3.0.0-1", @@ -70,27 +71,29 @@ "node": ">=22" }, "optionalDependencies": { - "@swagger-api/apidom-ns-asyncapi-2": "^1.12.1", - "@swagger-api/apidom-ns-asyncapi-3": "^1.12.1", - "@swagger-api/apidom-ns-openapi-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^1.12.1", - "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^1.12.1", - "@swagger-api/apidom-parser-adapter-arazzo-json-1": "^1.12.1", - "@swagger-api/apidom-parser-adapter-arazzo-yaml-1": "^1.12.1", - "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-asyncapi-json-3": "^1.12.1", - "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": "^1.12.1", - "@swagger-api/apidom-parser-adapter-json": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-json-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-json-3-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.1" + "@swagger-api/apidom-ns-asyncapi-2": "^1.13.3", + "@swagger-api/apidom-ns-asyncapi-3": "^1.13.3", + "@swagger-api/apidom-ns-openapi-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^1.13.3", + "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^1.13.3", + "@swagger-api/apidom-parser-adapter-arazzo-json-1": "^1.13.3", + "@swagger-api/apidom-parser-adapter-arazzo-yaml-1": "^1.13.3", + "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-asyncapi-json-3": "^1.13.3", + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": "^1.13.3", + "@swagger-api/apidom-parser-adapter-json": "^1.13.3", + "@swagger-api/apidom-parser-adapter-json-schema-json-2020-12": "^1.13.3", + "@swagger-api/apidom-parser-adapter-json-schema-yaml-2020-12": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-json-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-json-3-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.13.3" } }, "node_modules/@asamuzakjp/css-color": { @@ -3681,13 +3684,13 @@ } }, "node_modules/@swagger-api/apidom-ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.12.1.tgz", - "integrity": "sha512-h1wIHNZ+4eQ00aXgX5VsTFLyi/9JWywK2CNOfZ3xzEfi9Qh/sDNHB9i4pbaGdlE8oxZa8HwqPl7+6M0fLCXfbA==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.13.3.tgz", + "integrity": "sha512-5722GHs+iLl4F5EbyReh+d0XEmG6t4F1BRTPEjHBrmfboBICi7u1cwwZ2wsCskQl0TehEl3RX1ZJWPmpkzjQFw==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-error": "^1.12.1", + "@swagger-api/apidom-error": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -3695,14 +3698,14 @@ } }, "node_modules/@swagger-api/apidom-core": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-1.12.1.tgz", - "integrity": "sha512-aBTAjJ0YzxFccXe5kB3nnVuHxRyPYEaE8eLEs/kJFmbv8UdMji1AVnNSk61MfJ4nodyoMYLfp6AoPa9fWXFgiQ==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-1.13.3.tgz", + "integrity": "sha512-Sgd8pWucD2vs055koRTrRG1zH+pj9VXdyLtjKKC/iML6eAFgd0lmLccK2aFNGt2/36olIzNODeRMTPRnufZgCA==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-ast": "^1.12.1", - "@swagger-api/apidom-error": "^1.12.1", + "@swagger-api/apidom-ast": "^1.13.3", + "@swagger-api/apidom-error": "^1.13.3", "@types/ramda": "~0.30.0", "minim": "~0.23.8", "ramda": "~0.30.0", @@ -3712,35 +3715,35 @@ } }, "node_modules/@swagger-api/apidom-error": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-error/-/apidom-error-1.12.1.tgz", - "integrity": "sha512-y44fOaleXZGIEX+MIUIksA704naPjhvpHEMfadWY/D9DW2JTdwv2edpyZEFjyK1jwchp/b3aQ+XG9MDvfwb8HQ==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-error/-/apidom-error-1.13.3.tgz", + "integrity": "sha512-5erobaeuRu5rAwcL/ljE3jGuX/ILENq3zLU8rZCV0/rvlc5vuGGhyowt8UmI79L7y+vifYFs6d+kcfn/di0T6A==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.20.7" } }, "node_modules/@swagger-api/apidom-json-pointer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-1.12.1.tgz", - "integrity": "sha512-EZmwpku1eMsc7Lvdz0sm/XHkRAmj7FpMwfVm1MfoXiljhPrEVHH6FTZmM3QXaprkOeDMkBTDGtIv/LgYOq/b+w==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-1.13.3.tgz", + "integrity": "sha512-eQaMH3HnR5eElsBQc5IueDsZoK76uCnW78JZDP2uLDTq3Lqx0hzUyFnZUSwY/XrLmfcLWtJiSABezEU/DcfJBg==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-error": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-error": "^1.13.3", "@swaggerexpert/json-pointer": "^2.10.1" } }, "node_modules/@swagger-api/apidom-ns-a2a-1": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-a2a-1/-/apidom-ns-a2a-1-1.12.1.tgz", - "integrity": "sha512-/HjUqcV8XpUQET/P5fqVCsm9ZXpeYwHpKVJtv+pIPDF1Zk5thHCKgyC3qIM+/ZGBDqt6MVrhpyrYIvEHFyd3dQ==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-a2a-1/-/apidom-ns-a2a-1-1.13.3.tgz", + "integrity": "sha512-TZEmV3E2Hji9dDm7e1wAckMnqFGdxnFSrbDtwKbubSfYgshSMqx0Vc3mQu/IsigqSmZZLWgmOI9iq737SqraZQ==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -3748,16 +3751,16 @@ } }, "node_modules/@swagger-api/apidom-ns-api-design-systems": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-1.12.1.tgz", - "integrity": "sha512-Va3ljpxF8Ti/cMxD8/JdFtEdVg8hxwZ8/1TsUNwcE12qLu9o95hHsqmm8/OlKSqfSpO3KG/QT1TIKmQsl7cAsQ==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-1.13.3.tgz", + "integrity": "sha512-00aflYATDo1RbkLNvAq/lS3aEZHfCKFwBZbSSvoLAXsIguzKYCCIjm5F+4fWLIiToL7junHvnUuS9PHTMre7bQ==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-error": "^1.12.1", - "@swagger-api/apidom-ns-openapi-3-1": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-error": "^1.13.3", + "@swagger-api/apidom-ns-openapi-3-1": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -3765,15 +3768,15 @@ } }, "node_modules/@swagger-api/apidom-ns-arazzo-1": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-arazzo-1/-/apidom-ns-arazzo-1-1.12.1.tgz", - "integrity": "sha512-0G+KnlXb9QVJGC/tJLjkv7oiFBl45xAd8CM9dwwvnG7vBFJZGOq04qs8S8oCuA94ugSvdmave88IA03SEM7FWg==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-arazzo-1/-/apidom-ns-arazzo-1-1.13.3.tgz", + "integrity": "sha512-42apSY7ttFRHIX7JDCH494IGwd6AhS7Owj4ftdfzhZUj1ZQU8YcXzunmlwig4tw1RyOR3hBzH51h0uvWO6M2+w==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-ns-json-schema-2020-12": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-json-schema-2020-12": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -3781,15 +3784,15 @@ } }, "node_modules/@swagger-api/apidom-ns-asyncapi-2": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-1.12.1.tgz", - "integrity": "sha512-GQUrBTz74usl88dFrrhRH1btc4sinJ0vb9bnFfBOuQBF0MYvVJ67Vsx1cz6iIfS4Y/2+x63WX5krJlmKavUQ9w==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-1.13.3.tgz", + "integrity": "sha512-TrUYjBi8obSkt30/rqmDS9mNepoPbJCF1zrnMVKx2EmYMqq3S35ZRC+PJSqCDCZ7VlVm8oE4/jtRm1LCmCPFvg==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-ns-json-schema-draft-7": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-json-schema-draft-7": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -3797,15 +3800,15 @@ } }, "node_modules/@swagger-api/apidom-ns-asyncapi-3": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-3/-/apidom-ns-asyncapi-3-1.12.1.tgz", - "integrity": "sha512-lPJd/zXcCv2zBAjMfxHDbiC0xQD7cpztD403mh74unGsVN9Xj+aBfGMZ9qsh+UdMYhxk6Ad2C4esYkROB2Awqw==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-3/-/apidom-ns-asyncapi-3-1.13.3.tgz", + "integrity": "sha512-uN8dK3zUS0rR2uWgeH7X2AGVzsupBjJqz3jebkneTdPm9r8xmJuKhPboXiafrveqv0V9BGFUcMSn9S9FPyD1Cg==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-ns-asyncapi-2": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-asyncapi-2": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -3813,15 +3816,15 @@ } }, "node_modules/@swagger-api/apidom-ns-json-schema-2019-09": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-2019-09/-/apidom-ns-json-schema-2019-09-1.12.1.tgz", - "integrity": "sha512-HlzTVQZV1d2twOGcZd7h2LUm/KAaWy0il1Bcwy6pGkdyHcP3lm85xgSbeGuJgn0sE9j0IuZrMuMMwAzcqhMAGA==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-2019-09/-/apidom-ns-json-schema-2019-09-1.13.3.tgz", + "integrity": "sha512-W0P/yN5ZDxjEFb2W+f7NK1XokhBxXvfhWVSPoze7K+bF4ekcB2qkfxAdsI38FWvlxQGL6TQETxQna9Lp1d7blw==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-error": "^1.12.1", - "@swagger-api/apidom-ns-json-schema-draft-7": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-error": "^1.13.3", + "@swagger-api/apidom-ns-json-schema-draft-7": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -3829,15 +3832,15 @@ } }, "node_modules/@swagger-api/apidom-ns-json-schema-2020-12": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-2020-12/-/apidom-ns-json-schema-2020-12-1.12.1.tgz", - "integrity": "sha512-0C5I+0MwE/fPueRoHnbFjUF2v1I85PRRUtlmifRM9NZgbCPJ6X/u5TDhllDmu0UDnvFjrSAGMvS/vw+i77qtdA==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-2020-12/-/apidom-ns-json-schema-2020-12-1.13.3.tgz", + "integrity": "sha512-fF5Wqvmb2vvx0o0Tr5Vcz+Q6gedVi7oGeKJWaSBA7WJ0awUz+PCIW3OPiKhcxdVW2/Zrxd5kMELDCmarnHS65Q==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-error": "^1.12.1", - "@swagger-api/apidom-ns-json-schema-2019-09": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-error": "^1.13.3", + "@swagger-api/apidom-ns-json-schema-2019-09": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -3845,14 +3848,14 @@ } }, "node_modules/@swagger-api/apidom-ns-json-schema-draft-4": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.12.1.tgz", - "integrity": "sha512-szwBlRH0jey/FtJ25f8k3rVUuQFg66c9FyLozZzEkMu5MFFGcl/NK2AZ9TRATmcIrpNFuK8FSz9PaPmNEUbnMw==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.13.3.tgz", + "integrity": "sha512-8jh6dhDKskIPP/76aIMlNpCHf5oHHyMuAe4FL2nw3fat3GOq/l0ea0a/6E1tjUJLokAaES0sWTkxYgNRWMBrBg==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-ast": "^1.12.1", - "@swagger-api/apidom-core": "^1.12.1", + "@swagger-api/apidom-ast": "^1.13.3", + "@swagger-api/apidom-core": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -3860,15 +3863,15 @@ } }, "node_modules/@swagger-api/apidom-ns-json-schema-draft-6": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-1.12.1.tgz", - "integrity": "sha512-HvV5NHiqGNLI4M3BS1RJcRyxVCK4jJd4T2UaSKhZOezy3MaKguGI8oDgwix8eyw+6OwhxuXNQpRMobYb+WaseQ==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-1.13.3.tgz", + "integrity": "sha512-axUur9XmKCi42WSGceBXKwnqPTMMVF+0Etaksazo5j7ydWLEM0J1EyeeE7uZlpaoIA1/ev2m09j2v1LNOc6NYg==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-error": "^1.12.1", - "@swagger-api/apidom-ns-json-schema-draft-4": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-error": "^1.13.3", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -3876,15 +3879,15 @@ } }, "node_modules/@swagger-api/apidom-ns-json-schema-draft-7": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-1.12.1.tgz", - "integrity": "sha512-t7LqogFYGN1ocxFTkJIXsqM4M66WAWIFW/yBbaGOO//OiuVuRe5wVXzpbJttT3cA2peotQbpKxlBL/GuvxWguA==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-1.13.3.tgz", + "integrity": "sha512-AyIMoer1bFhn3ULeEe6EMQDcD4b4JiTWQMvS/qDUoQqY6fCLPu6er/6cxcjcgTgf8L86aG6PhPxucnYDWr0DFQ==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-error": "^1.12.1", - "@swagger-api/apidom-ns-json-schema-draft-6": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-error": "^1.13.3", + "@swagger-api/apidom-ns-json-schema-draft-6": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -3892,16 +3895,16 @@ } }, "node_modules/@swagger-api/apidom-ns-openapi-2": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-1.12.1.tgz", - "integrity": "sha512-v93DUAk5hehbsp81kJKwbcJbWBVFXcdBqZy8G8Lbo6QeCar1/dP6CWItbn8xArZmG3PeYkZK77v903HEmLxghA==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-1.13.3.tgz", + "integrity": "sha512-ndrNoyETfWPOvoqNIQ19yoYGVhXlrLCK2xanTn+IGJ4hg/9vK2Le4Qmiv1O/E90hODs5JPx1WuZBRQikKOLW8Q==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-error": "^1.12.1", - "@swagger-api/apidom-ns-json-schema-draft-4": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-error": "^1.13.3", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -3909,15 +3912,15 @@ } }, "node_modules/@swagger-api/apidom-ns-openapi-3-0": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-1.12.1.tgz", - "integrity": "sha512-EO2c552sMDtZXllLSQVI5u1+gpo654LW1apmi9YvblCrRek2Gyejoj3otBfo9IFdBEuczbt+Rll3hRnZx3aqeA==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-1.13.3.tgz", + "integrity": "sha512-emLzxkwOb/RCenwZ6DRukuTAWwwqcM6a76gpHL8rq9Ax8AEbmKB0Ex25tqc/HWXRRTOioylSuotluUvHcRqXEQ==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-error": "^1.12.1", - "@swagger-api/apidom-ns-json-schema-draft-4": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-error": "^1.13.3", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -3925,17 +3928,17 @@ } }, "node_modules/@swagger-api/apidom-ns-openapi-3-1": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-1.12.1.tgz", - "integrity": "sha512-qDJ6rGUTMLQ5MHWqAQ+3/+VGBjS7je5pYkP3hiF2AeXTESh1/MOjPICxDWE6XDwcvkVOIcXX+xEDt7ulnzvrtA==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-1.13.3.tgz", + "integrity": "sha512-Oh1lonFFB1sEATek6e18efUMNG+ttXWrRHrblJtVMx7cUelP1BTvBt4ykrvlnXw4EoG8bIHwLtD6yVgCX1mcpw==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-ast": "^1.12.1", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-json-pointer": "^1.12.1", - "@swagger-api/apidom-ns-json-schema-2020-12": "^1.12.1", - "@swagger-api/apidom-ns-openapi-3-0": "^1.12.1", + "@swagger-api/apidom-ast": "^1.13.3", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-json-pointer": "^1.13.3", + "@swagger-api/apidom-ns-json-schema-2020-12": "^1.13.3", + "@swagger-api/apidom-ns-openapi-3-0": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -3943,18 +3946,18 @@ } }, "node_modules/@swagger-api/apidom-ns-openapi-3-2": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-2/-/apidom-ns-openapi-3-2-1.12.1.tgz", - "integrity": "sha512-H0VmiegQhWc+53xypcxS3T7qUVcfJKX+DYmiPx9FZvvSOp77Ctvc2/RtztGQ5531oFWDWtPt0Ad0VwxmEcfL8w==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-2/-/apidom-ns-openapi-3-2-1.13.3.tgz", + "integrity": "sha512-1wUZvT2E71WorxQDy9zxrq2GJud7wUO6r/oDPTldnRiagAe/KXyLyRbucxeaLqh417wCg2ySGyClZVUT5QwE9g==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-ast": "^1.12.1", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-json-pointer": "^1.12.1", - "@swagger-api/apidom-ns-json-schema-2020-12": "^1.12.1", - "@swagger-api/apidom-ns-openapi-3-0": "^1.12.1", - "@swagger-api/apidom-ns-openapi-3-1": "^1.12.1", + "@swagger-api/apidom-ast": "^1.13.3", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-json-pointer": "^1.13.3", + "@swagger-api/apidom-ns-json-schema-2020-12": "^1.13.3", + "@swagger-api/apidom-ns-openapi-3-0": "^1.13.3", + "@swagger-api/apidom-ns-openapi-3-1": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -3962,176 +3965,176 @@ } }, "node_modules/@swagger-api/apidom-parser-adapter-a2a-json-1": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-a2a-json-1/-/apidom-parser-adapter-a2a-json-1-1.12.1.tgz", - "integrity": "sha512-4Bp56QdMOmv6pkhmDExFlzHHjtB3CqW4rjyo+QcnhiWBmB3qbsS4/rHEQVGNlGtUC+ze0+LbAI4pycAnqF88dQ==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-a2a-json-1/-/apidom-parser-adapter-a2a-json-1-1.13.3.tgz", + "integrity": "sha512-v1GCfvk/goJBlroHSW5gVHWI0OgHG3HfsfHR6XlWwronJaaTq/Adz7yE/rNyjC+Wfmi7VVhuNoWvOUuWjbWr3w==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-ns-a2a-1": "^1.12.1", - "@swagger-api/apidom-parser-adapter-json": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-a2a-1": "^1.13.3", + "@swagger-api/apidom-parser-adapter-json": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-a2a-yaml-1": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-a2a-yaml-1/-/apidom-parser-adapter-a2a-yaml-1-1.12.1.tgz", - "integrity": "sha512-GsQogmK2vUQXOIImFFSoKnhiJDVOm2K4zxRAtIHrwCVgFqD4mwV+cSojE44dOi7tqH/3bp7yCdo4LilleeetRQ==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-a2a-yaml-1/-/apidom-parser-adapter-a2a-yaml-1-1.13.3.tgz", + "integrity": "sha512-WMDimop6PdvAoNKymE5sm4xclvEfFFQhASKjY48t9E7gYg0qterZKspNBVDw0Zd6OP/Lg4NxmdafPlkhD5JTQA==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-ns-a2a-1": "^1.12.1", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-a2a-1": "^1.13.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-json": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-1.12.1.tgz", - "integrity": "sha512-xhUgAXs8Rxssfr3dEWpKhDheeycI9Vt6jtBVEwavcL9+ss8cY9jzVck3R7Ql7P9yBVhF41K4ZTUIO1QJVngYZA==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-1.13.3.tgz", + "integrity": "sha512-eaoQxXaMBBqYlNxYDbmzNyYCfn73TiqVuTQxfDNENtQmX36w3c/T7fBwOjfL41GqzzJF6pzVvu3jFHOFgse0Ww==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-ns-api-design-systems": "^1.12.1", - "@swagger-api/apidom-parser-adapter-json": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-api-design-systems": "^1.13.3", + "@swagger-api/apidom-parser-adapter-json": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-yaml": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-1.12.1.tgz", - "integrity": "sha512-FPJ9Da6CChE3Xp5+vS8bXrsdmzy7+BrTxLt38SQrpYCpx1fRKsfs+xZqLTRHEhv01QG7Hj7MFG/xGEqK7rt8/w==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-1.13.3.tgz", + "integrity": "sha512-Ne6oCXVaanpN1h4ThuOhiF4HDUN3lZmSfpkJt5qJrsMYNGIVqIIEjrqhVs3MDK+gSa+oQszHnbZSkePz+6dsJQ==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-ns-api-design-systems": "^1.12.1", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-api-design-systems": "^1.13.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-arazzo-json-1": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-arazzo-json-1/-/apidom-parser-adapter-arazzo-json-1-1.12.1.tgz", - "integrity": "sha512-nJB+UWnSU9wz615wsXJfPh5Ij180UOYsMN2icuVADVBKGAAxfEbafq3ATtPVvpBNZBg50eKeIwjAXjjC4OsLMg==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-arazzo-json-1/-/apidom-parser-adapter-arazzo-json-1-1.13.3.tgz", + "integrity": "sha512-UTV8YHKZInMCyXDBQq4rmgW+dK9rQ7tjRFjr4lzXWMZuBPcd0+ot4Wh0azn7NC8vXqa1qBX6a+pqX9hUVLRXQA==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-ns-arazzo-1": "^1.12.1", - "@swagger-api/apidom-parser-adapter-json": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-arazzo-1": "^1.13.3", + "@swagger-api/apidom-parser-adapter-json": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-arazzo-yaml-1": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-arazzo-yaml-1/-/apidom-parser-adapter-arazzo-yaml-1-1.12.1.tgz", - "integrity": "sha512-8lgMKvb0Lhl1C9uNtBYm9DR9Dljpk/u23dH1H/xGAD1A54zZaQYchbyC4VCAHuesmjVT+mWylGoIU0SIlAOcNQ==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-arazzo-yaml-1/-/apidom-parser-adapter-arazzo-yaml-1-1.13.3.tgz", + "integrity": "sha512-s0eT30Yy0izzZIRSJfQedwI0nOFMLs5E5wRzAUje8DI2qdSlFjA1AFtDtylMq9TQPn9y6AG2ysPy/TecfigdbQ==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-ns-arazzo-1": "^1.12.1", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-arazzo-1": "^1.13.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-json-2": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-1.12.1.tgz", - "integrity": "sha512-HetDS5x6yHF/JeDokHiPNzX5lhm0GbVpy5MMVOcfH7zp9QSBFBGwhJTRPDEYm8PKfjkCMFLzCgiUKwqn73LV4g==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-1.13.3.tgz", + "integrity": "sha512-u7OXUI17VK+SX6j8whSBNPnMl85fvexFyu1lbrdpNIJP3gojUgvSZN6g9w0pII7VYBfsYDkDLKQemYVQtK4jEA==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-ns-asyncapi-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-json": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-asyncapi-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-json": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-json-3": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-3/-/apidom-parser-adapter-asyncapi-json-3-1.12.1.tgz", - "integrity": "sha512-FzWH1uVNu3jWG6qB1OtbC76bQburCf3zoi3VPEQONSjT+i2vfcgc+WhAjVxaGQEwpjhbCkF2oo6WPC1hPbxARA==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-3/-/apidom-parser-adapter-asyncapi-json-3-1.13.3.tgz", + "integrity": "sha512-Z65quIJg7z74wGJH/JuAbGM92FsWxmHKrZQl7/0yMAzI56mATUTgDWNXbm51JypRzSA393MKpFzy0dinU0eN+A==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-ns-asyncapi-3": "^1.12.1", - "@swagger-api/apidom-parser-adapter-json": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-asyncapi-3": "^1.13.3", + "@swagger-api/apidom-parser-adapter-json": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-1.12.1.tgz", - "integrity": "sha512-zkMaQZPbdJ1rg+754BxJsd+gOmFRBAGObJ2xGLSRf7OKqhwJhnJQVGlyYViaNNpzHPfrhc6KeEsiczVqGESDKA==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-1.13.3.tgz", + "integrity": "sha512-18m7ubllmlEp1xUL2idh7u5QgJbQVkLVYtQvIalvVHC8Lr6I4kbG4LiZXjxGx1SBg+BpPV1jjlQ+bG+Iz4Vh7w==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-ns-asyncapi-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-asyncapi-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-3/-/apidom-parser-adapter-asyncapi-yaml-3-1.12.1.tgz", - "integrity": "sha512-B2vVz0V+3WC+SBgZvHFYceF3RqGGghVOhXVQm5R9L6yUqT1zrmbu9RC+CFftKHs9mp3BX83dZZBhh+l4vHBuPw==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-3/-/apidom-parser-adapter-asyncapi-yaml-3-1.13.3.tgz", + "integrity": "sha512-iuLrgNFv1mujaHkeO1xTRvrP28RMtwT3D6zq8RmqHrdlXEYd9RFTsecuBBax6ZyB4WRU3r/bbTPhy2UfhM0vaw==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-ns-asyncapi-3": "^1.12.1", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-asyncapi-3": "^1.13.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-json": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-1.12.1.tgz", - "integrity": "sha512-ImdTAC3+qTwEyjumBD19lcLp8UZ0WdB0T/iXNJOMvAw4oqZdGvsq2Zdthgbvxq5CnTr146YH5IzXuOYPfh47jw==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-1.13.3.tgz", + "integrity": "sha512-1TbxPuoh+ZSoGo0bcGnAeOFANU8GPTYVt1jsDnVIhNQL/DU/27hCuKse5xiystKNt6qiGyAQ1wEKqtaBedojzg==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-ast": "^1.12.1", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-error": "^1.12.1", + "@swagger-api/apidom-ast": "^1.13.3", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-error": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -4140,145 +4143,177 @@ "web-tree-sitter": "=0.24.5" } }, + "node_modules/@swagger-api/apidom-parser-adapter-json-schema-json-2020-12": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json-schema-json-2020-12/-/apidom-parser-adapter-json-schema-json-2020-12-1.13.3.tgz", + "integrity": "sha512-+FFJVzThB6KXQ/PLkIaus24VuzkILpE83PzAmPpG6/SsjOmgXRJfMWz2zITgRcbPkyIPQFUwT2PpvMhepgN27g==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-json-schema-2020-12": "^1.13.3", + "@swagger-api/apidom-parser-adapter-json": "^1.13.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-json-schema-yaml-2020-12": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json-schema-yaml-2020-12/-/apidom-parser-adapter-json-schema-yaml-2020-12-1.13.3.tgz", + "integrity": "sha512-+0b3+1Ad9A66u0daemqB/nvElPgvjU13unfsSO3EzcI8++PDCuT9D6qxjH/7TQY76EVorQWTijtMua3JIBbfKg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-json-schema-2020-12": "^1.13.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.13.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-2": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-2/-/apidom-parser-adapter-openapi-json-2-1.12.1.tgz", - "integrity": "sha512-oo98u119nP05eLetyc/DPmDtDBfj+ZYeLRnq9pDa+7UOQs83NUvNWsfdWvJtSpgwOZZIqhR8+O7amULKJR+BdA==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-2/-/apidom-parser-adapter-openapi-json-2-1.13.3.tgz", + "integrity": "sha512-gwc2lBGvmMlHXPKU5a8isCbGvo1CDg84uEjp3A/zT3SSYXaEBKOqrqLFvSVoPAtVo3tpTTwtSoxodESxdl1YLw==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-ns-openapi-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-json": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-openapi-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-json": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-0": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-1.12.1.tgz", - "integrity": "sha512-SA5PiAytwGuC56pS8FOCLZVeUdudHHrot2ms9SwFFZMF+sHGgeQ8+EoBiE0+H4L+M5otpMyV/slT+WwfGCCGsw==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-1.13.3.tgz", + "integrity": "sha512-VrW34GMn8PerjUBCNDHJpulSEAH8BsUAV09jPw4PhVsIFSombRbEflLDH8cpmLYS4l2ywNVgn6YiZKy92jnJ6Q==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-ns-openapi-3-0": "^1.12.1", - "@swagger-api/apidom-parser-adapter-json": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-openapi-3-0": "^1.13.3", + "@swagger-api/apidom-parser-adapter-json": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-1": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-1.12.1.tgz", - "integrity": "sha512-cqJ/vvxI6+apG7NZHMugiqLpF/xquKcw1vVxrRraXetrzwNBGHHdfYh8pnW1BlVDXNoVplqMHxyJnANYrD13KQ==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-1.13.3.tgz", + "integrity": "sha512-h06hVkFgpRiyWes2kRBKvquAVozIjAXBmEV7xowyo1hRyE+45VFPjey/pdaV6YWiSCekLZ2kRYh4U4b+U+oL0w==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-ns-openapi-3-1": "^1.12.1", - "@swagger-api/apidom-parser-adapter-json": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-openapi-3-1": "^1.13.3", + "@swagger-api/apidom-parser-adapter-json": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-2": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-2/-/apidom-parser-adapter-openapi-json-3-2-1.12.1.tgz", - "integrity": "sha512-BFrjyONcPrp8kFYC8YPIKO2mvJcJMp2Gc99MwnVdpbZY+PJ2GWNh8ZMDR+e90VAAATCn+ZaeYQXjKo788BtZ2w==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-2/-/apidom-parser-adapter-openapi-json-3-2-1.13.3.tgz", + "integrity": "sha512-QsVjugqHqwM4VvN2DIIHpHt+5clvliHOBAj/WBYeiCBTBBI6m1/2hlG2Vu7jyMEpA88ML1WLK8JOHf2+fOwN1A==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-ns-openapi-3-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-json": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-openapi-3-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-json": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-2": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-2/-/apidom-parser-adapter-openapi-yaml-2-1.12.1.tgz", - "integrity": "sha512-1tZI8BYmqbgSYKBW3zQy0YtG0xDwVCdguBffSITb6qXUfhpzDrziJFKqetAe+aa1SysyuZzyr/VOXizmvuY5mg==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-2/-/apidom-parser-adapter-openapi-yaml-2-1.13.3.tgz", + "integrity": "sha512-lWTxEXmISAE5qN3H0sKLZyNdlBTM3LwyeRbkuGSLbzT8SyIAL37chiuYaBNRfdA1Y8x9xbIdimCWZpACet4Glw==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-ns-openapi-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-openapi-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-1.12.1.tgz", - "integrity": "sha512-tnzd433XQeW3h8n4IrHCDZtMi1/55DYwl5Keov+w6ozfuX+akYm9y+tdD7wjlxF5sIVj0qD0glU05m2L4JH0rQ==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-1.13.3.tgz", + "integrity": "sha512-EDmWNX7OFaHQl8YIRNdH06aehJZr3qt+1p2Qzrc2vn0adnmw9lQX3FvBUI44A5w3R/Q2z0iMtOXIo3baqr7MZQ==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-ns-openapi-3-0": "^1.12.1", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-openapi-3-0": "^1.13.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-1.12.1.tgz", - "integrity": "sha512-1E6w/e9rOQ3tPoalI3WzUEbkxqtDWMeJvYqlTCKjESBxejz8LMr5PWbmSObG317Bd6E38LkWP6vEB9acXcO9Zg==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-1.13.3.tgz", + "integrity": "sha512-0Qi+dK+6F6cgu0GTm1Y/tCSbhN6zZI9iULjLPNsQYLZEKvUzo56FFcGVIOaYveG7qguXKC9YQ9bZcS1ZqhgS1Q==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-ns-openapi-3-1": "^1.12.1", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-openapi-3-1": "^1.13.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-2": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-2/-/apidom-parser-adapter-openapi-yaml-3-2-1.12.1.tgz", - "integrity": "sha512-6wzxjBavlFghHmcNyqGxHLlOrRPndUjKOtz677YLjmiRz5TCDI7bSkqE6LaxktgsO4ygxUs9mkXNcEkAeJtfYw==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-2/-/apidom-parser-adapter-openapi-yaml-3-2-1.13.3.tgz", + "integrity": "sha512-dL3vJ8kFSvh9mmlHM1Cl8hNFJWHQ8CpypkfN7QTuMHIVJruvOwJ2VWhNXM8GTH07ytFg8cFto0UAGYgG2E/raw==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-ns-openapi-3-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-ns-openapi-3-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.13.3", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-1.12.1.tgz", - "integrity": "sha512-AMIvD+XHtkjFnuXRfn0OJynitPCdi+ru9WEj4SrZ9KnSOV6tkhhuZqi+IB2LEdwPBAjI7WuuNNqLkMhCu1ZDGw==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-1.13.3.tgz", + "integrity": "sha512-6jNhcCGnEE35dCG1NGEEddUb26kFU8VzuBoCJpOeCOJcKcsVehSpS2igD9bOVepoWMORRRE7CQkcCjoKMk95Xg==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-ast": "^1.12.1", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-error": "^1.12.1", + "@swagger-api/apidom-ast": "^1.13.3", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-error": "^1.13.3", "@tree-sitter-grammars/tree-sitter-yaml": "=0.7.1", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", @@ -4320,49 +4355,49 @@ } }, "node_modules/@swagger-api/apidom-reference": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-1.12.1.tgz", - "integrity": "sha512-2Au0z4AwwpqKia09p87xCTOWRHAoLuwgSY+082FOcyyOev8ICBUFd6vQXBL5wGKOgwsn6hShkvopj5dObGcKUA==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-1.13.3.tgz", + "integrity": "sha512-mOJ7ANqcrxhzGHPybNuzAV8Ytm7gCGRj+S4xOkNmg7cerPmMgUxTwhDPF3FDQ/wUfQL+jjdYef2IydwdOxab+A==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-error": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-error": "^1.13.3", "@types/ramda": "~0.30.0", - "axios": "^1.18.0", + "axios": "^1.20.0", "minimatch": "^10.2.6", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" }, "optionalDependencies": { - "@swagger-api/apidom-json-pointer": "^1.12.1", - "@swagger-api/apidom-ns-a2a-1": "^1.12.1", - "@swagger-api/apidom-ns-arazzo-1": "^1.12.1", - "@swagger-api/apidom-ns-asyncapi-2": "^1.12.1", - "@swagger-api/apidom-ns-openapi-2": "^1.12.1", - "@swagger-api/apidom-ns-openapi-3-0": "^1.12.1", - "@swagger-api/apidom-ns-openapi-3-1": "^1.12.1", - "@swagger-api/apidom-ns-openapi-3-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-a2a-json-1": "^1.12.1", - "@swagger-api/apidom-parser-adapter-a2a-yaml-1": "^1.12.1", - "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^1.12.1", - "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^1.12.1", - "@swagger-api/apidom-parser-adapter-arazzo-json-1": "^1.12.1", - "@swagger-api/apidom-parser-adapter-arazzo-yaml-1": "^1.12.1", - "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-asyncapi-json-3": "^1.12.1", - "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": "^1.12.1", - "@swagger-api/apidom-parser-adapter-json": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-json-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-json-3-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.1" + "@swagger-api/apidom-json-pointer": "^1.13.3", + "@swagger-api/apidom-ns-a2a-1": "^1.13.3", + "@swagger-api/apidom-ns-arazzo-1": "^1.13.3", + "@swagger-api/apidom-ns-asyncapi-2": "^1.13.3", + "@swagger-api/apidom-ns-openapi-2": "^1.13.3", + "@swagger-api/apidom-ns-openapi-3-0": "^1.13.3", + "@swagger-api/apidom-ns-openapi-3-1": "^1.13.3", + "@swagger-api/apidom-ns-openapi-3-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-a2a-json-1": "^1.13.3", + "@swagger-api/apidom-parser-adapter-a2a-yaml-1": "^1.13.3", + "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^1.13.3", + "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^1.13.3", + "@swagger-api/apidom-parser-adapter-arazzo-json-1": "^1.13.3", + "@swagger-api/apidom-parser-adapter-arazzo-yaml-1": "^1.13.3", + "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-asyncapi-json-3": "^1.13.3", + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": "^1.13.3", + "@swagger-api/apidom-parser-adapter-json": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-json-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-json-3-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.13.3" } }, "node_modules/@swaggerexpert/cookie": { diff --git a/package.json b/package.json index 49792e2e1..2fe4a249d 100644 --- a/package.json +++ b/package.json @@ -74,12 +74,13 @@ "dependencies": { "@babel/runtime-corejs3": "^7.22.15", "@scarf/scarf": "=1.4.0", - "@swagger-api/apidom-core": "^1.12.1", - "@swagger-api/apidom-error": "^1.12.1", - "@swagger-api/apidom-json-pointer": "^1.12.1", - "@swagger-api/apidom-ns-openapi-3-1": "^1.12.1", - "@swagger-api/apidom-ns-openapi-3-2": "^1.12.1", - "@swagger-api/apidom-reference": "^1.12.1", + "@swagger-api/apidom-core": "^1.13.3", + "@swagger-api/apidom-error": "^1.13.3", + "@swagger-api/apidom-json-pointer": "^1.13.3", + "@swagger-api/apidom-ns-json-schema-2020-12": "^1.13.3", + "@swagger-api/apidom-ns-openapi-3-1": "^1.13.3", + "@swagger-api/apidom-ns-openapi-3-2": "^1.13.3", + "@swagger-api/apidom-reference": "^1.13.3", "@swaggerexpert/cookie": "^2.0.2", "deepmerge": "~4.3.0", "fast-json-patch": "^3.0.0-1", @@ -130,27 +131,29 @@ "webpack-stats-plugin": "=1.1.3" }, "optionalDependencies": { - "@swagger-api/apidom-ns-asyncapi-2": "^1.12.1", - "@swagger-api/apidom-ns-asyncapi-3": "^1.12.1", - "@swagger-api/apidom-ns-openapi-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^1.12.1", - "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^1.12.1", - "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-asyncapi-json-3": "^1.12.1", - "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": "^1.12.1", - "@swagger-api/apidom-parser-adapter-json": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-json-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-json-3-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "^1.12.1", - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-2": "^1.12.1", - "@swagger-api/apidom-parser-adapter-arazzo-json-1": "^1.12.1", - "@swagger-api/apidom-parser-adapter-arazzo-yaml-1": "^1.12.1", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.1" + "@swagger-api/apidom-ns-asyncapi-2": "^1.13.3", + "@swagger-api/apidom-ns-asyncapi-3": "^1.13.3", + "@swagger-api/apidom-ns-openapi-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^1.13.3", + "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^1.13.3", + "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-asyncapi-json-3": "^1.13.3", + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": "^1.13.3", + "@swagger-api/apidom-parser-adapter-json": "^1.13.3", + "@swagger-api/apidom-parser-adapter-json-schema-json-2020-12": "^1.13.3", + "@swagger-api/apidom-parser-adapter-json-schema-yaml-2020-12": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-json-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-json-3-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "^1.13.3", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-2": "^1.13.3", + "@swagger-api/apidom-parser-adapter-arazzo-json-1": "^1.13.3", + "@swagger-api/apidom-parser-adapter-arazzo-yaml-1": "^1.13.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.13.3" }, "allowScripts": { "tree-sitter@0.22.4": true, From 108218ec7e96702840d582a3721fbeb10e9d7fc2 Mon Sep 17 00:00:00 2001 From: roberthebel Date: Mon, 26 Oct 2026 23:55:21 +0100 Subject: [PATCH 2/8] build(deps): use unreleased ApiDOM from phase-3 branch (drop before merge) Install @swagger-api/apidom-reference and @swagger-api/apidom-ns-json-schema-2020-12 from tarballs packed from the ApiDOM branch feat/json-schema-2020-12/phase-3 at commit 11951862a, which adds the json-schema-2020-12 resolve and dereference strategies and exports keyMap and getNodeType from the namespace. The tarballs live in ../upstream-tarballs; "$" overrides make nested copies use them. Drop this commit once ApiDOM releases the branch, and bump every @swagger-api/apidom-* package to that release. Co-Authored-By: Claude Opus 5.5 --- package-lock.json | 15 +++++++++------ package.json | 8 ++++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index d5763ebe6..461e4ff75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,10 +14,10 @@ "@swagger-api/apidom-core": "^1.13.3", "@swagger-api/apidom-error": "^1.13.3", "@swagger-api/apidom-json-pointer": "^1.13.3", - "@swagger-api/apidom-ns-json-schema-2020-12": "^1.13.3", + "@swagger-api/apidom-ns-json-schema-2020-12": "file:../upstream-tarballs/swagger-api-apidom-ns-json-schema-2020-12-1.13.3-11951862a.tgz", "@swagger-api/apidom-ns-openapi-3-1": "^1.13.3", "@swagger-api/apidom-ns-openapi-3-2": "^1.13.3", - "@swagger-api/apidom-reference": "^1.13.3", + "@swagger-api/apidom-reference": "file:../upstream-tarballs/swagger-api-apidom-reference-1.13.3-11951862a.tgz", "@swaggerexpert/cookie": "^2.0.2", "deepmerge": "~4.3.0", "fast-json-patch": "^3.0.0-1", @@ -3833,8 +3833,8 @@ }, "node_modules/@swagger-api/apidom-ns-json-schema-2020-12": { "version": "1.13.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-2020-12/-/apidom-ns-json-schema-2020-12-1.13.3.tgz", - "integrity": "sha512-fF5Wqvmb2vvx0o0Tr5Vcz+Q6gedVi7oGeKJWaSBA7WJ0awUz+PCIW3OPiKhcxdVW2/Zrxd5kMELDCmarnHS65Q==", + "resolved": "file:../upstream-tarballs/swagger-api-apidom-ns-json-schema-2020-12-1.13.3-11951862a.tgz", + "integrity": "sha512-EW0bedNFtQCyN5+fvBI5MiKQZR0CM35s9Va2NvqakXWIQ5rfVNB+bl09+1AbSzSAkO8B2zAWjgbv7Ut0AEopaw==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", @@ -4356,8 +4356,8 @@ }, "node_modules/@swagger-api/apidom-reference": { "version": "1.13.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-1.13.3.tgz", - "integrity": "sha512-mOJ7ANqcrxhzGHPybNuzAV8Ytm7gCGRj+S4xOkNmg7cerPmMgUxTwhDPF3FDQ/wUfQL+jjdYef2IydwdOxab+A==", + "resolved": "file:../upstream-tarballs/swagger-api-apidom-reference-1.13.3-11951862a.tgz", + "integrity": "sha512-TAWOGyKMM5dlGaCrt+ekJR5JV2wQ/BT9SjcHaIS8nM5UQFSU4Sus0swsLUggMObWeLIEohx2eS4IoSi01HS56Q==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", @@ -4374,6 +4374,7 @@ "@swagger-api/apidom-ns-a2a-1": "^1.13.3", "@swagger-api/apidom-ns-arazzo-1": "^1.13.3", "@swagger-api/apidom-ns-asyncapi-2": "^1.13.3", + "@swagger-api/apidom-ns-json-schema-2020-12": "^1.13.3", "@swagger-api/apidom-ns-openapi-2": "^1.13.3", "@swagger-api/apidom-ns-openapi-3-0": "^1.13.3", "@swagger-api/apidom-ns-openapi-3-1": "^1.13.3", @@ -4389,6 +4390,8 @@ "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^1.13.3", "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": "^1.13.3", "@swagger-api/apidom-parser-adapter-json": "^1.13.3", + "@swagger-api/apidom-parser-adapter-json-schema-json-2020-12": "^1.13.3", + "@swagger-api/apidom-parser-adapter-json-schema-yaml-2020-12": "^1.13.3", "@swagger-api/apidom-parser-adapter-openapi-json-2": "^1.13.3", "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^1.13.3", "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^1.13.3", diff --git a/package.json b/package.json index 2fe4a249d..bb5b31292 100644 --- a/package.json +++ b/package.json @@ -77,10 +77,10 @@ "@swagger-api/apidom-core": "^1.13.3", "@swagger-api/apidom-error": "^1.13.3", "@swagger-api/apidom-json-pointer": "^1.13.3", - "@swagger-api/apidom-ns-json-schema-2020-12": "^1.13.3", + "@swagger-api/apidom-ns-json-schema-2020-12": "file:../upstream-tarballs/swagger-api-apidom-ns-json-schema-2020-12-1.13.3-11951862a.tgz", "@swagger-api/apidom-ns-openapi-3-1": "^1.13.3", "@swagger-api/apidom-ns-openapi-3-2": "^1.13.3", - "@swagger-api/apidom-reference": "^1.13.3", + "@swagger-api/apidom-reference": "file:../upstream-tarballs/swagger-api-apidom-reference-1.13.3-11951862a.tgz", "@swaggerexpert/cookie": "^2.0.2", "deepmerge": "~4.3.0", "fast-json-patch": "^3.0.0-1", @@ -155,6 +155,10 @@ "@swagger-api/apidom-parser-adapter-arazzo-yaml-1": "^1.13.3", "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.13.3" }, + "overrides": { + "@swagger-api/apidom-ns-json-schema-2020-12": "$@swagger-api/apidom-ns-json-schema-2020-12", + "@swagger-api/apidom-reference": "$@swagger-api/apidom-reference" + }, "allowScripts": { "tree-sitter@0.22.4": true, "tree-sitter@0.21.1": true, From 364dc43b1d68493fdd9ed0b99100b2279075cb8d Mon Sep 17 00:00:00 2001 From: roberthebel Date: Tue, 27 Oct 2026 04:43:26 +0100 Subject: [PATCH 3/8] feat(json-schema): resolve standalone JSON Schema 2020-12 documents Add the json-schema-2020-12-apidom resolve strategy for documents whose root $schema is https://json-schema.org/draft/2020-12/schema (an empty fragment normalised) and which have no openapi or swagger field. Any other $schema keeps the generic strategy. The strategy resolves over apidom-reference's json-schema-2020-12 strategies, so $refs resolve against $id base URIs, $anchor and $dynamicAnchor plain names and external documents as in OpenAPI 3.1 Schema Objects; every $dynamicRef is left in place. The json-schema-2020-12-swagger-client dereference strategy carries over the OpenAPI 3.1 Schema Object handling (cycles, $$ref meta patches, collected errors), merges allOf unless mode is strict and applies modelPropertyMacro, so a schema resolves the same standalone and inside an OpenAPI 3.1 document. A $ref in the document root keeps its replacement as the parse result. The JSON and YAML parsers detect a document by its parsed root and are used only by this strategy; the OpenAPI parser lists are unchanged. Register the strategy ahead of generic in Swagger.resolve and Swagger.resolveSubtree, and export the parsers and the dereference strategy in Swagger.apidom. Co-Authored-By: Claude Opus 5.5 --- .eslintrc.js | 4 + CLAUDE.md | 6 +- README.md | 8 + docs/usage/installation.md | 2 + src/helpers/json-schema-predicates.js | 29 + src/index.js | 10 + .../errors/SchemaRefError.js | 5 + .../index.js | 118 ++++ .../utils/get-root-cause.js | 17 + .../utils/to-path.js | 27 + .../visitors/all-of.js | 124 ++++ .../visitors/dereference.js | 399 ++++++++++++ .../visitors/properties.js | 36 ++ .../visitors/root.js | 39 ++ .../parsers/json-schema-json-2020-12/index.js | 73 +++ .../parsers/json-schema-yaml-2020-12/index.js | 74 +++ .../json-schema-2020-12-apidom/index.js | 18 + .../json-schema-2020-12-apidom/normalize.js | 10 + .../json-schema-2020-12-apidom/resolve.js | 178 ++++++ test/helpers/json-schema-predicates.js | 137 +++++ .../__utils__/jest.local.setup.js | 47 ++ .../anchor-external/dereferenced.json | 18 + .../__fixtures__/anchor-external/ex.json | 16 + .../__fixtures__/anchor-external/root.json | 7 + .../anchor-internal/dereferenced.json | 58 ++ .../__fixtures__/anchor-internal/root.json | 24 + .../boolean-json-schema/dereferenced.json | 14 + .../boolean-json-schema/root.json | 12 + .../__fixtures__/cycle-external/ex.json | 16 + .../__fixtures__/cycle-external/root.json | 7 + .../__fixtures__/cycle-internal/root.json | 12 + .../dynamic-anchor/dereferenced.json | 17 + .../__fixtures__/dynamic-anchor/root.json | 12 + .../dynamic-ref/dereferenced.json | 40 ++ .../__fixtures__/dynamic-ref/root.json | 29 + .../external-only/dereferenced.json | 29 + .../__fixtures__/external-only/ex.json | 16 + .../__fixtures__/external-only/root.json | 8 + .../id-canonical/dereferenced.json | 76 +++ .../__fixtures__/id-canonical/root.json | 27 + .../id-relative-nested/dereferenced.json | 58 ++ .../id-relative-nested/nested/ex.json | 10 + .../__fixtures__/id-relative-nested/root.json | 23 + .../id-relative/dereferenced.json | 46 ++ .../__fixtures__/id-relative/nested/ex.json | 10 + .../__fixtures__/id-relative/root.json | 21 + .../internal-external/dereferenced.json | 38 ++ .../__fixtures__/internal-external/ex.json | 15 + .../__fixtures__/internal-external/root.json | 15 + .../internal-only/dereferenced.json | 60 ++ .../__fixtures__/internal-only/root.json | 22 + .../invalid-pointer/dereferenced.json | 19 + .../__fixtures__/invalid-pointer/root.json | 11 + .../merging-keywords/dereferenced.json | 19 + .../__fixtures__/merging-keywords/root.json | 17 + .../__fixtures__/root-ref/dereferenced.json | 51 ++ .../__fixtures__/root-ref/root.json | 23 + .../unresolvable-reference/dereferenced.json | 19 + .../unresolvable-reference/root.json | 11 + .../json-schema/index.js | 270 +++++++++ .../utils/get-root-cause.js | 70 +++ .../visitors/all-of.js | 239 ++++++++ .../visitors/dereference.js | 212 +++++++ .../visitors/properties.js | 96 +++ .../visitors/root.js | 150 +++++ .../__fixtures__/sample-schema.json | 19 + .../parsers/json-schema-json-2020-12/index.js | 255 ++++++++ .../__fixtures__/sample-schema.yaml | 16 + .../parsers/json-schema-yaml-2020-12/index.js | 232 +++++++ .../__fixtures__/circular.json | 13 + .../__fixtures__/external.json | 15 + .../__fixtures__/macros.json | 22 + .../openapi-3-1-external-json-schema.json | 11 + .../__fixtures__/product.json | 10 + .../__fixtures__/root.json | 48 ++ .../__fixtures__/user.schema.json | 17 + .../json-schema-2020-12-apidom/index.js | 573 ++++++++++++++++++ 77 files changed, 4554 insertions(+), 1 deletion(-) create mode 100644 src/helpers/json-schema-predicates.js create mode 100644 src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/errors/SchemaRefError.js create mode 100644 src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/index.js create mode 100644 src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/utils/get-root-cause.js create mode 100644 src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/utils/to-path.js create mode 100644 src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/all-of.js create mode 100644 src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/dereference.js create mode 100644 src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/properties.js create mode 100644 src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/root.js create mode 100644 src/resolver/apidom/reference/parse/parsers/json-schema-json-2020-12/index.js create mode 100644 src/resolver/apidom/reference/parse/parsers/json-schema-yaml-2020-12/index.js create mode 100644 src/resolver/strategies/json-schema-2020-12-apidom/index.js create mode 100644 src/resolver/strategies/json-schema-2020-12-apidom/normalize.js create mode 100644 src/resolver/strategies/json-schema-2020-12-apidom/resolve.js create mode 100644 test/helpers/json-schema-predicates.js create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/__utils__/jest.local.setup.js create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-external/dereferenced.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-external/ex.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-external/root.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-internal/dereferenced.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-internal/root.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/boolean-json-schema/dereferenced.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/boolean-json-schema/root.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/cycle-external/ex.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/cycle-external/root.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/cycle-internal/root.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/dynamic-anchor/dereferenced.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/dynamic-anchor/root.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/dynamic-ref/dereferenced.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/dynamic-ref/root.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/external-only/dereferenced.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/external-only/ex.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/external-only/root.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-canonical/dereferenced.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-canonical/root.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative-nested/dereferenced.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative-nested/nested/ex.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative-nested/root.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative/dereferenced.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative/nested/ex.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative/root.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/internal-external/dereferenced.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/internal-external/ex.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/internal-external/root.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/internal-only/dereferenced.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/internal-only/root.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/invalid-pointer/dereferenced.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/invalid-pointer/root.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/merging-keywords/dereferenced.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/merging-keywords/root.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/root-ref/dereferenced.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/root-ref/root.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/unresolvable-reference/dereferenced.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/unresolvable-reference/root.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/index.js create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/utils/get-root-cause.js create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/all-of.js create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/dereference.js create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/properties.js create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/root.js create mode 100644 test/resolver/apidom/reference/parse/parsers/json-schema-json-2020-12/__fixtures__/sample-schema.json create mode 100644 test/resolver/apidom/reference/parse/parsers/json-schema-json-2020-12/index.js create mode 100644 test/resolver/apidom/reference/parse/parsers/json-schema-yaml-2020-12/__fixtures__/sample-schema.yaml create mode 100644 test/resolver/apidom/reference/parse/parsers/json-schema-yaml-2020-12/index.js create mode 100644 test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/circular.json create mode 100644 test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/external.json create mode 100644 test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/macros.json create mode 100644 test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/openapi-3-1-external-json-schema.json create mode 100644 test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/product.json create mode 100644 test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/root.json create mode 100644 test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/user.schema.json create mode 100644 test/resolver/strategies/json-schema-2020-12-apidom/index.js diff --git a/.eslintrc.js b/.eslintrc.js index aef09b730..7999613c5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -52,9 +52,13 @@ module.exports = { '^@swagger-api/apidom-reference/dereference/strategies/openapi-3-2$', '^@swagger-api/apidom-reference/dereference/strategies/openapi-3-2/selectors/\\$anchor$', '^@swagger-api/apidom-reference/dereference/strategies/openapi-3-2/selectors/uri$', + '^@swagger-api/apidom-reference/dereference/strategies/json-schema-2020-12$', + '^@swagger-api/apidom-reference/dereference/strategies/json-schema-2020-12/selectors/\\$anchor$', + '^@swagger-api/apidom-reference/dereference/strategies/json-schema-2020-12/selectors/uri$', '^@swagger-api/apidom-reference/resolve/resolvers/file$', '^@swagger-api/apidom-reference/resolve/strategies/openapi-3-1$', '^@swagger-api/apidom-reference/resolve/strategies/openapi-3-2$', + '^@swagger-api/apidom-reference/resolve/strategies/json-schema-2020-12$', '^@swagger-api/apidom-reference/parse/parsers/binary$', '^@swagger-api/apidom-json-pointer/modern$', ], diff --git a/CLAUDE.md b/CLAUDE.md index 35211882f..308433452 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,6 +10,7 @@ This document provides comprehensive guidance for AI assistants working on the s - OpenAPI 3.0.x (3.0.0 through 3.0.4) - Swagger/OpenAPI 2.0 - Legacy Swagger 1.x (via version 2.x branch) +- Standalone JSON Schema 2020-12 documents (resolution only: root `$schema`, no `openapi`/`swagger` field) **Current Version**: 3.36.0 @@ -304,10 +305,13 @@ Handles $ref resolution and spec normalization. Uses different strategies based - `openapi-3-1-apidom`: Uses ApiDOM library for OpenAPI 3.1 - `openapi-3-0`: Custom resolver for OpenAPI 3.0.x - `openapi-2-0`: Custom resolver for Swagger 2.0 +- `json-schema-2020-12-apidom`: Uses ApiDOM for standalone JSON Schema 2020-12 documents (`$id`, `$anchor`; `$dynamicRef` left in place) - `generic`: Fallback for unknown specs **Strategy Selection** (in `src/resolver/index.js`): -Strategies are tried in order until one matches. ApiDOM is preferred for OAS 3.1. +Strategies are tried in order until one matches. ApiDOM is preferred for OAS 3.1. The JSON Schema +strategy comes right before `generic`; its predicate (`src/helpers/json-schema-predicates.js`) refuses +OpenAPI documents and any root `$schema` other than the 2020-12 identifier. **SpecMap** (`src/resolver/specmap/`): Legacy resolution engine used by non-ApiDOM strategies. Handles: diff --git a/README.md b/README.md index 0d5e04127..75cb2f67f 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,14 @@ Swagger Client Version | Release Date | OpenAPI Spec compatibility 3.10.x | 2020-01-17 | 2.0, 3.0.0, 3.0.1, 3.0.2, 3.0.3 | [tag v3.10.0](https://github.com/swagger-api/swagger-client/tree/v3.10.0) 2.1.32 | 2017-01-12 | 1.0, 1.1, 1.2 | [tag v2.1.32](https://github.com/swagger-api/swagger-client/tree/v2.1.32). This [release](https://github.com/swagger-api/swagger-client/releases/tag/v2.1.32) is only available on GitHub. +### Standalone JSON Schema + +Swagger Client also resolves standalone JSON Schema 2020-12 documents: documents whose root `$schema` is +`https://json-schema.org/draft/2020-12/schema` (with or without an empty fragment) and which have no `openapi` +or `swagger` field. Their `$ref`s resolve against `$id` base URIs, `$anchor`s and external documents as in +OpenAPI 3.1 Schema Objects, `allOf` is merged unless `mode: 'strict'`, and every `$dynamicRef` is left in place. +Documents with any other `$schema` value keep the generic resolver. + ## Anonymized analytics Swagger Client uses [Scarf](https://scarf.sh/) to collect [anonymized installation analytics](https://github.com/scarf-sh/scarf-js?tab=readme-ov-file#as-a-user-of-a-package-using-scarf-js-what-information-does-scarf-js-send-about-me). These analytics help support the maintainers of this library and ONLY run during installation. To [opt out](https://github.com/scarf-sh/scarf-js?tab=readme-ov-file#as-a-user-of-a-package-using-scarf-js-how-can-i-opt-out-of-analytics), you can set the `scarfSettings.enabled` field to `false` in your project's `package.json`: diff --git a/docs/usage/installation.md b/docs/usage/installation.md index a6b75b92f..34e0799fd 100644 --- a/docs/usage/installation.md +++ b/docs/usage/installation.md @@ -30,6 +30,8 @@ which means you can run `npm install --omit=optional` to speed up the installati "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^1.11.3", "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": "^1.11.3", "@swagger-api/apidom-parser-adapter-json": "^1.11.3", + "@swagger-api/apidom-parser-adapter-json-schema-json-2020-12": "^1.11.3", + "@swagger-api/apidom-parser-adapter-json-schema-yaml-2020-12": "^1.11.3", "@swagger-api/apidom-parser-adapter-openapi-json-2": "^1.11.3", "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "^1.11.3", "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^1.11.3", diff --git a/src/helpers/json-schema-predicates.js b/src/helpers/json-schema-predicates.js new file mode 100644 index 000000000..d790df0f1 --- /dev/null +++ b/src/helpers/json-schema-predicates.js @@ -0,0 +1,29 @@ +import { isPlainObject } from 'ramda-adjunct'; + +/** + * Standalone JSON Schema documents are recognized by the `$schema` keyword of the document root, + * when the document is not an OpenAPI or Swagger document (their Schema Objects can carry + * `$schema` too). Only the dialect identifier itself matches; an empty fragment is normalised + * because documents add or drop it. Other values (the other URI scheme, custom meta-schemas, + * dialects not supported yet) are refused and keep the generic strategy. + * + * https://json-schema.org/specification-links + */ +const normalizeDialectIdentifier = (identifier) => + identifier.endsWith('#') ? identifier.slice(0, -1) : identifier; + +const isJSONSchemaDialect = (identifier) => (spec) => { + try { + if (!isPlainObject(spec)) return false; + if (Object.hasOwn(spec, 'openapi') || Object.hasOwn(spec, 'swagger')) return false; + + const { $schema } = spec; + return typeof $schema === 'string' && normalizeDialectIdentifier($schema) === identifier; + } catch { + return false; + } +}; + +export const JSON_SCHEMA_2020_12_DIALECT = 'https://json-schema.org/draft/2020-12/schema'; + +export const isJSONSchema202012 = isJSONSchemaDialect(JSON_SCHEMA_2020_12_DIALECT); diff --git a/src/index.js b/src/index.js index 6919ae151..69951598f 100644 --- a/src/index.js +++ b/src/index.js @@ -9,6 +9,7 @@ import openApi2ResolveStrategy, { clearCache } from './resolver/strategies/opena import openApi30ResolveStrategy from './resolver/strategies/openapi-3-0/index.js'; import openApi31ApiDOMResolveStrategy from './resolver/strategies/openapi-3-1-apidom/index.js'; import openApi32ApiDOMResolveStrategy from './resolver/strategies/openapi-3-2-apidom/index.js'; +import jsonSchema202012ApiDOMResolveStrategy from './resolver/strategies/json-schema-2020-12-apidom/index.js'; import { makeApisTagOperation } from './interfaces.js'; import { execute, buildRequest, baseUrl } from './execute/index.js'; import { opId, isHttpUrl } from './helpers/index.js'; @@ -20,8 +21,11 @@ import OpenApiJson3_1Parser from './resolver/apidom/reference/parse/parsers/open import OpenApiYaml3_1Parser from './resolver/apidom/reference/parse/parsers/openapi-yaml-3-1/index.js'; import OpenApiJson3_2Parser from './resolver/apidom/reference/parse/parsers/openapi-json-3-2/index.js'; import OpenApiYaml3_2Parser from './resolver/apidom/reference/parse/parsers/openapi-yaml-3-2/index.js'; +import JsonSchemaJson2020_12Parser from './resolver/apidom/reference/parse/parsers/json-schema-json-2020-12/index.js'; +import JsonSchemaYaml2020_12Parser from './resolver/apidom/reference/parse/parsers/json-schema-yaml-2020-12/index.js'; import OpenApi3_1SwaggerClientDereferenceStrategy from './resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/index.js'; import OpenApi3_2SwaggerClientDereferenceStrategy from './resolver/apidom/reference/dereference/strategies/openapi-3-2-swagger-client/index.js'; +import JsonSchema2020_12SwaggerClientDereferenceStrategy from './resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/index.js'; Swagger.http = Http; Swagger.makeHttp = makeHttp.bind(null, Swagger.http); @@ -30,6 +34,7 @@ Swagger.resolveStrategies = { 'openapi-3-1-apidom': openApi31ApiDOMResolveStrategy, 'openapi-3-0': openApi30ResolveStrategy, 'openapi-2-0': openApi2ResolveStrategy, + 'json-schema-2020-12-apidom': jsonSchema202012ApiDOMResolveStrategy, generic: genericResolveStrategy, }; Swagger.resolve = makeResolve({ @@ -38,6 +43,7 @@ Swagger.resolve = makeResolve({ Swagger.resolveStrategies['openapi-3-1-apidom'], Swagger.resolveStrategies['openapi-3-0'], Swagger.resolveStrategies['openapi-2-0'], + Swagger.resolveStrategies['json-schema-2020-12-apidom'], Swagger.resolveStrategies.generic, ], }); @@ -47,6 +53,7 @@ Swagger.resolveSubtree = makeResolveSubtree({ Swagger.resolveStrategies['openapi-3-1-apidom'], Swagger.resolveStrategies['openapi-3-0'], Swagger.resolveStrategies['openapi-2-0'], + Swagger.resolveStrategies['json-schema-2020-12-apidom'], Swagger.resolveStrategies.generic, ], }); @@ -70,12 +77,15 @@ Swagger.apidom = { OpenApiYaml3_1Parser, OpenApiJson3_2Parser, OpenApiYaml3_2Parser, + JsonSchemaJson2020_12Parser, + JsonSchemaYaml2020_12Parser, }, }, dereference: { strategies: { OpenApi3_1SwaggerClientDereferenceStrategy, OpenApi3_2SwaggerClientDereferenceStrategy, + JsonSchema2020_12SwaggerClientDereferenceStrategy, }, }, }; diff --git a/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/errors/SchemaRefError.js b/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/errors/SchemaRefError.js new file mode 100644 index 000000000..a64f59aad --- /dev/null +++ b/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/errors/SchemaRefError.js @@ -0,0 +1,5 @@ +import { ApiDOMStructuredError } from '@swagger-api/apidom-error'; + +class SchemaRefError extends ApiDOMStructuredError {} + +export default SchemaRefError; diff --git a/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/index.js b/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/index.js new file mode 100644 index 000000000..369642e03 --- /dev/null +++ b/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/index.js @@ -0,0 +1,118 @@ +/* eslint-disable camelcase */ +import { createNamespace, visit, cloneDeep } from '@swagger-api/apidom-core'; +import { ReferenceSet, Reference } from '@swagger-api/apidom-reference/configuration/empty'; +import JSONSchema2020_12DereferenceStrategy from '@swagger-api/apidom-reference/dereference/strategies/json-schema-2020-12'; +import jsonSchema2020_12Namespace, { + getNodeType, + keyMap, +} from '@swagger-api/apidom-ns-json-schema-2020-12'; + +import RootVisitor from './visitors/root.js'; + +const visitAsync = visit[Symbol.for('nodejs.util.promisify.custom')]; + +class JSONSchema2020_12SwaggerClientDereferenceStrategy extends JSONSchema2020_12DereferenceStrategy { + allowMetaPatches; + + modelPropertyMacro; + + mode; + + ancestors; + + constructor({ + allowMetaPatches = false, + modelPropertyMacro = null, + mode = 'non-strict', + ancestors = [], + ...rest + } = {}) { + super({ ...rest }); + + this.name = 'json-schema-2020-12-swagger-client'; + this.allowMetaPatches = allowMetaPatches; + this.modelPropertyMacro = modelPropertyMacro; + this.mode = mode; + this.ancestors = [...ancestors]; + } + + async dereference(file, options) { + const namespace = createNamespace(jsonSchema2020_12Namespace); + const immutableRefSet = options.dereference.refSet ?? new ReferenceSet(); + const mutableRefsSet = new ReferenceSet(); + let refSet = immutableRefSet; + let reference; + + if (!immutableRefSet.has(file.uri)) { + reference = new Reference({ uri: file.uri, value: file.parseResult }); + immutableRefSet.add(reference); + } else { + // pre-computed refSet was provided as configuration option + reference = immutableRefSet.find((ref) => ref.uri === file.uri); + } + + /** + * Clone refSet due the dereferencing process being mutable. + * We don't want to mutate the original refSet and the references. + */ + if (options.dereference.immutable) { + immutableRefSet.refs + .map( + (ref) => + new Reference({ + ...ref, + value: cloneDeep(ref.value), + }) + ) + .forEach((ref) => mutableRefsSet.add(ref)); + reference = mutableRefsSet.find((ref) => ref.uri === file.uri); + refSet = mutableRefsSet; + } + + const rootVisitor = new RootVisitor({ + reference, + namespace, + options, + allowMetaPatches: this.allowMetaPatches, + ancestors: this.ancestors, + modelPropertyMacro: this.modelPropertyMacro, + mode: this.mode, + }); + + const dereferencedElement = await visitAsync(refSet.rootRef.value, rootVisitor, { + keyMap, + nodeTypeGetter: getNodeType, + }); + + /** + * If immutable option is set, replay refs from the refSet. + */ + if (options.dereference.immutable) { + mutableRefsSet.refs + .filter((ref) => ref.uri.startsWith('immutable://')) + .map( + (ref) => + new Reference({ + ...ref, + uri: ref.uri.replace(/^immutable:\/\//, ''), + }) + ) + .forEach((ref) => immutableRefSet.add(ref)); + } + + /** + * Release all memory if this refSet was not provided as an configuration option. + * If provided as configuration option, then provider is responsible for cleanup. + */ + if (options.dereference.refSet === null) { + immutableRefSet.clean(); + } + + mutableRefsSet.clean(); + + return dereferencedElement; + } +} + +export default JSONSchema2020_12SwaggerClientDereferenceStrategy; +/* eslint-enable camelcase */ diff --git a/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/utils/get-root-cause.js b/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/utils/get-root-cause.js new file mode 100644 index 000000000..691de05da --- /dev/null +++ b/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/utils/get-root-cause.js @@ -0,0 +1,17 @@ +/** + * Retrieves the root cause of ApiDOM error hierarchy. + * ApiDOM error hierarchies are modeled similar to Java. + * Every error can have cause attribute which references + * cause of this error. + */ +const getRootCause = (error) => { + if (error.cause == null) return error; + + let { cause } = error; + while (cause.cause != null) { + cause = cause.cause; + } + return cause; +}; + +export default getRootCause; diff --git a/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/utils/to-path.js b/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/utils/to-path.js new file mode 100644 index 000000000..55e73742f --- /dev/null +++ b/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/utils/to-path.js @@ -0,0 +1,27 @@ +import { isMemberElement, isArrayElement, toValue } from '@swagger-api/apidom-core'; + +const trimParseResult = (elementPath) => elementPath.slice(2); + +/** + * Transforms ApiDOM traversal meta information into + * SwaggerClient compatible path. + * + * SwaggerClient path is a list of JSON Pointer tokens. + */ +const toPath = (elementPath) => { + const elementPathSanitized = trimParseResult(elementPath); + + return elementPathSanitized.reduce((path, element, index) => { + if (isMemberElement(element)) { + const token = String(toValue(element.key)); + path.push(token); + } else if (isArrayElement(elementPathSanitized[index - 2])) { + const token = elementPathSanitized[index - 2].content.indexOf(element); + path.push(token); + } + + return path; + }, []); +}; + +export default toPath; diff --git a/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/all-of.js b/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/all-of.js new file mode 100644 index 000000000..7e5d6ca12 --- /dev/null +++ b/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/all-of.js @@ -0,0 +1,124 @@ +import { uniqWith } from 'ramda'; +import { + isArrayElement, + isObjectElement, + deepmerge, + toValue, + cloneShallow, + includesClasses, +} from '@swagger-api/apidom-core'; +import { isJSONSchemaElement } from '@swagger-api/apidom-ns-json-schema-2020-12'; + +import toPath from '../utils/to-path.js'; + +class AllOfVisitor { + options; + + JSONSchema202012Element = { + leave(schemaElement, key, parent, path, ancestors) { + // do nothing + if (typeof schemaElement.allOf === 'undefined') return undefined; + + // collect error and return if allOf keyword is not an array + if (!isArrayElement(schemaElement.allOf)) { + const error = new TypeError('allOf must be an array'); + error.fullPath = [...toPath([...ancestors, parent, schemaElement]), 'allOf']; + this.options.dereference.dereferenceOpts?.errors?.push?.(error); + return undefined; + } + + // remove allOf keyword if empty + if (schemaElement.allOf.isEmpty) { + schemaElement.remove('allOf'); + return undefined; + } + + // collect errors if allOf keyword contains anything else than schemas + const includesSchemaElementOnly = schemaElement.allOf.content.every(isJSONSchemaElement); + if (!includesSchemaElementOnly) { + const error = new TypeError('Elements in allOf must be objects'); + error.fullPath = [...toPath([...ancestors, parent, schemaElement]), 'allOf']; + this.options.dereference.dereferenceOpts?.errors?.push?.(error); + return undefined; + } + + while (schemaElement.hasKey('allOf')) { + const { allOf } = schemaElement; + schemaElement.remove('allOf'); + const allOfMerged = deepmerge.all([...allOf.content, schemaElement], { + customMerge: (keyElement) => { + if (toValue(keyElement) === 'enum') { + return (targetElement, sourceElement) => { + if ( + includesClasses(['json-schema-enum'], targetElement) && + includesClasses(['json-schema-enum'], sourceElement) + ) { + const areElementsEqual = (a, b) => { + if ( + isArrayElement(a) || + isArrayElement(b) || + isObjectElement(a) || + isObjectElement(b) + ) { + return false; + } + return a.equals(toValue(b)); + }; + const clone = cloneShallow(targetElement); + clone.content = uniqWith(areElementsEqual)([ + ...targetElement.content, + ...sourceElement.content, + ]); + + return clone; + } + return deepmerge(targetElement, sourceElement); + }; + } + return deepmerge; + }, + }); + + /** + * If there was not an original $$ref value, make sure to remove + * any $$ref value that may exist from the result of `allOf` merges. + */ + if (!schemaElement.hasKey('$$ref')) { + allOfMerged.remove('$$ref'); + } + + /** + * If there was an example keyword in the original schema, + * keep it instead of merging with example from other schema. + */ + if (schemaElement.hasKey('example')) { + const member = allOfMerged.getMember('example'); + if (member) { + member.value = schemaElement.get('example'); + } + } + + /** + * If there was an examples keyword in the original schema, + * keep it instead of merging with examples from other schema. + */ + if (schemaElement.hasKey('examples')) { + const member = allOfMerged.getMember('examples'); + if (member) { + member.value = schemaElement.get('examples'); + } + } + + schemaElement.content = allOfMerged.content; + } + + return undefined; + }, + }; + + constructor({ options }) { + this.options = options; + } +} + +export default AllOfVisitor; diff --git a/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/dereference.js b/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/dereference.js new file mode 100644 index 000000000..dc51a552e --- /dev/null +++ b/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/dereference.js @@ -0,0 +1,399 @@ +/* eslint-disable camelcase */ +import { + RefElement, + isStringElement, + isMemberElement, + IdentityManager, + visit, + toValue, + cloneDeep, +} from '@swagger-api/apidom-core'; +import { ApiDOMError } from '@swagger-api/apidom-error'; +import { + isJSONSchemaElement, + isBooleanJsonSchemaElement, + JSONSchemaElement, + getNodeType, + keyMap, +} from '@swagger-api/apidom-ns-json-schema-2020-12'; +import { + evaluate as jsonPointerEvaluate, + URIFragmentIdentifier, +} from '@swagger-api/apidom-json-pointer/modern'; +import { + url, + MaximumDereferenceDepthError, + File, +} from '@swagger-api/apidom-reference/configuration/empty'; +import { + JSONSchema2020_12DereferenceVisitor, + resolveSchema$refField, + maybeRefractToSchemaElement, +} from '@swagger-api/apidom-reference/dereference/strategies/json-schema-2020-12'; +import { + isAnchor, + uriToAnchor, + evaluate as $anchorEvaluate, +} from '@swagger-api/apidom-reference/dereference/strategies/json-schema-2020-12/selectors/$anchor'; +import { + evaluate as uriEvaluate, + EvaluationJsonSchemaUriError, +} from '@swagger-api/apidom-reference/dereference/strategies/json-schema-2020-12/selectors/uri'; + +import toPath from '../utils/to-path.js'; +import getRootCause from '../utils/get-root-cause.js'; +import SchemaRefError from '../errors/SchemaRefError.js'; + +const visitAsync = visit[Symbol.for('nodejs.util.promisify.custom')]; + +// initialize element identity manager +const identityManager = new IdentityManager(); + +// custom mutation replacer +const mutationReplacer = (newElement, oldElement, key, parent) => { + if (isMemberElement(parent)) { + parent.value = newElement; // eslint-disable-line no-param-reassign + } else if (Array.isArray(parent)) { + parent[key] = newElement; // eslint-disable-line no-param-reassign + } +}; + +/** + * Dereferences the $ref keyword of JSON Schema 2020-12 schemas the way the OpenAPI 3.1 + * swagger-client visitor dereferences Schema Objects: $ref resolves against $id base URIs and + * $anchor (or $dynamicAnchor) plain name fragments. $dynamicRef is left in place. + */ +class JSONSchema2020_12SwaggerClientDereferenceVisitor extends JSONSchema2020_12DereferenceVisitor { + useCircularStructures; + + allowMetaPatches; + + basePath; + + constructor({ + allowMetaPatches = true, + useCircularStructures = false, + basePath = null, + ...rest + }) { + super(rest); + + this.allowMetaPatches = allowMetaPatches; + this.useCircularStructures = useCircularStructures; + this.basePath = basePath; + } + + async JSONSchema202012Element(referencingElement, key, parent, path, ancestors, link) { + try { + // skip current referencing schema as $ref keyword was not defined + if (!isStringElement(referencingElement.$ref)) { + // skip traversing this schema but traverse all it's child schemas + return undefined; + } + + // skip current referencing element as it's already been access + if (this.indirections.includes(referencingElement)) { + return false; + } + + const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]); + + // compute baseURI using rules around $id and $ref keywords + let reference = await this.toReference(url.unsanitize(this.reference.uri)); + let { uri: retrievalURI } = reference; + const $refBaseURI = resolveSchema$refField(retrievalURI, referencingElement); + const $refBaseURIStrippedHash = url.stripHash($refBaseURI); + const file = new File({ uri: $refBaseURIStrippedHash }); + const isUnknownURI = !this.options.resolve.resolvers.some((r) => r.canRead(file)); + const isURL = !isUnknownURI; + let isInternalReference = url.stripHash(this.reference.uri) === $refBaseURI; + let isExternalReference = !isInternalReference; + + this.indirections.push(referencingElement); + + // determining reference, proper evaluation and selection mechanism + let referencedElement; + + try { + if (isUnknownURI || isURL) { + // we're dealing with canonical URI or URL with possible fragment + retrievalURI = this.toBaseURI($refBaseURI); + const selector = $refBaseURI; + const referenceAsSchema = maybeRefractToSchemaElement(reference.value.result); + referencedElement = uriEvaluate(selector, referenceAsSchema); + referencedElement = maybeRefractToSchemaElement(referencedElement); + referencedElement.id = identityManager.identify(referencedElement); + + // ignore resolving internal schemas + if (!this.options.resolve.internal && isInternalReference) { + // skip traversing this schema element but traverse all it's child elements + return undefined; + } + // ignore resolving external schemas + if (!this.options.resolve.external && isExternalReference) { + // skip traversing this schema element but traverse all it's child elements + return undefined; + } + } else { + // we're assuming here that we're dealing with JSON Pointer here + retrievalURI = this.toBaseURI($refBaseURI); + isInternalReference = url.stripHash(this.reference.uri) === retrievalURI; + isExternalReference = !isInternalReference; + + // ignore resolving internal schemas + if (!this.options.resolve.internal && isInternalReference) { + // skip traversing this schema element but traverse all it's child elements + return undefined; + } + // ignore resolving external schemas + if (!this.options.resolve.external && isExternalReference) { + // skip traversing this schema element but traverse all it's child elements + return undefined; + } + + reference = await this.toReference(url.unsanitize($refBaseURI)); + const selector = URIFragmentIdentifier.fromURIReference($refBaseURI); + const referenceAsSchema = maybeRefractToSchemaElement(reference.value.result); + referencedElement = jsonPointerEvaluate(referenceAsSchema, selector); + referencedElement = maybeRefractToSchemaElement(referencedElement); + referencedElement.id = identityManager.identify(referencedElement); + } + } catch (error) { + /** + * No schema with $id=URL was found, so we're going to try to resolve + * the URL and assume the returned response is a JSON Schema. + */ + if (isURL && error instanceof EvaluationJsonSchemaUriError) { + if (isAnchor(uriToAnchor($refBaseURI))) { + // we're dealing with JSON Schema $anchor (or $dynamicAnchor) here + isInternalReference = url.stripHash(this.reference.uri) === retrievalURI; + isExternalReference = !isInternalReference; + + // ignore resolving internal schemas + if (!this.options.resolve.internal && isInternalReference) { + // skip traversing this schema element but traverse all it's child elements + return undefined; + } + // ignore resolving external schemas + if (!this.options.resolve.external && isExternalReference) { + // skip traversing this schema element but traverse all it's child elements + return undefined; + } + + reference = await this.toReference(url.unsanitize($refBaseURI)); + const selector = uriToAnchor($refBaseURI); + const referenceAsSchema = maybeRefractToSchemaElement(reference.value.result); + referencedElement = $anchorEvaluate(selector, referenceAsSchema); + referencedElement = maybeRefractToSchemaElement(referencedElement); + referencedElement.id = identityManager.identify(referencedElement); + } else { + // we're assuming here that we're dealing with JSON Pointer here + retrievalURI = this.toBaseURI(toValue($refBaseURI)); + isInternalReference = url.stripHash(this.reference.uri) === retrievalURI; + isExternalReference = !isInternalReference; + + // ignore resolving internal schemas + if (!this.options.resolve.internal && isInternalReference) { + // skip traversing this schema element but traverse all it's child elements + return undefined; + } + // ignore resolving external schemas + if (!this.options.resolve.external && isExternalReference) { + // skip traversing this schema element but traverse all it's child elements + return undefined; + } + + reference = await this.toReference(url.unsanitize($refBaseURI)); + const selector = URIFragmentIdentifier.fromURIReference($refBaseURI); + const referenceAsSchema = maybeRefractToSchemaElement(reference.value.result); + referencedElement = jsonPointerEvaluate(referenceAsSchema, selector); + referencedElement = maybeRefractToSchemaElement(referencedElement); + referencedElement.id = identityManager.identify(referencedElement); + } + } else { + throw error; + } + } + + // detect direct or indirect reference + if (referencingElement === referencedElement) { + throw new ApiDOMError('Recursive JSON Schema reference detected'); + } + + // detect maximum depth of dereferencing + if (this.indirections.length > this.options.dereference.maxDepth) { + throw new MaximumDereferenceDepthError( + `Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"` + ); + } + + // detect second deep dive into the same fragment and avoid it + if (ancestorsLineage.includes(referencedElement)) { + reference.refSet.circular = true; + + if (this.options.dereference.circular === 'error') { + throw new ApiDOMError('Circular reference detected'); + } else if (this.options.dereference.circular === 'replace') { + const refElement = new RefElement(referencedElement.id, { + type: 'json-schema', + uri: reference.uri, + $ref: toValue(referencingElement.$ref), + baseURI: url.resolve(retrievalURI, $refBaseURI), + referencingElement, + }); + const replacer = + this.options.dereference.strategyOpts['json-schema-2020-12']?.circularReplacer ?? + this.options.dereference.circularReplacer; + const replacement = replacer(refElement); + + link.replaceWith(replacement, mutationReplacer); + + return !parent ? replacement : false; + } + } + + /** + * Dive deep into the fragment. + * + * Cases to consider: + * 1. We're crossing document boundary + * 2. Fragment is from non-root document + * 3. Fragment is a schema with $ref keyword. We need to follow it to get the eventual value + * 4. We are dereferencing the fragment lazily/eagerly depending on circular mode + */ + const isNonRootDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri; + const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular); + if ( + (isExternalReference || + isNonRootDocument || + (isJSONSchemaElement(referencedElement) && isStringElement(referencedElement.$ref)) || + shouldDetectCircular) && + !ancestorsLineage.includesCycle(referencedElement) + ) { + // append referencing schema to ancestors lineage + directAncestors.add(referencingElement); + + // dive deep into the fragment + const mergeVisitor = new JSONSchema2020_12SwaggerClientDereferenceVisitor({ + reference, + namespace: this.namespace, + indirections: [...this.indirections], + options: this.options, + refractCache: this.refractCache, + useCircularStructures: this.useCircularStructures, + allowMetaPatches: this.allowMetaPatches, + ancestors: ancestorsLineage, + basePath: this.basePath ?? [ + ...toPath([...ancestors, parent, referencingElement]), + '$ref', + ], + }); + referencedElement = await visitAsync(referencedElement, mergeVisitor, { + keyMap, + nodeTypeGetter: getNodeType, + }); + + // remove referencing schema from ancestors lineage + directAncestors.delete(referencingElement); + } + + this.indirections.pop(); + + /** + * The document root is a schema: when it is the referencing schema, its replacement + * must stay the result of the parse result, as later references into the document + * are evaluated against it. + */ + const isParseResultRoot = referencingElement.classes.includes('result'); + + if (isBooleanJsonSchemaElement(referencedElement)) { + const booleanJsonSchemaElement = cloneDeep(referencedElement); + if (isParseResultRoot && !booleanJsonSchemaElement.classes.includes('result')) { + booleanJsonSchemaElement.classes.push('result'); + } + // annotate referenced element with info about original referencing element + booleanJsonSchemaElement.setMetaProperty('ref-fields', { + $ref: toValue(referencingElement.$ref), + }); + // annotate referenced element with info about origin + booleanJsonSchemaElement.setMetaProperty('ref-origin', reference.uri); + // annotate fragment with info about referencing element + booleanJsonSchemaElement.setMetaProperty( + 'ref-referencing-element-id', + cloneDeep(identityManager.identify(referencingElement)) + ); + + link.replaceWith(booleanJsonSchemaElement, mutationReplacer); + + return !parent ? booleanJsonSchemaElement : false; + } + + /** + * Creating a new version of the schema by merging keywords from the referenced schema with the referencing one. + */ + if (isJSONSchemaElement(referencedElement)) { + // merge keywords from referenced schema with referencing schema + const mergedElement = new JSONSchemaElement( + [...referencedElement.content], + cloneDeep(referencedElement.meta), + cloneDeep(referencedElement.attributes) + ); + // existing keywords from referencing schema overrides ones from referenced schema + referencingElement.forEach((value, keyElement, item) => { + mergedElement.remove(toValue(keyElement)); + mergedElement.content.push(item); + }); + mergedElement.remove('$ref'); + if (isParseResultRoot && !mergedElement.classes.includes('result')) { + mergedElement.classes.push('result'); + } + // annotate referenced element with info about original referencing element + mergedElement.setMetaProperty('ref-fields', { + $ref: toValue(referencingElement.$ref), + }); + // annotate fragment with info about origin + mergedElement.setMetaProperty('ref-origin', reference.uri); + // annotate fragment with info about referencing element + mergedElement.setMetaProperty( + 'ref-referencing-element-id', + cloneDeep(identityManager.identify(referencingElement)) + ); + + // allowMetaPatches option processing + if (this.allowMetaPatches) { + // apply meta patch only when not already applied + if (typeof mergedElement.get('$$ref') === 'undefined') { + const baseURI = url.resolve(retrievalURI, $refBaseURI); + mergedElement.set('$$ref', baseURI); + } + } + + referencedElement = mergedElement; + } + + /** + * Transclude referencing element with merged referenced element. + */ + link.replaceWith(referencedElement, mutationReplacer); + + /** + * We're at the root of the tree, so we're just replacing the entire tree. + */ + return !parent ? referencedElement : undefined; + } catch (error) { + const rootCause = getRootCause(error); + const wrappedError = new SchemaRefError(`Could not resolve reference: ${rootCause.message}`, { + baseDoc: this.reference.uri, + $ref: toValue(referencingElement.$ref), + fullPath: this.basePath ?? [...toPath([...ancestors, parent, referencingElement]), '$ref'], + cause: rootCause, + }); + this.options.dereference.dereferenceOpts?.errors?.push?.(wrappedError); + + return undefined; + } + } +} + +export default JSONSchema2020_12SwaggerClientDereferenceVisitor; +/* eslint-enable camelcase */ diff --git a/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/properties.js b/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/properties.js new file mode 100644 index 000000000..5fc99fa4c --- /dev/null +++ b/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/properties.js @@ -0,0 +1,36 @@ +import { isObjectElement, toValue } from '@swagger-api/apidom-core'; + +import toPath from '../utils/to-path.js'; + +class ModelPropertyMacroVisitor { + modelPropertyMacro; + + options; + + JSONSchema202012Element = { + leave: (schemaElement, key, parent, path, ancestors) => { + if (typeof schemaElement.properties === 'undefined') return; + if (!isObjectElement(schemaElement.properties)) return; + + schemaElement.properties.forEach((property) => { + if (!isObjectElement(property)) return; + + try { + const macroValue = this.modelPropertyMacro(toValue(property)); + property.set('default', macroValue); + } catch (error) { + const macroError = new Error(error, { cause: error }); + macroError.fullPath = [...toPath([...ancestors, parent, schemaElement]), 'properties']; + this.options.dereference.dereferenceOpts?.errors?.push?.(macroError); + } + }); + }, + }; + + constructor({ modelPropertyMacro, options }) { + this.modelPropertyMacro = modelPropertyMacro; + this.options = options; + } +} + +export default ModelPropertyMacroVisitor; diff --git a/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/root.js b/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/root.js new file mode 100644 index 000000000..990765f75 --- /dev/null +++ b/src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/root.js @@ -0,0 +1,39 @@ +import { mergeAllVisitors } from '@swagger-api/apidom-core'; +import { getNodeType } from '@swagger-api/apidom-ns-json-schema-2020-12'; + +import ModelPropertyMacroVisitor from './properties.js'; +import AllOfVisitor from './all-of.js'; +import JSONSchema2020_12SwaggerClientDereferenceVisitor from './dereference.js'; // eslint-disable-line camelcase + +const mergeAllVisitorsAsync = mergeAllVisitors[Symbol.for('nodejs.util.promisify.custom')]; + +/** + * Same visitors as for OpenAPI 3.1 Schema Objects, so that a schema resolves the same + * standalone and inside an OpenAPI 3.1 document. There is no parameter macro: + * JSON Schema documents have no Parameter Objects. + */ +class RootVisitor { + constructor({ modelPropertyMacro, mode, options, ...rest }) { + const visitors = []; + + visitors.push( + new JSONSchema2020_12SwaggerClientDereferenceVisitor({ + ...rest, + options, + }) + ); + + if (typeof modelPropertyMacro === 'function') { + visitors.push(new ModelPropertyMacroVisitor({ modelPropertyMacro, options })); + } + + if (mode !== 'strict') { + visitors.push(new AllOfVisitor({ options })); + } + + const mergedVisitor = mergeAllVisitorsAsync(visitors, { nodeTypeGetter: getNodeType }); + Object.assign(this, mergedVisitor); + } +} + +export default RootVisitor; diff --git a/src/resolver/apidom/reference/parse/parsers/json-schema-json-2020-12/index.js b/src/resolver/apidom/reference/parse/parsers/json-schema-json-2020-12/index.js new file mode 100644 index 000000000..71454ffa3 --- /dev/null +++ b/src/resolver/apidom/reference/parse/parsers/json-schema-json-2020-12/index.js @@ -0,0 +1,73 @@ +/* eslint-disable camelcase */ +import { ParseResultElement } from '@swagger-api/apidom-core'; +import { ParserError, Parser } from '@swagger-api/apidom-reference/configuration/empty'; +import { + mediaTypes, + refract, + JSONSchema202012MediaTypes, +} from '@swagger-api/apidom-ns-json-schema-2020-12'; + +import { isJSONSchema202012 } from '../../../../../../helpers/json-schema-predicates.js'; + +class JSONSchemaJSON2020_12Parser extends Parser { + constructor(options = {}) { + super({ + name: 'json-schema-json-2020-12-swagger-client', + mediaTypes: new JSONSchema202012MediaTypes( + ...mediaTypes.filterByFormat('generic'), + ...mediaTypes.filterByFormat('json') + ), + ...options, + }); + } + + async canParse(file) { + const hasSupportedFileExtension = + this.fileExtensions.length === 0 ? true : this.fileExtensions.includes(file.extension); + const hasSupportedMediaType = this.mediaTypes.includes(file.mediaType); + + if (!hasSupportedFileExtension) return false; + if (hasSupportedMediaType) return true; + if (!hasSupportedMediaType) { + /** + * Detection uses the parsed document root: a text match would also + * claim documents with a nested $schema only. + */ + try { + return isJSONSchema202012(JSON.parse(file.toString())); + } catch (error) { + return false; + } + } + return false; + } + + async parse(file) { + if (this.sourceMap) { + throw new ParserError( + "json-schema-json-2020-12-swagger-client parser plugin doesn't support sourceMaps option" + ); + } + + const parseResultElement = new ParseResultElement(); + const source = file.toString(); + + // allow empty files + if (this.allowEmpty && source.trim() === '') { + return parseResultElement; + } + + try { + const pojo = JSON.parse(source); + const element = refract(pojo, this.refractorOpts); + element.classes.push('result'); + parseResultElement.push(element); + return parseResultElement; + } catch (error) { + throw new ParserError(`Error parsing "${file.uri}"`, { cause: error }); + } + } +} + +export default JSONSchemaJSON2020_12Parser; +/* eslint-enable camelcase */ diff --git a/src/resolver/apidom/reference/parse/parsers/json-schema-yaml-2020-12/index.js b/src/resolver/apidom/reference/parse/parsers/json-schema-yaml-2020-12/index.js new file mode 100644 index 000000000..f9b88368b --- /dev/null +++ b/src/resolver/apidom/reference/parse/parsers/json-schema-yaml-2020-12/index.js @@ -0,0 +1,74 @@ +/* eslint-disable camelcase */ +import YAML, { JSON_SCHEMA } from 'js-yaml'; +import { ParseResultElement } from '@swagger-api/apidom-core'; +import { ParserError, Parser } from '@swagger-api/apidom-reference/configuration/empty'; +import { + mediaTypes, + refract, + JSONSchema202012MediaTypes, +} from '@swagger-api/apidom-ns-json-schema-2020-12'; + +import { isJSONSchema202012 } from '../../../../../../helpers/json-schema-predicates.js'; + +class JSONSchemaYAML2020_12Parser extends Parser { + constructor(options = {}) { + super({ + name: 'json-schema-yaml-2020-12-swagger-client', + mediaTypes: new JSONSchema202012MediaTypes( + ...mediaTypes.filterByFormat('generic'), + ...mediaTypes.filterByFormat('yaml') + ), + ...options, + }); + } + + async canParse(file) { + const hasSupportedFileExtension = + this.fileExtensions.length === 0 ? true : this.fileExtensions.includes(file.extension); + const hasSupportedMediaType = this.mediaTypes.includes(file.mediaType); + + if (!hasSupportedFileExtension) return false; + if (hasSupportedMediaType) return true; + if (!hasSupportedMediaType) { + /** + * Detection uses the parsed document root: a text match would also + * claim documents with a nested $schema only. + */ + try { + return isJSONSchema202012(YAML.load(file.toString(), { schema: JSON_SCHEMA })); + } catch (error) { + return false; + } + } + return false; + } + + async parse(file) { + if (this.sourceMap) { + throw new ParserError( + "json-schema-yaml-2020-12-swagger-client parser plugin doesn't support sourceMaps option" + ); + } + + const parseResultElement = new ParseResultElement(); + const source = file.toString(); + + try { + const pojo = YAML.load(source, { schema: JSON_SCHEMA }); + + if (this.allowEmpty && typeof pojo === 'undefined') { + return parseResultElement; + } + + const element = refract(pojo, this.refractorOpts); + element.classes.push('result'); + parseResultElement.push(element); + return parseResultElement; + } catch (error) { + throw new ParserError(`Error parsing "${file.uri}"`, { cause: error }); + } + } +} + +export default JSONSchemaYAML2020_12Parser; +/* eslint-enable camelcase */ diff --git a/src/resolver/strategies/json-schema-2020-12-apidom/index.js b/src/resolver/strategies/json-schema-2020-12-apidom/index.js new file mode 100644 index 000000000..068e8988a --- /dev/null +++ b/src/resolver/strategies/json-schema-2020-12-apidom/index.js @@ -0,0 +1,18 @@ +import resolveJSONSchema202012Strategy from './resolve.js'; +import normalize from './normalize.js'; +import { isJSONSchema202012 } from '../../../helpers/json-schema-predicates.js'; + +const jsonSchema202012ApiDOMStrategy = { + name: 'json-schema-2020-12-apidom', + match(spec) { + return isJSONSchema202012(spec); + }, + normalize(spec) { + return normalize(spec); + }, + async resolve(options) { + return resolveJSONSchema202012Strategy(options); + }, +}; + +export default jsonSchema202012ApiDOMStrategy; diff --git a/src/resolver/strategies/json-schema-2020-12-apidom/normalize.js b/src/resolver/strategies/json-schema-2020-12-apidom/normalize.js new file mode 100644 index 000000000..2f1b2470e --- /dev/null +++ b/src/resolver/strategies/json-schema-2020-12-apidom/normalize.js @@ -0,0 +1,10 @@ +/** + * Standalone JSON Schema documents have no operations, parameters or security requirements, + * so the OpenAPI normalization plugins have nothing to act on: normalization is the identity. + * + * The subtree resolver skips normalization for every strategy but the OpenAPI 3.1+ ones; + * add this strategy to its list if normalization here ever does something. + */ +const normalize = (spec) => spec; + +export default normalize; diff --git a/src/resolver/strategies/json-schema-2020-12-apidom/resolve.js b/src/resolver/strategies/json-schema-2020-12-apidom/resolve.js new file mode 100644 index 000000000..926e971e1 --- /dev/null +++ b/src/resolver/strategies/json-schema-2020-12-apidom/resolve.js @@ -0,0 +1,178 @@ +/* eslint-disable camelcase */ +import { + ParseResultElement, + ObjectElement, + toValue, + transclude, + cloneDeep, +} from '@swagger-api/apidom-core'; +import { + compile as jsonPointerCompile, + evaluate as jsonPointerEvaluate, + JSONPointerEvaluateError, +} from '@swagger-api/apidom-json-pointer/modern'; +import { mediaTypes, refract } from '@swagger-api/apidom-ns-json-schema-2020-12'; +import { + dereferenceApiDOM, + url, + ReferenceSet, + Reference, + options as referenceOptions, +} from '@swagger-api/apidom-reference/configuration/empty'; +import BinaryParser from '@swagger-api/apidom-reference/parse/parsers/binary'; +import JSONSchema2020_12ResolveStrategy from '@swagger-api/apidom-reference/resolve/strategies/json-schema-2020-12'; + +import { DEFAULT_BASE_URL } from '../../../constants.js'; +import * as optionsUtil from '../../utils/options.js'; +import normalize from './normalize.js'; +import HTTPResolverSwaggerClient from '../../apidom/reference/resolve/resolvers/http-swagger-client/index.js'; +import JSONParser from '../../apidom/reference/parse/parsers/json/index.js'; +import YAMLParser from '../../apidom/reference/parse/parsers/yaml-1-2/index.js'; +import JSONSchemaJSON2020_12Parser from '../../apidom/reference/parse/parsers/json-schema-json-2020-12/index.js'; +import JSONSchemaYAML2020_12Parser from '../../apidom/reference/parse/parsers/json-schema-yaml-2020-12/index.js'; +import JSONSchema2020_12SwaggerClientDereferenceStrategy from '../../apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/index.js'; + +export const circularReplacer = (refElement) => { + const $refBaseURI = toValue(refElement.meta.get('baseURI')); + const referencingElement = refElement.meta.get('referencingElement'); + + /** + * Removing semantics from the absolutified referencing element by + * using generic ObjectElement to represent the reference. + */ + return new ObjectElement( + { $ref: $refBaseURI }, + cloneDeep(referencingElement.meta), + cloneDeep(referencingElement.attributes) + ); +}; + +const resolveJSONSchema202012Strategy = async (options) => { + const { + spec, + timeout, + redirects, + requestInterceptor, + responseInterceptor, + pathDiscriminator = [], + allowMetaPatches = false, + useCircularStructures = false, + skipNormalization = false, + modelPropertyMacro = null, + mode = 'non-strict', + } = options; + try { + const { cache } = resolveJSONSchema202012Strategy; + + // determining BaseURI + const cwd = url.isHttpUrl(url.cwd()) ? url.cwd() : DEFAULT_BASE_URL; + const retrievalURI = optionsUtil.retrievalURI(options); + const baseURI = url.resolve(cwd, retrievalURI); + + // prepare spec for dereferencing + let jsonSchemaElement; + if (cache.has(spec)) { + jsonSchemaElement = cache.get(spec); + } else { + /** + * The namespace refractor is used instead of `JSONSchemaElement.refract`: + * other namespaces building on JSON Schema 2020-12 (Arazzo) reassign that static method. + */ + jsonSchemaElement = refract(spec); + jsonSchemaElement.classes.push('result'); + cache.set(spec, jsonSchemaElement); + } + + const jsonSchemaParseResultElement = new ParseResultElement([jsonSchemaElement]); + + // prepare fragment for dereferencing + const jsonPointer = jsonPointerCompile(pathDiscriminator); + const jsonPointerURI = jsonPointer === '' ? '' : `#${jsonPointer}`; + const fragmentElement = jsonPointerEvaluate(jsonSchemaElement, jsonPointer); + + // prepare reference set for dereferencing + const jsonSchemaElementReference = new Reference({ + uri: baseURI, + value: jsonSchemaParseResultElement, + }); + const refSet = new ReferenceSet({ refs: [jsonSchemaElementReference] }); + if (jsonPointer !== '') refSet.rootRef = undefined; // reset root reference as we want fragment to become the root reference + + // prepare ancestors; needed for cases where fragment is not the document root + const ancestors = [new Set([fragmentElement])]; + + const errors = []; + const dereferenced = await dereferenceApiDOM(fragmentElement, { + resolve: { + /** + * swagger-client only supports resolving HTTP(S) URLs or spec objects. + * If runtime env is detected as non-browser one, + * and baseURI was not provided as part of resolver options, + * then below baseURI check will make sure that constant HTTPS URL is used as baseURI. + */ + baseURI: `${baseURI}${jsonPointerURI}`, + resolvers: [ + new HTTPResolverSwaggerClient({ + timeout: timeout || 10000, + redirects: redirects || 10, + }), + ], + resolverOpts: { + swaggerHTTPClientConfig: { + requestInterceptor, + responseInterceptor, + }, + }, + strategies: [new JSONSchema2020_12ResolveStrategy()], + }, + parse: { + mediaType: mediaTypes.latest(), + /** + * The JSON Schema 2020-12 parsers detect documents by their root $schema. They belong + * only to this strategy: parser lists used by the OpenAPI strategies must not contain + * them, or external files of OpenAPI documents that declare a root $schema would be + * refracted as JSON Schema documents. + */ + parsers: [ + new JSONSchemaJSON2020_12Parser({ allowEmpty: false, sourceMap: false }), + new JSONSchemaYAML2020_12Parser({ allowEmpty: false, sourceMap: false }), + new JSONParser({ allowEmpty: false, sourceMap: false }), + new YAMLParser({ allowEmpty: false, sourceMap: false }), + new BinaryParser({ allowEmpty: false, sourceMap: false }), + ], + }, + dereference: { + maxDepth: 100, + strategies: [ + new JSONSchema2020_12SwaggerClientDereferenceStrategy({ + allowMetaPatches, + useCircularStructures, + modelPropertyMacro, + mode, + ancestors, + }), + ], + refSet, + dereferenceOpts: { errors }, + immutable: false, + circular: useCircularStructures ? 'ignore' : 'replace', + circularReplacer: useCircularStructures + ? referenceOptions.dereference.circularReplacer + : circularReplacer, + }, + }); + const transcluded = transclude(fragmentElement, dereferenced, jsonSchemaElement); + const normalized = skipNormalization ? transcluded : normalize(transcluded); + + return { spec: toValue(normalized), errors }; + } catch (error) { + if (error instanceof JSONPointerEvaluateError) { + return { spec, errors: [] }; + } + throw error; + } +}; +resolveJSONSchema202012Strategy.cache = new WeakMap(); + +export default resolveJSONSchema202012Strategy; +/* eslint-enable camelcase */ diff --git a/test/helpers/json-schema-predicates.js b/test/helpers/json-schema-predicates.js new file mode 100644 index 000000000..f2e98080d --- /dev/null +++ b/test/helpers/json-schema-predicates.js @@ -0,0 +1,137 @@ +import { + isJSONSchema202012, + JSON_SCHEMA_2020_12_DIALECT, +} from '../../src/helpers/json-schema-predicates.js'; + +describe('helpers', () => { + describe('json-schema-predicates', () => { + describe('isJSONSchema202012', () => { + test('should accept the JSON Schema 2020-12 dialect identifier', () => { + expect( + isJSONSchema202012({ $schema: 'https://json-schema.org/draft/2020-12/schema' }) + ).toBe(true); + expect( + isJSONSchema202012({ + $schema: 'https://json-schema.org/draft/2020-12/schema', + $id: 'https://example.com/schema.json', + type: 'object', + }) + ).toBe(true); + }); + + test('should accept the identifier with an empty fragment', () => { + expect( + isJSONSchema202012({ $schema: 'https://json-schema.org/draft/2020-12/schema#' }) + ).toBe(true); + }); + + test('should refuse the http scheme', () => { + expect(isJSONSchema202012({ $schema: 'http://json-schema.org/draft/2020-12/schema' })).toBe( + false + ); + expect( + isJSONSchema202012({ $schema: 'http://json-schema.org/draft/2020-12/schema#' }) + ).toBe(false); + }); + + test('should refuse a non-empty fragment and other variations of the identifier', () => { + // derived from the identifier: the variants are not URLs anyone can retrieve + expect(JSON_SCHEMA_2020_12_DIALECT).toBe('https://json-schema.org/draft/2020-12/schema'); + expect(isJSONSchema202012({ $schema: `${JSON_SCHEMA_2020_12_DIALECT}#foo` })).toBe(false); + expect(isJSONSchema202012({ $schema: `${JSON_SCHEMA_2020_12_DIALECT}##` })).toBe(false); + expect(isJSONSchema202012({ $schema: `${JSON_SCHEMA_2020_12_DIALECT}/` })).toBe(false); + expect( + isJSONSchema202012({ $schema: JSON_SCHEMA_2020_12_DIALECT.replace(/schema$/, 'Schema') }) + ).toBe(false); + expect(isJSONSchema202012({ $schema: ` ${JSON_SCHEMA_2020_12_DIALECT}` })).toBe(false); + }); + + test('should refuse other dialects', () => { + expect( + isJSONSchema202012({ $schema: 'https://json-schema.org/draft/2019-09/schema' }) + ).toBe(false); + expect(isJSONSchema202012({ $schema: 'http://json-schema.org/draft-07/schema#' })).toBe( + false + ); + expect(isJSONSchema202012({ $schema: 'http://json-schema.org/draft-04/schema#' })).toBe( + false + ); + }); + + test('should refuse custom meta-schemas and OpenAPI dialects', () => { + expect(isJSONSchema202012({ $schema: 'https://example.com/meta/schema' })).toBe(false); + expect( + isJSONSchema202012({ $schema: 'https://spec.openapis.org/oas/3.1/dialect/base' }) + ).toBe(false); + }); + + test('should refuse a $schema that is not a string', () => { + expect(isJSONSchema202012({ $schema: null })).toBe(false); + expect(isJSONSchema202012({ $schema: 2020 })).toBe(false); + expect( + isJSONSchema202012({ $schema: { $ref: 'https://json-schema.org/draft/2020-12/schema' } }) + ).toBe(false); + }); + + test('should refuse a document without a root $schema', () => { + expect(isJSONSchema202012({ type: 'object' })).toBe(false); + expect(isJSONSchema202012({})).toBe(false); + }); + + test('should refuse a $schema in a nested schema only', () => { + expect( + isJSONSchema202012({ + type: 'object', + properties: { + a: { $schema: 'https://json-schema.org/draft/2020-12/schema' }, + }, + }) + ).toBe(false); + }); + + test('should refuse OpenAPI and Swagger documents with a root $schema', () => { + expect( + isJSONSchema202012({ + openapi: '3.1.0', + $schema: 'https://json-schema.org/draft/2020-12/schema', + }) + ).toBe(false); + expect( + isJSONSchema202012({ + swagger: '2.0', + $schema: 'https://json-schema.org/draft/2020-12/schema', + }) + ).toBe(false); + }); + + test('should refuse an OpenAPI 3.1 document with $schema in a Schema Object', () => { + expect( + isJSONSchema202012({ + openapi: '3.1.0', + info: { title: 'test', version: '1.0.0' }, + components: { + schemas: { + User: { + $schema: 'https://json-schema.org/draft/2020-12/schema', + type: 'object', + }, + }, + }, + }) + ).toBe(false); + }); + + test('should refuse non-objects', () => { + expect(isJSONSchema202012(null)).toBe(false); + expect(isJSONSchema202012(undefined)).toBe(false); + expect(isJSONSchema202012(true)).toBe(false); + expect(isJSONSchema202012('https://json-schema.org/draft/2020-12/schema')).toBe(false); + expect(isJSONSchema202012(1)).toBe(false); + expect(isJSONSchema202012([])).toBe(false); + expect( + isJSONSchema202012([{ $schema: 'https://json-schema.org/draft/2020-12/schema' }]) + ).toBe(false); + }); + }); + }); +}); diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/__utils__/jest.local.setup.js b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/__utils__/jest.local.setup.js new file mode 100644 index 000000000..a0b2a9052 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/__utils__/jest.local.setup.js @@ -0,0 +1,47 @@ +/* eslint-disable camelcase */ +import { options } from '@swagger-api/apidom-reference/configuration/empty'; +import FileResolver from '@swagger-api/apidom-reference/resolve/resolvers/file'; +import BinaryParser from '@swagger-api/apidom-reference/parse/parsers/binary'; +import JSONSchema2020_12ResolveStrategy from '@swagger-api/apidom-reference/resolve/strategies/json-schema-2020-12'; +import JSONSchema2020_12DereferenceStrategy from '@swagger-api/apidom-reference/dereference/strategies/json-schema-2020-12'; + +import JSONParser from '../../../../../../../../src/resolver/apidom/reference/parse/parsers/json/index.js'; +import YAMLParser from '../../../../../../../../src/resolver/apidom/reference/parse/parsers/yaml-1-2/index.js'; +import JSONSchemaJSON2020_12Parser from '../../../../../../../../src/resolver/apidom/reference/parse/parsers/json-schema-json-2020-12/index.js'; +import JSONSchemaYAML2020_12Parser from '../../../../../../../../src/resolver/apidom/reference/parse/parsers/json-schema-yaml-2020-12/index.js'; +import HTTPResolverSwaggerClient from '../../../../../../../../src/resolver/apidom/reference/resolve/resolvers/http-swagger-client/index.js'; +import JSONSchema2020_12SwaggerClientDereferenceStrategy from '../../../../../../../../src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/index.js'; + +export const beforeAll = () => { + // configure custom parser plugins globally + options.parse.parsers = [ + new JSONSchemaJSON2020_12Parser({ allowEmpty: false, sourceMap: false }), + new JSONSchemaYAML2020_12Parser({ allowEmpty: false, sourceMap: false }), + new JSONParser({ allowEmpty: false, sourceMap: false }), + new YAMLParser({ allowEmpty: false, sourceMap: false }), + new BinaryParser({ allowEmpty: false, sourceMap: false }), + ]; + + // configure custom resolver plugins globally + options.resolve.resolvers = [ + new FileResolver({ fileAllowList: ['*'] }), + new HTTPResolverSwaggerClient(), + ]; + + // configure custom resolver strategies globally + options.resolve.strategies = [new JSONSchema2020_12ResolveStrategy()]; + + // configure custom dereference strategy globally + options.dereference.strategies = [ + new JSONSchema2020_12SwaggerClientDereferenceStrategy(), + new JSONSchema2020_12DereferenceStrategy(), + ]; +}; + +export const afterAll = () => { + options.parse.parsers = []; + options.resolve.resolvers = []; + options.resolve.strategies = []; + options.dereference.strategies = []; +}; +/* eslint-enable camelcase */ diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-external/dereferenced.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-external/dereferenced.json new file mode 100644 index 000000000..a41d7a516 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-external/dereferenced.json @@ -0,0 +1,18 @@ +[ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "user": { + "$anchor": "user", + "type": "object", + "properties": { + "profile": { + "$anchor": "profile", + "type": "object" + } + } + } + } + } +] diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-external/ex.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-external/ex.json new file mode 100644 index 000000000..d5243d3a8 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-external/ex.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "User": { + "$anchor": "user", + "type": "object", + "properties": { + "profile": { "$ref": "#profile" } + } + }, + "Profile": { + "$anchor": "profile", + "type": "object" + } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-external/root.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-external/root.json new file mode 100644 index 000000000..405ca4d9e --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-external/root.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "user": { "$ref": "./ex.json#user" } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-internal/dereferenced.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-internal/dereferenced.json new file mode 100644 index 000000000..bcaac1e9d --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-internal/dereferenced.json @@ -0,0 +1,58 @@ +[ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "user": { + "$anchor": "user", + "type": "object", + "properties": { + "profile": { + "$anchor": "profile", + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } + }, + "profile": { + "$anchor": "profile", + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + }, + "$defs": { + "User": { + "$anchor": "user", + "type": "object", + "properties": { + "profile": { + "$anchor": "profile", + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } + }, + "Profile": { + "$anchor": "profile", + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } + } +] diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-internal/root.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-internal/root.json new file mode 100644 index 000000000..ec26251b9 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-internal/root.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "user": { "$ref": "#user" }, + "profile": { "$ref": "#profile" } + }, + "$defs": { + "User": { + "$anchor": "user", + "type": "object", + "properties": { + "profile": { "$ref": "#profile" } + } + }, + "Profile": { + "$anchor": "profile", + "type": "object", + "properties": { + "name": { "type": "string" } + } + } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/boolean-json-schema/dereferenced.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/boolean-json-schema/dereferenced.json new file mode 100644 index 000000000..bd9e88339 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/boolean-json-schema/dereferenced.json @@ -0,0 +1,14 @@ +[ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "any": true, + "none": false + }, + "$defs": { + "Any": true, + "None": false + } + } +] diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/boolean-json-schema/root.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/boolean-json-schema/root.json new file mode 100644 index 000000000..9edc7f115 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/boolean-json-schema/root.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "any": { "$ref": "#/$defs/Any" }, + "none": { "$ref": "#/$defs/None" } + }, + "$defs": { + "Any": true, + "None": false + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/cycle-external/ex.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/cycle-external/ex.json new file mode 100644 index 000000000..aa416fad6 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/cycle-external/ex.json @@ -0,0 +1,16 @@ +{ + "$defs": { + "User": { + "type": "object", + "properties": { + "profile": { "$ref": "#/$defs/Profile" } + } + }, + "Profile": { + "type": "object", + "properties": { + "parent": { "$ref": "#/$defs/User" } + } + } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/cycle-external/root.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/cycle-external/root.json new file mode 100644 index 000000000..11ac726d9 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/cycle-external/root.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "user": { "$ref": "./ex.json#/$defs/User" } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/cycle-internal/root.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/cycle-internal/root.json new file mode 100644 index 000000000..b50f59ed0 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/cycle-internal/root.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/User", + "$defs": { + "User": { + "type": "object", + "properties": { + "parent": { "$ref": "#/$defs/User" } + } + } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/dynamic-anchor/dereferenced.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/dynamic-anchor/dereferenced.json new file mode 100644 index 000000000..7b50ee274 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/dynamic-anchor/dereferenced.json @@ -0,0 +1,17 @@ +[ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/ref-dynamicAnchor-same-schema/root", + "type": "array", + "items": { + "$dynamicAnchor": "items", + "type": "string" + }, + "$defs": { + "foo": { + "$dynamicAnchor": "items", + "type": "string" + } + } + } +] diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/dynamic-anchor/root.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/dynamic-anchor/root.json new file mode 100644 index 000000000..70c4475af --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/dynamic-anchor/root.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/ref-dynamicAnchor-same-schema/root", + "type": "array", + "items": { "$ref": "#items" }, + "$defs": { + "foo": { + "$dynamicAnchor": "items", + "type": "string" + } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/dynamic-ref/dereferenced.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/dynamic-ref/dereferenced.json new file mode 100644 index 000000000..a47b11c51 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/dynamic-ref/dereferenced.json @@ -0,0 +1,40 @@ +[ + { + "type": "array", + "items": { + "$dynamicRef": "#items" + }, + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/typical-dynamic-resolution/root", + "$defs": { + "foo": { + "$dynamicAnchor": "items", + "type": "string" + }, + "list": { + "$id": "list", + "type": "array", + "items": { + "$dynamicRef": "#items" + }, + "$defs": { + "items": { + "$comment": "This is only needed to satisfy the bookending requirement", + "$dynamicAnchor": "items" + } + } + }, + "sameResource": { + "type": "array", + "items": { + "$dynamicRef": "#/$defs/foo" + } + }, + "unmatched": { + "items": { + "$dynamicRef": "#unknown" + } + } + } + } +] diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/dynamic-ref/root.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/dynamic-ref/root.json new file mode 100644 index 000000000..105b4b6fd --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/dynamic-ref/root.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/typical-dynamic-resolution/root", + "$ref": "list", + "$defs": { + "foo": { + "$dynamicAnchor": "items", + "type": "string" + }, + "list": { + "$id": "list", + "type": "array", + "items": { "$dynamicRef": "#items" }, + "$defs": { + "items": { + "$comment": "This is only needed to satisfy the bookending requirement", + "$dynamicAnchor": "items" + } + } + }, + "sameResource": { + "type": "array", + "items": { "$dynamicRef": "#/$defs/foo" } + }, + "unmatched": { + "items": { "$dynamicRef": "#unknown" } + } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/external-only/dereferenced.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/external-only/dereferenced.json new file mode 100644 index 000000000..b8c025f4d --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/external-only/dereferenced.json @@ -0,0 +1,29 @@ +[ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "address": { + "type": "object", + "properties": { + "street": { + "type": "string" + } + } + } + } + }, + "address": { + "type": "object", + "properties": { + "street": { + "type": "string" + } + } + } + } + } +] diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/external-only/ex.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/external-only/ex.json new file mode 100644 index 000000000..298285010 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/external-only/ex.json @@ -0,0 +1,16 @@ +{ + "$defs": { + "User": { + "type": "object", + "properties": { + "address": { "$ref": "#/$defs/Address" } + } + }, + "Address": { + "type": "object", + "properties": { + "street": { "type": "string" } + } + } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/external-only/root.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/external-only/root.json new file mode 100644 index 000000000..cc7defdbf --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/external-only/root.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "user": { "$ref": "./ex.json#/$defs/User" }, + "address": { "$ref": "./ex.json#/$defs/Address" } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-canonical/dereferenced.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-canonical/dereferenced.json new file mode 100644 index 000000000..94d539cc7 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-canonical/dereferenced.json @@ -0,0 +1,76 @@ +[ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/schemas/root.json", + "type": "object", + "properties": { + "user": { + "$id": "user.json", + "type": "object", + "properties": { + "address": { + "type": "object", + "properties": { + "street": { + "$anchor": "street", + "type": "string" + } + } + } + }, + "$defs": { + "Address": { + "type": "object", + "properties": { + "street": { + "$anchor": "street", + "type": "string" + } + } + } + } + }, + "address": { + "type": "object", + "properties": { + "street": { + "$anchor": "street", + "type": "string" + } + } + }, + "street": { + "$anchor": "street", + "type": "string" + } + }, + "$defs": { + "User": { + "$id": "user.json", + "type": "object", + "properties": { + "address": { + "type": "object", + "properties": { + "street": { + "$anchor": "street", + "type": "string" + } + } + } + }, + "$defs": { + "Address": { + "type": "object", + "properties": { + "street": { + "$anchor": "street", + "type": "string" + } + } + } + } + } + } + } +] diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-canonical/root.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-canonical/root.json new file mode 100644 index 000000000..809ea6cfe --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-canonical/root.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/schemas/root.json", + "type": "object", + "properties": { + "user": { "$ref": "user.json" }, + "address": { "$ref": "https://example.com/schemas/user.json#/$defs/Address" }, + "street": { "$ref": "user.json#street" } + }, + "$defs": { + "User": { + "$id": "user.json", + "type": "object", + "properties": { + "address": { "$ref": "#/$defs/Address" } + }, + "$defs": { + "Address": { + "type": "object", + "properties": { + "street": { "$anchor": "street", "type": "string" } + } + } + } + } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative-nested/dereferenced.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative-nested/dereferenced.json new file mode 100644 index 000000000..f14595b9a --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative-nested/dereferenced.json @@ -0,0 +1,58 @@ +[ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "user": { + "$id": "./nested/", + "type": "object", + "properties": { + "profile": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }, + "tag": { + "$id": "tag.json", + "type": "string" + } + }, + "$defs": { + "Tag": { + "$id": "tag.json", + "type": "string" + } + } + } + }, + "$defs": { + "User": { + "$id": "./nested/", + "type": "object", + "properties": { + "profile": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }, + "tag": { + "$id": "tag.json", + "type": "string" + } + }, + "$defs": { + "Tag": { + "$id": "tag.json", + "type": "string" + } + } + } + } + } +] diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative-nested/nested/ex.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative-nested/nested/ex.json new file mode 100644 index 000000000..63571f0a2 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative-nested/nested/ex.json @@ -0,0 +1,10 @@ +{ + "$defs": { + "Profile": { + "type": "object", + "properties": { + "name": { "type": "string" } + } + } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative-nested/root.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative-nested/root.json new file mode 100644 index 000000000..74897795b --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative-nested/root.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "user": { "$ref": "#/$defs/User" } + }, + "$defs": { + "User": { + "$id": "./nested/", + "type": "object", + "properties": { + "profile": { "$ref": "ex.json#/$defs/Profile" }, + "tag": { "$ref": "tag.json" } + }, + "$defs": { + "Tag": { + "$id": "tag.json", + "type": "string" + } + } + } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative/dereferenced.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative/dereferenced.json new file mode 100644 index 000000000..1edcdba9b --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative/dereferenced.json @@ -0,0 +1,46 @@ +[ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "user": { + "$id": "./nested/", + "type": "object", + "properties": { + "profile": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } + }, + "tag": { + "$id": "tag.json", + "type": "string" + } + }, + "$defs": { + "User": { + "$id": "./nested/", + "type": "object", + "properties": { + "profile": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } + }, + "Tag": { + "$id": "tag.json", + "type": "string" + } + } + } +] diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative/nested/ex.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative/nested/ex.json new file mode 100644 index 000000000..63571f0a2 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative/nested/ex.json @@ -0,0 +1,10 @@ +{ + "$defs": { + "Profile": { + "type": "object", + "properties": { + "name": { "type": "string" } + } + } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative/root.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative/root.json new file mode 100644 index 000000000..fff414f7c --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative/root.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "user": { "$ref": "#/$defs/User" }, + "tag": { "$ref": "tag.json" } + }, + "$defs": { + "User": { + "$id": "./nested/", + "type": "object", + "properties": { + "profile": { "$ref": "ex.json#/$defs/Profile" } + } + }, + "Tag": { + "$id": "tag.json", + "type": "string" + } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/internal-external/dereferenced.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/internal-external/dereferenced.json new file mode 100644 index 000000000..15c2d7fa2 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/internal-external/dereferenced.json @@ -0,0 +1,38 @@ +[ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "profile": { + "type": "object", + "properties": { + "avatar": { + "type": "string", + "format": "uri" + } + } + } + } + } + }, + "$defs": { + "User": { + "type": "object", + "properties": { + "profile": { + "type": "object", + "properties": { + "avatar": { + "type": "string", + "format": "uri" + } + } + } + } + } + } + } +] diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/internal-external/ex.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/internal-external/ex.json new file mode 100644 index 000000000..ca92da394 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/internal-external/ex.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "Profile": { + "type": "object", + "properties": { + "avatar": { "$ref": "#/$defs/Avatar" } + } + }, + "Avatar": { + "type": "string", + "format": "uri" + } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/internal-external/root.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/internal-external/root.json new file mode 100644 index 000000000..260b94b60 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/internal-external/root.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "user": { "$ref": "#/$defs/User" } + }, + "$defs": { + "User": { + "type": "object", + "properties": { + "profile": { "$ref": "./ex.json#/$defs/Profile" } + } + } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/internal-only/dereferenced.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/internal-only/dereferenced.json new file mode 100644 index 000000000..466ce5c6b --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/internal-only/dereferenced.json @@ -0,0 +1,60 @@ +[ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "profile": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } + }, + "users": { + "type": "array", + "items": { + "type": "object", + "properties": { + "profile": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } + } + } + }, + "$defs": { + "User": { + "type": "object", + "properties": { + "profile": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } + }, + "Profile": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } + } +] diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/internal-only/root.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/internal-only/root.json new file mode 100644 index 000000000..7c520ab3b --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/internal-only/root.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "user": { "$ref": "#/$defs/User" }, + "users": { "type": "array", "items": { "$ref": "#/$defs/User" } } + }, + "$defs": { + "User": { + "type": "object", + "properties": { + "profile": { "$ref": "#/$defs/Profile" } + } + }, + "Profile": { + "type": "object", + "properties": { + "name": { "type": "string" } + } + } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/invalid-pointer/dereferenced.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/invalid-pointer/dereferenced.json new file mode 100644 index 000000000..9f438446a --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/invalid-pointer/dereferenced.json @@ -0,0 +1,19 @@ +[ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "user": { + "$ref": "#/$defs/Missing" + }, + "profile": { + "type": "object" + } + }, + "$defs": { + "Profile": { + "type": "object" + } + } + } +] diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/invalid-pointer/root.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/invalid-pointer/root.json new file mode 100644 index 000000000..0cc734de5 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/invalid-pointer/root.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "user": { "$ref": "#/$defs/Missing" }, + "profile": { "$ref": "#/$defs/Profile" } + }, + "$defs": { + "Profile": { "type": "object" } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/merging-keywords/dereferenced.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/merging-keywords/dereferenced.json new file mode 100644 index 000000000..16bc43568 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/merging-keywords/dereferenced.json @@ -0,0 +1,19 @@ +[ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "user": { + "type": "object", + "description": "description of the referencing schema", + "readOnly": true + } + }, + "$defs": { + "User": { + "description": "description of the referenced schema", + "type": "object" + } + } + } +] diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/merging-keywords/root.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/merging-keywords/root.json new file mode 100644 index 000000000..71ba60db5 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/merging-keywords/root.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "user": { + "$ref": "#/$defs/User", + "description": "description of the referencing schema", + "readOnly": true + } + }, + "$defs": { + "User": { + "description": "description of the referenced schema", + "type": "object" + } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/root-ref/dereferenced.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/root-ref/dereferenced.json new file mode 100644 index 000000000..5d39ded54 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/root-ref/dereferenced.json @@ -0,0 +1,51 @@ +[ + { + "type": "object", + "properties": { + "profile": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }, + "tag": { + "$anchor": "tag", + "type": "string" + } + }, + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "User": { + "type": "object", + "properties": { + "profile": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }, + "tag": { + "$anchor": "tag", + "type": "string" + } + } + }, + "Profile": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }, + "Tag": { + "$anchor": "tag", + "type": "string" + } + } + } +] diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/root-ref/root.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/root-ref/root.json new file mode 100644 index 000000000..878282db2 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/root-ref/root.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/User", + "$defs": { + "User": { + "type": "object", + "properties": { + "profile": { "$ref": "#/$defs/Profile" }, + "tag": { "$ref": "#tag" } + } + }, + "Profile": { + "type": "object", + "properties": { + "name": { "type": "string" } + } + }, + "Tag": { + "$anchor": "tag", + "type": "string" + } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/unresolvable-reference/dereferenced.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/unresolvable-reference/dereferenced.json new file mode 100644 index 000000000..078c3aff5 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/unresolvable-reference/dereferenced.json @@ -0,0 +1,19 @@ +[ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "user": { + "$ref": "./missing.json#/$defs/User" + }, + "profile": { + "type": "object" + } + }, + "$defs": { + "Profile": { + "type": "object" + } + } + } +] diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/unresolvable-reference/root.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/unresolvable-reference/root.json new file mode 100644 index 000000000..187e88a49 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/unresolvable-reference/root.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "user": { "$ref": "./missing.json#/$defs/User" }, + "profile": { "$ref": "#/$defs/Profile" } + }, + "$defs": { + "Profile": { "type": "object" } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/index.js b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/index.js new file mode 100644 index 000000000..f1db343dd --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/index.js @@ -0,0 +1,270 @@ +import path from 'node:path'; +import { toValue } from '@swagger-api/apidom-core'; +import { + isJSONSchemaElement, + isBooleanJsonSchemaElement, + mediaTypes, +} from '@swagger-api/apidom-ns-json-schema-2020-12'; +import { evaluate } from '@swagger-api/apidom-json-pointer'; +import { dereference } from '@swagger-api/apidom-reference/configuration/empty'; + +import * as jestSetup from '../__utils__/jest.local.setup.js'; + +const rootFixturePath = path.join(__dirname, '__fixtures__'); + +const dereferenceFixture = (fixture, options = {}) => + dereference(path.join(rootFixturePath, fixture, 'root.json'), { + parse: { mediaType: mediaTypes.latest('json') }, + ...options, + }); + +const loadDereferenced = (fixture) => + globalThis.loadJsonFile(path.join(rootFixturePath, fixture, 'dereferenced.json')); + +const collect$dynamicRefs = (value, pointer = '', acc = []) => { + if (Array.isArray(value)) { + value.forEach((item, index) => collect$dynamicRefs(item, `${pointer}/${index}`, acc)); + } else if (value !== null && typeof value === 'object') { + Object.entries(value).forEach(([key, item]) => { + if (key === '$dynamicRef') acc.push([pointer, item]); + collect$dynamicRefs(item, `${pointer}/${key}`, acc); + }); + } + return acc; +}; + +describe('dereference', () => { + beforeAll(() => { + jestSetup.beforeAll(); + }); + + afterAll(() => { + jestSetup.afterAll(); + }); + + describe('strategies', () => { + describe('json-schema-2020-12-swagger-client', () => { + describe('JSON Schema - $ref keyword', () => { + describe('given schemas pointing internally only', () => { + test('should dereference', async () => { + const actual = await dereferenceFixture('internal-only'); + + expect(toValue(actual)).toEqual(loadDereferenced('internal-only')); + }); + + test('should apply semantics to the dereferenced schema', async () => { + const dereferenced = await dereferenceFixture('internal-only'); + const fragment = evaluate('/0/properties/user/properties/profile', dereferenced); + + expect(isJSONSchemaElement(fragment)).toBe(true); + }); + }); + + describe('given schemas pointing externally only', () => { + test('should dereference', async () => { + const actual = await dereferenceFixture('external-only'); + + expect(toValue(actual)).toEqual(loadDereferenced('external-only')); + }); + + test('should apply semantics to the external fragment', async () => { + const dereferenced = await dereferenceFixture('external-only'); + const fragment = evaluate('/0/properties/user', dereferenced); + + expect(isJSONSchemaElement(fragment)).toBe(true); + }); + }); + + describe('given schemas pointing internally and externally', () => { + test('should dereference', async () => { + const actual = await dereferenceFixture('internal-external'); + + expect(toValue(actual)).toEqual(loadDereferenced('internal-external')); + }); + }); + + describe('given schemas with internal cycles', () => { + test('should dereference', async () => { + const dereferenced = await dereferenceFixture('cycle-internal'); + const parent = evaluate('/0/properties/parent', dereferenced); + const cyclicParent = evaluate('/0/properties/parent/properties/parent', dereferenced); + + expect(parent).toStrictEqual(cyclicParent); + }); + }); + + describe('given schemas with external cycles', () => { + test('should dereference with circular structure', async () => { + const dereferenced = await dereferenceFixture('cycle-external'); + const parent = evaluate( + '/0/properties/user/properties/profile/properties/parent', + dereferenced + ); + + expect(toValue(parent)).toHaveProperty('type', 'object'); + expect(toValue(parent)).toHaveProperty('properties'); + }); + }); + + describe('given a $ref to an $anchor in the same document', () => { + test('should dereference', async () => { + const actual = await dereferenceFixture('anchor-internal'); + + expect(toValue(actual)).toEqual(loadDereferenced('anchor-internal')); + }); + }); + + describe('given a $ref to an $anchor in another document', () => { + test('should dereference', async () => { + const actual = await dereferenceFixture('anchor-external'); + + expect(toValue(actual)).toEqual(loadDereferenced('anchor-external')); + }); + }); + + describe('given a $ref to a $dynamicAnchor', () => { + test('should dereference it like a $ref to an $anchor', async () => { + const actual = await dereferenceFixture('dynamic-anchor'); + + expect(toValue(actual)).toEqual(loadDereferenced('dynamic-anchor')); + }); + }); + + describe('given $refs relative to an $id base URI', () => { + test('should dereference', async () => { + const actual = await dereferenceFixture('id-relative'); + + expect(toValue(actual)).toEqual(loadDereferenced('id-relative')); + }); + }); + + describe('given a $ref to an embedded schema whose relative $id is nested in another relative $id', () => { + /** + * apidom-reference's `uri` selector (OpenAPI 3.1 and JSON Schema 2020-12 alike) + * resolves the $id chain of candidate schemas against the $ref target instead of the + * document's base URI, so this $ref stays in place. When ApiDOM fixes the selector, + * this test starts failing: turn it into a regular test. + */ + test.failing('should dereference', async () => { + const actual = await dereferenceFixture('id-relative-nested'); + + expect(toValue(actual)).toEqual(loadDereferenced('id-relative-nested')); + }); + }); + + describe('given $refs to canonical URIs of embedded schema resources', () => { + test('should dereference without retrieving them', async () => { + const actual = await dereferenceFixture('id-canonical'); + + expect(toValue(actual)).toEqual(loadDereferenced('id-canonical')); + }); + }); + + describe('given $dynamicRef keywords', () => { + test('should leave every $dynamicRef in place', async () => { + const actual = await dereferenceFixture('dynamic-ref'); + const original = globalThis.loadJsonFile( + path.join(rootFixturePath, 'dynamic-ref', 'root.json') + ); + + expect(toValue(actual)).toEqual(loadDereferenced('dynamic-ref')); + expect(collect$dynamicRefs(original).sort()).toEqual([ + ['/$defs/list/items', '#items'], + ['/$defs/sameResource/items', '#/$defs/foo'], + ['/$defs/unmatched/items', '#unknown'], + ]); + // the root's $ref brings the list schema's items, its $dynamicRef unchanged + expect(collect$dynamicRefs(toValue(actual)[0]).sort()).toEqual([ + ['/$defs/list/items', '#items'], + ['/$defs/sameResource/items', '#/$defs/foo'], + ['/$defs/unmatched/items', '#unknown'], + ['/items', '#items'], + ]); + }); + }); + + describe('given a $ref in the document root', () => { + test('should dereference the root and the schemas within it', async () => { + const actual = await dereferenceFixture('root-ref'); + + expect(toValue(actual)).toEqual(loadDereferenced('root-ref')); + }); + + test('should keep the dereferenced root as the parse result', async () => { + const dereferenced = await dereferenceFixture('root-ref'); + + expect(isJSONSchemaElement(dereferenced.result)).toBe(true); + expect(toValue(dereferenced.result)).toEqual(loadDereferenced('root-ref')[0]); + }); + }); + + describe('given $refs to Boolean JSON Schemas', () => { + test('should dereference', async () => { + const actual = await dereferenceFixture('boolean-json-schema'); + + expect(toValue(actual)).toEqual(loadDereferenced('boolean-json-schema')); + }); + + test('should keep the Boolean JSON Schema semantics', async () => { + const dereferenced = await dereferenceFixture('boolean-json-schema'); + + expect(isBooleanJsonSchemaElement(evaluate('/0/properties/any', dereferenced))).toBe( + true + ); + }); + }); + + describe('given a referencing schema with other keywords', () => { + test('should merge them, the referencing ones winning', async () => { + const actual = await dereferenceFixture('merging-keywords'); + + expect(toValue(actual)).toEqual(loadDereferenced('merging-keywords')); + }); + }); + + describe('given an unresolvable reference', () => { + test('should leave it in place and dereference the rest', async () => { + const actual = await dereferenceFixture('unresolvable-reference'); + + expect(toValue(actual)).toEqual(loadDereferenced('unresolvable-reference')); + }); + + test('should collect the error', async () => { + const errors = []; + await dereferenceFixture('unresolvable-reference', { + dereference: { dereferenceOpts: { errors } }, + }); + + expect(errors).toHaveLength(1); + expect(errors[0]).toMatchObject({ + message: expect.stringMatching(/^Could not resolve reference/), + $ref: './missing.json#/$defs/User', + fullPath: ['properties', 'user', '$ref'], + }); + }); + }); + + describe('given a JSON Pointer that evaluates to nothing', () => { + test('should leave it in place and dereference the rest', async () => { + const actual = await dereferenceFixture('invalid-pointer'); + + expect(toValue(actual)).toEqual(loadDereferenced('invalid-pointer')); + }); + + test('should collect the error', async () => { + const errors = []; + await dereferenceFixture('invalid-pointer', { + dereference: { dereferenceOpts: { errors } }, + }); + + expect(errors).toHaveLength(1); + expect(errors[0]).toMatchObject({ + $ref: '#/$defs/Missing', + fullPath: ['properties', 'user', '$ref'], + }); + }); + }); + }); + }); + }); +}); diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/utils/get-root-cause.js b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/utils/get-root-cause.js new file mode 100644 index 000000000..ef13df4a7 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/utils/get-root-cause.js @@ -0,0 +1,70 @@ +import getRootCause from '../../../../../../../../src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/utils/get-root-cause.js'; + +describe('getRootCause', () => { + describe('given an error with no cause', () => { + test('should return the error itself', () => { + const error = new Error('original'); + + expect(getRootCause(error)).toBe(error); + }); + }); + + describe('given an error with cause explicitly set to null', () => { + test('should return the error itself', () => { + const error = new Error('original'); + error.cause = null; + + expect(getRootCause(error)).toBe(error); + }); + }); + + describe('given an error with cause set to undefined', () => { + test('should return the error itself', () => { + const error = new Error('original'); + error.cause = undefined; + + expect(getRootCause(error)).toBe(error); + }); + }); + + describe('given an error with a single cause', () => { + test('should return the direct cause', () => { + const root = new Error('root cause'); + const error = new Error('outer', { cause: root }); + + expect(getRootCause(error)).toBe(root); + }); + }); + + describe('given an error with a deeply nested cause chain', () => { + test('should return the deepest root cause', () => { + const root = new Error('root cause'); + const middle = new Error('middle', { cause: root }); + const outer = new Error('outer', { cause: middle }); + + expect(getRootCause(outer)).toBe(root); + }); + + test('should traverse a chain of arbitrary depth', () => { + const root = new Error('root'); + let current = root; + for (let i = 0; i < 10; i += 1) { + const next = new Error(`level ${i}`); + next.cause = current; + current = next; + } + + expect(getRootCause(current)).toBe(root); + }); + }); + + describe('given an error where an intermediate cause has null cause', () => { + test('should stop traversal at the null cause boundary', () => { + const root = new Error('root'); + root.cause = null; + const outer = new Error('outer', { cause: root }); + + expect(getRootCause(outer)).toBe(root); + }); + }); +}); diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/all-of.js b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/all-of.js new file mode 100644 index 000000000..4c0580a97 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/all-of.js @@ -0,0 +1,239 @@ +import { visit, toValue } from '@swagger-api/apidom-core'; +import { refract, getNodeType, keyMap } from '@swagger-api/apidom-ns-json-schema-2020-12'; + +import AllOfVisitor from '../../../../../../../../src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/all-of.js'; + +const $schema = 'https://json-schema.org/draft/2020-12/schema'; + +const buildOptions = (errors = []) => ({ + dereference: { dereferenceOpts: { errors } }, +}); + +const applyAllOf = ($defs, errors = []) => { + const element = refract({ $schema, $defs }); + const visitor = new AllOfVisitor({ options: buildOptions(errors) }); + return toValue(visit(element, visitor, { keyMap, nodeTypeGetter: getNodeType })).$defs; +}; + +describe('dereference', () => { + describe('strategies', () => { + describe('json-schema-2020-12-swagger-client', () => { + describe('visitors', () => { + describe('AllOfVisitor', () => { + describe('given a schema without allOf keyword', () => { + test('should leave the schema unchanged', () => { + expect(applyAllOf({ User: { type: 'object' } })).toEqual({ + User: { type: 'object' }, + }); + }); + }); + + describe('given allOf on the document root', () => { + test('should merge it into the root', () => { + const element = refract({ $schema, allOf: [{ type: 'string' }, { maxLength: 10 }] }); + const visitor = new AllOfVisitor({ options: buildOptions() }); + const result = visit(element, visitor, { keyMap, nodeTypeGetter: getNodeType }); + + expect(toValue(result)).toEqual({ $schema, type: 'string', maxLength: 10 }); + }); + }); + + describe('given allOf is not an array', () => { + test('should leave the schema unchanged', () => { + expect(applyAllOf({ User: { allOf: {} } })).toEqual({ User: { allOf: {} } }); + }); + + test('should collect an error', () => { + const errors = []; + + applyAllOf({ User: { allOf: {} } }, errors); + + expect(errors).toHaveLength(1); + expect(errors[0]).toMatchObject({ + message: expect.stringMatching(/^allOf must be an array/), + fullPath: ['$defs', 'User', 'allOf'], + }); + }); + }); + + describe('given allOf is an empty array', () => { + test('should remove the allOf keyword from the schema', () => { + expect(applyAllOf({ User: { allOf: [] } })).toEqual({ User: {} }); + }); + }); + + describe('given allOf contains a non-object item', () => { + test('should leave the schema unchanged', () => { + const $defs = { User: { allOf: [{ type: 'string' }, 2] } }; + + expect(applyAllOf($defs)).toEqual($defs); + }); + + test('should collect an error', () => { + const errors = []; + + applyAllOf({ User: { allOf: [{ type: 'string' }, 2] } }, errors); + + expect(errors).toHaveLength(1); + expect(errors[0]).toMatchObject({ + message: expect.stringMatching(/^Elements in allOf must be objects/), + fullPath: ['$defs', 'User', 'allOf'], + }); + }); + }); + + describe('given allOf contains a Boolean JSON Schema', () => { + test('should collect an error as for OpenAPI 3.1 Schema Objects', () => { + const errors = []; + const $defs = { User: { allOf: [{ type: 'string' }, true] } }; + + expect(applyAllOf($defs, errors)).toEqual($defs); + expect(errors).toHaveLength(1); + expect(errors[0]).toMatchObject({ + message: expect.stringMatching(/^Elements in allOf must be objects/), + }); + }); + }); + + describe('given a simple allOf with two schemas', () => { + test('should merge all schemas into one', () => { + expect( + applyAllOf({ User: { allOf: [{ type: 'string' }, { maxLength: 10 }] } }) + ).toEqual({ User: { type: 'string', maxLength: 10 } }); + }); + }); + + describe('given a schema with allOf and its own keywords', () => { + test('should give priority to the schema own keywords over allOf items', () => { + expect( + applyAllOf({ + User: { + type: 'object', + allOf: [{ type: 'string', description: 'from allOf' }], + }, + }) + ).toEqual({ User: { type: 'object', description: 'from allOf' } }); + }); + }); + + describe('given nested allOf schemas', () => { + test('should fully resolve all nesting levels', () => { + expect( + applyAllOf({ + Cat: { + allOf: [ + { allOf: [{ type: 'string' }] }, + { maximum: 1 }, + { + allOf: [ + { exclusiveMaximum: 2 }, + { allOf: [{ minimum: 3 }, { exclusiveMinimum: 4 }] }, + ], + }, + ], + }, + }) + ).toEqual({ + Cat: { + type: 'string', + maximum: 1, + exclusiveMaximum: 2, + minimum: 3, + exclusiveMinimum: 4, + }, + }); + }); + }); + + describe('given allOf items with deeply nested objects', () => { + test('should deep-merge nested objects', () => { + expect( + applyAllOf({ + Cat: { + allOf: [ + { properties: { name: { type: 'string' } } }, + { properties: { age: { type: 'integer' } } }, + ], + }, + }) + ).toEqual({ + Cat: { + properties: { name: { type: 'string' }, age: { type: 'integer' } }, + }, + }); + }); + }); + + describe('given allOf items with array keywords', () => { + test('should concatenate arrays from all items', () => { + expect( + applyAllOf({ + Cat: { allOf: [{ required: ['name'] }, { required: ['age'] }] }, + }) + ).toEqual({ Cat: { required: ['name', 'age'] } }); + }); + }); + + describe('given a schema with an examples keyword', () => { + test('should keep the original examples instead of merging', () => { + expect( + applyAllOf({ + Cat: { + examples: [{ name: 'my cat' }], + allOf: [{ examples: [{ name: 'other' }], type: 'object' }], + }, + }) + ).toEqual({ Cat: { examples: [{ name: 'my cat' }], type: 'object' } }); + }); + }); + + describe('given a schema without $$ref', () => { + test('should strip $$ref introduced by allOf merging', () => { + expect( + applyAllOf({ Cat: { allOf: [{ $$ref: 'some-ref', type: 'object' }] } }) + ).toEqual({ Cat: { type: 'object' } }); + }); + }); + + describe('given a schema with an existing $$ref', () => { + test('should retain the $$ref from the original schema', () => { + const result = applyAllOf({ + Cat: { $$ref: 'original-ref', allOf: [{ $$ref: 'other-ref', type: 'object' }] }, + }); + + expect(result.Cat.$$ref).toEqual('original-ref'); + }); + }); + + describe('given primitive enum values in allOf items', () => { + test('should deduplicate primitive enum values', () => { + expect( + applyAllOf({ + one: { + allOf: [ + { properties: { foo: { enum: [1, 2, 3, 4] } } }, + { properties: { foo: { enum: [1, 2, 3, 5] } } }, + ], + }, + }) + ).toEqual({ one: { properties: { foo: { enum: [1, 2, 3, 4, 5] } } } }); + }); + + test('should not deduplicate complex enum values', () => { + const result = applyAllOf({ + one: { + allOf: [ + { properties: { bar: { enum: [{ enum: [1, 2, 3] }] } } }, + { properties: { bar: { enum: [{ enum: [1, 2, 3] }] } } }, + ], + }, + }); + + expect(result.one.properties.bar.enum).toHaveLength(2); + }); + }); + }); + }); + }); + }); +}); diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/dereference.js b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/dereference.js new file mode 100644 index 000000000..c719dde1a --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/dereference.js @@ -0,0 +1,212 @@ +/* eslint-disable camelcase */ +import { toValue } from '@swagger-api/apidom-core'; +import { + mediaTypes, + refract, + isBooleanJsonSchemaElement, +} from '@swagger-api/apidom-ns-json-schema-2020-12'; +import { dereferenceApiDOM } from '@swagger-api/apidom-reference/configuration/empty'; + +import * as jestSetup from '../__utils__/jest.local.setup.js'; +import JSONSchema2020_12SwaggerClientDereferenceStrategy from '../../../../../../../../src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/index.js'; + +const $schema = 'https://json-schema.org/draft/2020-12/schema'; +const baseURI = 'https://example.com/schemas/root.json'; + +const dereferenceSpec = (spec, { strategyOptions, ...options } = {}) => + dereferenceApiDOM(refract(spec), { + parse: { mediaType: mediaTypes.latest('json') }, + resolve: { baseURI }, + ...(strategyOptions + ? { + dereference: { + strategies: [new JSONSchema2020_12SwaggerClientDereferenceStrategy(strategyOptions)], + ...options.dereference, + }, + } + : options), + }); + +describe('dereference', () => { + beforeAll(() => { + jestSetup.beforeAll(); + }); + + afterAll(() => { + jestSetup.afterAll(); + }); + + describe('strategies', () => { + describe('json-schema-2020-12-swagger-client', () => { + describe('visitors', () => { + describe('JSONSchema2020_12SwaggerClientDereferenceVisitor', () => { + describe('given allowMetaPatches=true', () => { + test('should annotate the dereferenced schema with an absolute $$ref', async () => { + const result = await dereferenceSpec( + { + $schema, + properties: { user: { $ref: '#/$defs/User' } }, + $defs: { User: { type: 'object' } }, + }, + { strategyOptions: { allowMetaPatches: true } } + ); + + expect(toValue(result).properties.user).toEqual({ + type: 'object', + $$ref: `${baseURI}#/$defs/User`, + }); + }); + + test('should resolve $$ref against the $id base URI', async () => { + const result = await dereferenceSpec( + { + $schema, + $id: 'https://example.com/other/root.json', + properties: { tag: { $ref: '#tag' } }, + $defs: { Tag: { $anchor: 'tag', type: 'string' } }, + }, + { strategyOptions: { allowMetaPatches: true } } + ); + + expect(toValue(result).properties.tag.$$ref).toBe( + 'https://example.com/other/root.json#tag' + ); + }); + }); + + describe('given allowMetaPatches=false', () => { + test('should NOT annotate the dereferenced schema with $$ref', async () => { + const result = await dereferenceSpec( + { + $schema, + properties: { user: { $ref: '#/$defs/User' } }, + $defs: { User: { type: 'object' } }, + }, + { strategyOptions: { allowMetaPatches: false } } + ); + + expect(toValue(result).properties.user).toEqual({ type: 'object' }); + }); + }); + + describe('given resolve.internal=false', () => { + test('should leave internal $refs unresolved', async () => { + const result = await dereferenceApiDOM( + refract({ + $schema, + properties: { user: { $ref: '#/$defs/User' }, tag: { $ref: '#tag' } }, + $defs: { User: { type: 'object' }, Tag: { $anchor: 'tag' } }, + }), + { + parse: { mediaType: mediaTypes.latest('json') }, + resolve: { baseURI, internal: false }, + } + ); + + expect(toValue(result).properties).toEqual({ + user: { $ref: '#/$defs/User' }, + tag: { $ref: '#tag' }, + }); + }); + }); + + describe('given resolve.external=false', () => { + test('should leave external $refs unresolved', async () => { + const result = await dereferenceApiDOM( + refract({ + $schema, + properties: { + user: { $ref: 'https://example.com/schemas/user.json' }, + tag: { $ref: '#/$defs/Tag' }, + }, + $defs: { Tag: { type: 'string' } }, + }), + { + parse: { mediaType: mediaTypes.latest('json') }, + resolve: { baseURI, external: false }, + } + ); + + expect(toValue(result).properties).toEqual({ + user: { $ref: 'https://example.com/schemas/user.json' }, + tag: { type: 'string' }, + }); + }); + }); + + describe('given a $ref to a Boolean JSON Schema', () => { + test('should dereference to a Boolean JSON Schema element', async () => { + const result = await dereferenceSpec({ + $schema, + properties: { any: { $ref: '#/$defs/Any' }, none: { $ref: '#/$defs/None' } }, + $defs: { Any: true, None: false }, + }); + + expect(toValue(result).properties).toEqual({ any: true, none: false }); + expect(isBooleanJsonSchemaElement(result.get('properties').get('any'))).toBe(true); + }); + }); + + describe('given a $dynamicRef', () => { + test('should leave it in place', async () => { + const spec = { + $schema, + $id: 'https://test.json-schema.org/dynamicRef-dynamicAnchor-same-schema/root', + type: 'array', + items: { $dynamicRef: '#items' }, + $defs: { foo: { $dynamicAnchor: 'items', type: 'string' } }, + }; + const result = await dereferenceSpec(spec); + + expect(toValue(result)).toEqual(spec); + }); + }); + + describe('given an unresolvable $ref', () => { + test('should collect an error and continue', async () => { + const errors = []; + const result = await dereferenceSpec( + { + $schema, + properties: { + user: { $ref: '#/$defs/Missing' }, + tag: { $ref: '#/$defs/Tag' }, + }, + $defs: { Tag: { type: 'string' } }, + }, + { dereference: { dereferenceOpts: { errors } } } + ); + + expect(toValue(result).properties).toEqual({ + user: { $ref: '#/$defs/Missing' }, + tag: { type: 'string' }, + }); + expect(errors).toHaveLength(1); + expect(errors[0]).toMatchObject({ + message: expect.stringMatching(/^Could not resolve reference/), + $ref: '#/$defs/Missing', + baseDoc: baseURI, + fullPath: ['properties', 'user', '$ref'], + }); + }); + + test('should collect an error for an unknown $anchor', async () => { + const errors = []; + await dereferenceSpec( + { $schema, properties: { user: { $ref: '#missing' } } }, + { dereference: { dereferenceOpts: { errors } } } + ); + + expect(errors).toHaveLength(1); + expect(errors[0]).toMatchObject({ + $ref: '#missing', + fullPath: ['properties', 'user', '$ref'], + }); + }); + }); + }); + }); + }); + }); +}); +/* eslint-enable camelcase */ diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/properties.js b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/properties.js new file mode 100644 index 000000000..84c3ff72d --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/properties.js @@ -0,0 +1,96 @@ +import { visit, toValue } from '@swagger-api/apidom-core'; +import { refract, getNodeType, keyMap } from '@swagger-api/apidom-ns-json-schema-2020-12'; + +import ModelPropertyMacroVisitor from '../../../../../../../../src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/properties.js'; + +const $schema = 'https://json-schema.org/draft/2020-12/schema'; + +const applyMacro = (spec, modelPropertyMacro, errors = []) => { + const element = refract({ $schema, ...spec }); + const visitor = new ModelPropertyMacroVisitor({ + modelPropertyMacro, + options: { dereference: { dereferenceOpts: { errors } } }, + }); + return toValue(visit(element, visitor, { keyMap, nodeTypeGetter: getNodeType })); +}; + +describe('dereference', () => { + describe('strategies', () => { + describe('json-schema-2020-12-swagger-client', () => { + describe('visitors', () => { + describe('ModelPropertyMacroVisitor', () => { + describe('given a schema without a properties keyword', () => { + test('should leave the schema unchanged', () => { + const macro = jest.fn(() => 'default'); + + expect(applyMacro({ type: 'string' }, macro)).toEqual({ $schema, type: 'string' }); + expect(macro).not.toHaveBeenCalled(); + }); + }); + + describe('given a schema with properties as a non-object value', () => { + test('should leave the schema unchanged', () => { + const macro = jest.fn(() => 'default'); + + expect(applyMacro({ properties: [] }, macro)).toEqual({ $schema, properties: [] }); + expect(macro).not.toHaveBeenCalled(); + }); + }); + + describe('given the document root and nested schemas with properties', () => { + test('should call the macro for each property and set the default', () => { + const macro = jest.fn((property) => `${property.type}-default`); + const result = applyMacro( + { + properties: { + id: { type: 'integer' }, + user: { type: 'object', properties: { name: { type: 'string' } } }, + }, + $defs: { Tag: { properties: { label: { type: 'string' } } } }, + }, + macro + ); + + expect(result.properties.id.default).toBe('integer-default'); + expect(result.properties.user.default).toBe('object-default'); + expect(result.properties.user.properties.name.default).toBe('string-default'); + expect(result.$defs.Tag.properties.label.default).toBe('string-default'); + expect(macro).toHaveBeenCalledTimes(4); + }); + }); + + describe('given a schema where the macro throws an error', () => { + test('should collect error with fullPath pointing to properties', () => { + const errors = []; + + applyMacro( + { $defs: { User: { properties: { id: { type: 'integer' } } } } }, + () => { + throw new Error('macro failed'); + }, + errors + ); + + expect(errors).toHaveLength(1); + expect(errors[0]).toMatchObject({ + message: expect.stringMatching(/macro failed/), + fullPath: ['$defs', 'User', 'properties'], + }); + }); + + test('should leave the property unchanged when the macro throws', () => { + const result = applyMacro( + { $defs: { User: { properties: { id: { type: 'integer' } } } } }, + () => { + throw new Error('macro failed'); + } + ); + + expect(result.$defs.User.properties.id).toEqual({ type: 'integer' }); + }); + }); + }); + }); + }); + }); +}); diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/root.js b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/root.js new file mode 100644 index 000000000..5b7ce1200 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/visitors/root.js @@ -0,0 +1,150 @@ +/* eslint-disable camelcase */ +import { toValue } from '@swagger-api/apidom-core'; +import { mediaTypes, refract } from '@swagger-api/apidom-ns-json-schema-2020-12'; +import { dereferenceApiDOM } from '@swagger-api/apidom-reference/configuration/empty'; + +import * as jestSetup from '../__utils__/jest.local.setup.js'; +import JSONSchema2020_12SwaggerClientDereferenceStrategy from '../../../../../../../../src/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/index.js'; + +const $schema = 'https://json-schema.org/draft/2020-12/schema'; + +describe('dereference', () => { + beforeAll(() => { + jestSetup.beforeAll(); + }); + + afterAll(() => { + jestSetup.afterAll(); + }); + + describe('strategies', () => { + describe('json-schema-2020-12-swagger-client', () => { + test('should be named json-schema-2020-12-swagger-client', () => { + expect(new JSONSchema2020_12SwaggerClientDereferenceStrategy().name).toBe( + 'json-schema-2020-12-swagger-client' + ); + }); + + describe('visitors', () => { + describe('RootVisitor', () => { + describe('given mode is not set (default)', () => { + test('should merge allOf schemas', async () => { + const spec = refract({ + $schema, + $defs: { Cat: { allOf: [{ type: 'string' }, { maxLength: 10 }] } }, + }); + const result = await dereferenceApiDOM(spec, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + + expect(toValue(result).$defs.Cat).toEqual({ type: 'string', maxLength: 10 }); + }); + + test('should merge allOf items that are $refs', async () => { + const spec = refract({ + $schema, + properties: { + cat: { allOf: [{ $ref: '#/$defs/Pet' }, { required: ['name'] }] }, + }, + $defs: { Pet: { type: 'object', properties: { name: { type: 'string' } } } }, + }); + const result = await dereferenceApiDOM(spec, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + + expect(toValue(result).properties.cat).toEqual({ + type: 'object', + properties: { name: { type: 'string' } }, + required: ['name'], + }); + }); + }); + + describe('given mode=strict', () => { + test('should NOT merge allOf schemas', async () => { + const spec = refract({ + $schema, + $defs: { Cat: { allOf: [{ type: 'string' }, { maxLength: 10 }] } }, + }); + const result = await dereferenceApiDOM(spec, { + parse: { mediaType: mediaTypes.latest('json') }, + dereference: { + strategies: [ + new JSONSchema2020_12SwaggerClientDereferenceStrategy({ mode: 'strict' }), + ], + }, + }); + + expect(toValue(result).$defs.Cat).toEqual({ + allOf: [{ type: 'string' }, { maxLength: 10 }], + }); + }); + + test('should still dereference $refs in strict mode', async () => { + const spec = refract({ + $schema, + properties: { cat: { allOf: [{ $ref: '#/$defs/Pet' }] } }, + $defs: { Pet: { type: 'object' } }, + }); + const result = await dereferenceApiDOM(spec, { + parse: { mediaType: mediaTypes.latest('json') }, + dereference: { + strategies: [ + new JSONSchema2020_12SwaggerClientDereferenceStrategy({ mode: 'strict' }), + ], + }, + }); + + expect(toValue(result).properties.cat).toEqual({ allOf: [{ type: 'object' }] }); + }); + }); + + describe('given modelPropertyMacro is provided', () => { + test('should apply the macro to schema properties', async () => { + const spec = refract({ + $schema, + $defs: { + User: { type: 'object', properties: { id: { type: 'integer' } } }, + }, + }); + const result = await dereferenceApiDOM(spec, { + parse: { mediaType: mediaTypes.latest('json') }, + dereference: { + strategies: [ + new JSONSchema2020_12SwaggerClientDereferenceStrategy({ + modelPropertyMacro: (prop) => `${prop.type}-default`, + }), + ], + }, + }); + + expect(toValue(result).$defs.User.properties.id.default).toBe('integer-default'); + }); + }); + + describe('given parameterMacro is provided', () => { + test('should ignore it: JSON Schema documents have no Parameter Objects', async () => { + const parameterMacro = jest.fn(() => 'default'); + const spec = refract({ + $schema, + properties: { parameters: { type: 'array', items: { name: 'limit' } } }, + }); + const result = await dereferenceApiDOM(spec, { + parse: { mediaType: mediaTypes.latest('json') }, + dereference: { + strategies: [ + new JSONSchema2020_12SwaggerClientDereferenceStrategy({ parameterMacro }), + ], + }, + }); + + expect(toValue(result)).toEqual(toValue(spec)); + expect(parameterMacro).not.toHaveBeenCalled(); + }); + }); + }); + }); + }); + }); +}); +/* eslint-enable camelcase */ diff --git a/test/resolver/apidom/reference/parse/parsers/json-schema-json-2020-12/__fixtures__/sample-schema.json b/test/resolver/apidom/reference/parse/parsers/json-schema-json-2020-12/__fixtures__/sample-schema.json new file mode 100644 index 000000000..04067d851 --- /dev/null +++ b/test/resolver/apidom/reference/parse/parsers/json-schema-json-2020-12/__fixtures__/sample-schema.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/schemas/user.json", + "title": "User", + "type": "object", + "properties": { + "name": { "type": "string" }, + "address": { "$ref": "#/$defs/Address" } + }, + "$defs": { + "Address": { + "$anchor": "address", + "type": "object", + "properties": { + "street": { "type": "string" } + } + } + } +} diff --git a/test/resolver/apidom/reference/parse/parsers/json-schema-json-2020-12/index.js b/test/resolver/apidom/reference/parse/parsers/json-schema-json-2020-12/index.js new file mode 100644 index 000000000..735deaf28 --- /dev/null +++ b/test/resolver/apidom/reference/parse/parsers/json-schema-json-2020-12/index.js @@ -0,0 +1,255 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { isParseResultElement, toValue } from '@swagger-api/apidom-core'; +import { File, ParserError } from '@swagger-api/apidom-reference/configuration/empty'; +import { mediaTypes, isJSONSchemaElement } from '@swagger-api/apidom-ns-json-schema-2020-12'; + +// eslint-disable-next-line camelcase +import JSONSchemaJSON2020_12Parser from '../../../../../../../src/resolver/apidom/reference/parse/parsers/json-schema-json-2020-12/index.js'; + +const fixturePath = path.join(__dirname, '__fixtures__', 'sample-schema.json'); + +describe('JSONSchemaJSON2020_12Parser', () => { + test('should be named json-schema-json-2020-12-swagger-client', () => { + const parser = new JSONSchemaJSON2020_12Parser(); + + expect(parser.name).toBe('json-schema-json-2020-12-swagger-client'); + }); + + describe('canParse', () => { + describe('given file with .json extension', () => { + describe('and with proper media type', () => { + test('should return true', async () => { + const file1 = new File({ + uri: '/path/to/schema.json', + mediaType: mediaTypes.latest('generic'), + }); + const file2 = new File({ + uri: '/path/to/schema.json', + mediaType: mediaTypes.latest('json'), + }); + const parser = new JSONSchemaJSON2020_12Parser(); + + expect(await parser.canParse(file1)).toBe(true); + expect(await parser.canParse(file2)).toBe(true); + }); + }); + + describe('and with YAML media type', () => { + test('should return false', async () => { + const file = new File({ + uri: '/path/to/schema.json', + mediaType: mediaTypes.latest('yaml'), + }); + const parser = new JSONSchemaJSON2020_12Parser(); + + expect(await parser.canParse(file)).toBe(false); + }); + }); + + describe('and with improper media type', () => { + test('should return false', async () => { + const file = new File({ + uri: '/path/to/schema.json', + mediaType: 'application/vnd.oai.openapi+json;version=3.1.0', + }); + const parser = new JSONSchemaJSON2020_12Parser(); + + expect(await parser.canParse(file)).toBe(false); + }); + }); + }); + + describe('given file with unknown extension', () => { + test('should return false', async () => { + const file = new File({ + uri: '/path/to/schema.yaml', + mediaType: mediaTypes.latest('json'), + }); + const parser = new JSONSchemaJSON2020_12Parser({ fileExtensions: ['.json'] }); + + expect(await parser.canParse(file)).toBe(false); + }); + }); + + describe('given file with supported extension', () => { + describe('and file data is buffer and can be detected as JSON Schema 2020-12', () => { + test('should return true', async () => { + const file = new File({ + uri: '/path/to/schema.json', + data: fs.readFileSync(fixturePath), + }); + const parser = new JSONSchemaJSON2020_12Parser(); + + expect(await parser.canParse(file)).toBe(true); + }); + }); + + describe('and file data is string and can be detected as JSON Schema 2020-12', () => { + test('should return true', async () => { + const file = new File({ + uri: '/path/to/schema.json', + data: fs.readFileSync(fixturePath).toString(), + }); + const parser = new JSONSchemaJSON2020_12Parser(); + + expect(await parser.canParse(file)).toBe(true); + }); + }); + + describe('and $schema has an empty fragment', () => { + test('should return true', async () => { + const file = new File({ + uri: '/path/to/schema.json', + data: '{"$schema": "https://json-schema.org/draft/2020-12/schema#"}', + }); + const parser = new JSONSchemaJSON2020_12Parser(); + + expect(await parser.canParse(file)).toBe(true); + }); + }); + + describe('and $schema is only in a nested schema', () => { + test('should return false', async () => { + const file = new File({ + uri: '/path/to/schema.json', + data: '{"type": "object", "properties": {"a": {"$schema": "https://json-schema.org/draft/2020-12/schema"}}}', + }); + const parser = new JSONSchemaJSON2020_12Parser(); + + expect(await parser.canParse(file)).toBe(false); + }); + }); + + describe('and $schema names another dialect', () => { + test('should return false', async () => { + const file = new File({ + uri: '/path/to/schema.json', + data: '{"$schema": "http://json-schema.org/draft-07/schema#"}', + }); + const parser = new JSONSchemaJSON2020_12Parser(); + + expect(await parser.canParse(file)).toBe(false); + }); + }); + + describe('and the document is an OpenAPI document with root $schema', () => { + test('should return false', async () => { + const file = new File({ + uri: '/path/to/openapi.json', + data: '{"openapi": "3.1.0", "$schema": "https://json-schema.org/draft/2020-12/schema"}', + }); + const parser = new JSONSchemaJSON2020_12Parser(); + + expect(await parser.canParse(file)).toBe(false); + }); + }); + + describe('and file data is not JSON', () => { + test('should return false', async () => { + const file = new File({ + uri: '/path/to/schema.json', + data: '$schema: https://json-schema.org/draft/2020-12/schema', + }); + const parser = new JSONSchemaJSON2020_12Parser(); + + expect(await parser.canParse(file)).toBe(false); + }); + }); + }); + }); + + describe('parse', () => { + describe('given JSON Schema 2020-12 JSON data', () => { + test('should return parse result with a JSON Schema 2020-12 element', async () => { + const data = fs.readFileSync(fixturePath).toString(); + const file = new File({ + uri: fixturePath, + data, + mediaType: mediaTypes.latest('json'), + }); + const parser = new JSONSchemaJSON2020_12Parser(); + const parseResult = await parser.parse(file); + + expect(isParseResultElement(parseResult)).toBe(true); + expect(isJSONSchemaElement(parseResult.result)).toBe(true); + expect(parseResult.result.element).toBe('JSONSchema202012'); + expect(isJSONSchemaElement(parseResult.result.get('$defs').get('Address'))).toBe(true); + expect(toValue(parseResult.result)).toEqual(JSON.parse(data)); + }); + }); + + describe('given JSON Schema 2020-12 JSON data as buffer', () => { + test('should return parse result', async () => { + const file = new File({ + uri: fixturePath, + data: fs.readFileSync(fixturePath), + mediaType: mediaTypes.latest('json'), + }); + const parser = new JSONSchemaJSON2020_12Parser(); + const parseResult = await parser.parse(file); + + expect(isParseResultElement(parseResult)).toBe(true); + expect(isJSONSchemaElement(parseResult.result)).toBe(true); + }); + }); + + describe('given empty file', () => { + test('should return empty parse result', async () => { + const file = new File({ + uri: '/path/to/schema.json', + data: '', + mediaType: mediaTypes.latest('json'), + }); + const parser = new JSONSchemaJSON2020_12Parser(); + const parseResult = await parser.parse(file); + + expect(isParseResultElement(parseResult)).toBe(true); + expect(parseResult.isEmpty).toBe(true); + }); + }); + + describe('given invalid JSON', () => { + test('should throw ParserError', async () => { + const file = new File({ + uri: '/path/to/schema.json', + data: '{"$schema": ', + mediaType: mediaTypes.latest('json'), + }); + const parser = new JSONSchemaJSON2020_12Parser(); + + await expect(parser.parse(file)).rejects.toThrow(ParserError); + }); + }); + + describe('sourceMap', () => { + describe('given sourceMap enabled', () => { + test('should throw error', async () => { + const file = new File({ uri: '/path/to/schema.json', data: '{"prop": "val"}' }); + const parser = new JSONSchemaJSON2020_12Parser({ sourceMap: true }); + const parseWithSourceMapThunk = () => parser.parse(file); + + await expect(parseWithSourceMapThunk()).rejects.toThrow( + new ParserError( + "json-schema-json-2020-12-swagger-client parser plugin doesn't support sourceMaps option" + ) + ); + }); + }); + + describe('given sourceMap disabled', () => { + test('should not decorate ApiDOM with source maps', async () => { + const file = new File({ + uri: fixturePath, + data: fs.readFileSync(fixturePath).toString(), + mediaType: mediaTypes.latest('json'), + }); + const parser = new JSONSchemaJSON2020_12Parser(); + const parseResult = await parser.parse(file); + + expect(parseResult.result.meta.get('sourceMap')).toBeUndefined(); + }); + }); + }); + }); +}); diff --git a/test/resolver/apidom/reference/parse/parsers/json-schema-yaml-2020-12/__fixtures__/sample-schema.yaml b/test/resolver/apidom/reference/parse/parsers/json-schema-yaml-2020-12/__fixtures__/sample-schema.yaml new file mode 100644 index 000000000..2f47534a4 --- /dev/null +++ b/test/resolver/apidom/reference/parse/parsers/json-schema-yaml-2020-12/__fixtures__/sample-schema.yaml @@ -0,0 +1,16 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: https://example.com/schemas/user.yaml +title: User +type: object +properties: + name: + type: string + address: + $ref: '#/$defs/Address' +$defs: + Address: + $anchor: address + type: object + properties: + street: + type: string diff --git a/test/resolver/apidom/reference/parse/parsers/json-schema-yaml-2020-12/index.js b/test/resolver/apidom/reference/parse/parsers/json-schema-yaml-2020-12/index.js new file mode 100644 index 000000000..b192c19ab --- /dev/null +++ b/test/resolver/apidom/reference/parse/parsers/json-schema-yaml-2020-12/index.js @@ -0,0 +1,232 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import YAML from 'js-yaml'; +import { isParseResultElement, toValue } from '@swagger-api/apidom-core'; +import { File, ParserError } from '@swagger-api/apidom-reference/configuration/empty'; +import { mediaTypes, isJSONSchemaElement } from '@swagger-api/apidom-ns-json-schema-2020-12'; + +// eslint-disable-next-line camelcase +import JSONSchemaYAML2020_12Parser from '../../../../../../../src/resolver/apidom/reference/parse/parsers/json-schema-yaml-2020-12/index.js'; + +const fixturePath = path.join(__dirname, '__fixtures__', 'sample-schema.yaml'); + +describe('JSONSchemaYAML2020_12Parser', () => { + test('should be named json-schema-yaml-2020-12-swagger-client', () => { + const parser = new JSONSchemaYAML2020_12Parser(); + + expect(parser.name).toBe('json-schema-yaml-2020-12-swagger-client'); + }); + + describe('canParse', () => { + describe('given file with .yaml extension', () => { + describe('and with proper media type', () => { + test('should return true', async () => { + const file1 = new File({ + uri: '/path/to/schema.yaml', + mediaType: mediaTypes.latest('generic'), + }); + const file2 = new File({ + uri: '/path/to/schema.yaml', + mediaType: mediaTypes.latest('yaml'), + }); + const parser = new JSONSchemaYAML2020_12Parser(); + + expect(await parser.canParse(file1)).toBe(true); + expect(await parser.canParse(file2)).toBe(true); + }); + }); + + describe('and with JSON media type', () => { + test('should return false', async () => { + const file = new File({ + uri: '/path/to/schema.yaml', + mediaType: mediaTypes.latest('json'), + }); + const parser = new JSONSchemaYAML2020_12Parser(); + + expect(await parser.canParse(file)).toBe(false); + }); + }); + }); + + describe('given file with unknown extension', () => { + test('should return false', async () => { + const file = new File({ + uri: '/path/to/schema.json', + mediaType: mediaTypes.latest('yaml'), + }); + const parser = new JSONSchemaYAML2020_12Parser({ fileExtensions: ['.yaml'] }); + + expect(await parser.canParse(file)).toBe(false); + }); + }); + + describe('given file with supported extension', () => { + describe('and file data is buffer and can be detected as JSON Schema 2020-12', () => { + test('should return true', async () => { + const file = new File({ + uri: '/path/to/schema.yaml', + data: fs.readFileSync(fixturePath), + }); + const parser = new JSONSchemaYAML2020_12Parser(); + + expect(await parser.canParse(file)).toBe(true); + }); + }); + + describe('and file data is JSON that can be detected as JSON Schema 2020-12', () => { + test('should return true', async () => { + const file = new File({ + uri: '/path/to/schema.yaml', + data: '{"$schema": "https://json-schema.org/draft/2020-12/schema"}', + }); + const parser = new JSONSchemaYAML2020_12Parser(); + + expect(await parser.canParse(file)).toBe(true); + }); + }); + + describe('and $schema is only in a nested schema', () => { + test('should return false', async () => { + const file = new File({ + uri: '/path/to/schema.yaml', + data: 'type: object\nproperties:\n a:\n $schema: https://json-schema.org/draft/2020-12/schema\n', + }); + const parser = new JSONSchemaYAML2020_12Parser(); + + expect(await parser.canParse(file)).toBe(false); + }); + }); + + describe('and $schema uses the http scheme', () => { + test('should return false', async () => { + const file = new File({ + uri: '/path/to/schema.yaml', + data: '$schema: http://json-schema.org/draft/2020-12/schema\n', + }); + const parser = new JSONSchemaYAML2020_12Parser(); + + expect(await parser.canParse(file)).toBe(false); + }); + }); + + describe('and the document is an OpenAPI document with root $schema', () => { + test('should return false', async () => { + const file = new File({ + uri: '/path/to/openapi.yaml', + data: 'openapi: 3.1.0\n$schema: https://json-schema.org/draft/2020-12/schema\n', + }); + const parser = new JSONSchemaYAML2020_12Parser(); + + expect(await parser.canParse(file)).toBe(false); + }); + }); + + describe('and file data is not YAML', () => { + test('should return false', async () => { + const file = new File({ + uri: '/path/to/schema.yaml', + data: '$schema: [', + }); + const parser = new JSONSchemaYAML2020_12Parser(); + + expect(await parser.canParse(file)).toBe(false); + }); + }); + }); + }); + + describe('parse', () => { + describe('given JSON Schema 2020-12 YAML data', () => { + test('should return parse result with a JSON Schema 2020-12 element', async () => { + const data = fs.readFileSync(fixturePath).toString(); + const file = new File({ + uri: fixturePath, + data, + mediaType: mediaTypes.latest('yaml'), + }); + const parser = new JSONSchemaYAML2020_12Parser(); + const parseResult = await parser.parse(file); + + expect(isParseResultElement(parseResult)).toBe(true); + expect(isJSONSchemaElement(parseResult.result)).toBe(true); + expect(parseResult.result.element).toBe('JSONSchema202012'); + expect(isJSONSchemaElement(parseResult.result.get('$defs').get('Address'))).toBe(true); + expect(toValue(parseResult.result)).toEqual(YAML.load(data)); + }); + }); + + describe('given JSON Schema 2020-12 YAML data as buffer', () => { + test('should return parse result', async () => { + const file = new File({ + uri: fixturePath, + data: fs.readFileSync(fixturePath), + mediaType: mediaTypes.latest('yaml'), + }); + const parser = new JSONSchemaYAML2020_12Parser(); + const parseResult = await parser.parse(file); + + expect(isParseResultElement(parseResult)).toBe(true); + expect(isJSONSchemaElement(parseResult.result)).toBe(true); + }); + }); + + describe('given empty file', () => { + test('should return empty parse result', async () => { + const file = new File({ + uri: '/path/to/schema.yaml', + data: '', + mediaType: mediaTypes.latest('yaml'), + }); + const parser = new JSONSchemaYAML2020_12Parser(); + const parseResult = await parser.parse(file); + + expect(isParseResultElement(parseResult)).toBe(true); + expect(parseResult.isEmpty).toBe(true); + }); + }); + + describe('given invalid YAML', () => { + test('should throw ParserError', async () => { + const file = new File({ + uri: '/path/to/schema.yaml', + data: '$schema: [', + mediaType: mediaTypes.latest('yaml'), + }); + const parser = new JSONSchemaYAML2020_12Parser(); + + await expect(parser.parse(file)).rejects.toThrow(ParserError); + }); + }); + + describe('sourceMap', () => { + describe('given sourceMap enabled', () => { + test('should throw error', async () => { + const file = new File({ uri: '/path/to/schema.yaml', data: 'prop: val' }); + const parser = new JSONSchemaYAML2020_12Parser({ sourceMap: true }); + const parseWithSourceMapThunk = () => parser.parse(file); + + await expect(parseWithSourceMapThunk()).rejects.toThrow( + new ParserError( + "json-schema-yaml-2020-12-swagger-client parser plugin doesn't support sourceMaps option" + ) + ); + }); + }); + + describe('given sourceMap disabled', () => { + test('should not decorate ApiDOM with source maps', async () => { + const file = new File({ + uri: fixturePath, + data: fs.readFileSync(fixturePath).toString(), + mediaType: mediaTypes.latest('yaml'), + }); + const parser = new JSONSchemaYAML2020_12Parser(); + const parseResult = await parser.parse(file); + + expect(parseResult.result.meta.get('sourceMap')).toBeUndefined(); + }); + }); + }); + }); +}); diff --git a/test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/circular.json b/test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/circular.json new file mode 100644 index 000000000..78e1a8533 --- /dev/null +++ b/test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/circular.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/Node", + "$defs": { + "Node": { + "type": "object", + "properties": { + "value": { "type": "string" }, + "next": { "$ref": "#/$defs/Node" } + } + } + } +} diff --git a/test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/external.json b/test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/external.json new file mode 100644 index 000000000..85f2dd9bd --- /dev/null +++ b/test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/external.json @@ -0,0 +1,15 @@ +{ + "$defs": { + "Address": { + "type": "object", + "properties": { + "street": { "type": "string" }, + "country": { "$ref": "#/$defs/Country" } + } + }, + "Country": { + "type": "string", + "minLength": 2 + } + } +} diff --git a/test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/macros.json b/test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/macros.json new file mode 100644 index 000000000..b11d65308 --- /dev/null +++ b/test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/macros.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "pet": { "$ref": "#/$defs/Pet" }, + "cat": { + "allOf": [ + { "$ref": "#/$defs/Pet" }, + { "type": "object", "properties": { "lives": { "type": "integer" } }, "required": ["lives"] } + ] + } + }, + "$defs": { + "Pet": { + "type": "object", + "properties": { + "name": { "type": "string" } + }, + "required": ["name"] + } + } +} diff --git a/test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/openapi-3-1-external-json-schema.json b/test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/openapi-3-1-external-json-schema.json new file mode 100644 index 000000000..8cd350f1c --- /dev/null +++ b/test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/openapi-3-1-external-json-schema.json @@ -0,0 +1,11 @@ +{ + "openapi": "3.1.0", + "info": { "title": "External JSON Schema", "version": "1.0.0" }, + "paths": {}, + "components": { + "schemas": { + "User": { "$ref": "./user.schema.json" }, + "Address": { "$ref": "./user.schema.json#/$defs/Address" } + } + } +} diff --git a/test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/product.json b/test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/product.json new file mode 100644 index 000000000..97460d7f5 --- /dev/null +++ b/test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/product.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "sku": { "$ref": "#sku" } + }, + "$defs": { + "Sku": { "$anchor": "sku", "type": "string" } + } +} diff --git a/test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/root.json b/test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/root.json new file mode 100644 index 000000000..676ad4d8d --- /dev/null +++ b/test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/root.json @@ -0,0 +1,48 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Order", + "type": "object", + "properties": { + "customer": { "$ref": "#/$defs/Customer" }, + "status": { "$ref": "#status" }, + "address": { "$ref": "external.json#/$defs/Address" }, + "items": { + "type": "array", + "items": { "$ref": "#/$defs/Item" } + }, + "metadata": { "$dynamicRef": "#meta" } + }, + "$defs": { + "Customer": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "address": { "$ref": "external.json#/$defs/Address" } + } + }, + "Status": { + "$anchor": "status", + "type": "string", + "enum": ["open", "closed"] + }, + "Item": { + "$id": "items/", + "type": "object", + "properties": { + "product": { "$ref": "product.json" }, + "price": { "$ref": "price.json" } + }, + "$defs": { + "Price": { + "$id": "https://example.com/schemas/items/price.json", + "type": "number", + "minimum": 0 + } + } + }, + "Meta": { + "$dynamicAnchor": "meta", + "type": "object" + } + } +} diff --git a/test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/user.schema.json b/test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/user.schema.json new file mode 100644 index 000000000..83335be55 --- /dev/null +++ b/test/resolver/strategies/json-schema-2020-12-apidom/__fixtures__/user.schema.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/schemas/user.schema.json", + "type": "object", + "properties": { + "name": { "type": "string" }, + "address": { "$ref": "#/$defs/Address" } + }, + "$defs": { + "Address": { + "type": "object", + "properties": { + "street": { "type": "string" } + } + } + } +} diff --git a/test/resolver/strategies/json-schema-2020-12-apidom/index.js b/test/resolver/strategies/json-schema-2020-12-apidom/index.js new file mode 100644 index 000000000..3ca5f3678 --- /dev/null +++ b/test/resolver/strategies/json-schema-2020-12-apidom/index.js @@ -0,0 +1,573 @@ +import path from 'node:path'; +import * as undici from 'undici'; + +import SwaggerClient from '../../../../src/index.js'; +import { DEFAULT_BASE_URL } from '../../../../src/constants.js'; +import { makeResolve } from '../../../../src/resolver/index.js'; +import jsonSchema202012ApiDOMStrategy from '../../../../src/resolver/strategies/json-schema-2020-12-apidom/index.js'; +import openApi31ApiDOMStrategy from '../../../../src/resolver/strategies/openapi-3-1-apidom/index.js'; +import genericStrategy from '../../../../src/resolver/strategies/generic/index.js'; + +const fixturePath = path.join(__dirname, '__fixtures__'); +const loadFixture = (name) => globalThis.loadJsonFile(path.join(fixturePath, name)); +const $schema = 'https://json-schema.org/draft/2020-12/schema'; + +const address = { + type: 'object', + properties: { + street: { type: 'string' }, + country: { type: 'string', minLength: 2 }, + }, +}; + +describe('resolve', () => { + let mockAgent; + let originalGlobalDispatcher; + + const mockFixture = (fixture, urlPath) => + mockAgent + .get('https://example.com') + .intercept({ path: urlPath }) + .reply(200, globalThis.loadFile(path.join(fixturePath, fixture)), { + headers: { 'Content-Type': 'application/json' }, + }); + + const mockOrder = () => { + mockFixture('root.json', '/schemas/order.json'); + mockFixture('external.json', '/schemas/external.json'); + mockFixture('product.json', '/schemas/items/product.json'); + }; + + beforeEach(() => { + mockAgent = new undici.MockAgent(); + mockAgent.disableNetConnect(); + originalGlobalDispatcher = undici.getGlobalDispatcher(); + undici.setGlobalDispatcher(mockAgent); + }); + + afterEach(() => { + undici.setGlobalDispatcher(originalGlobalDispatcher); + mockAgent = null; + originalGlobalDispatcher = null; + }); + + describe('JSON Schema 2020-12 strategy', () => { + test('should be registered ahead of the generic strategy', () => { + expect(SwaggerClient.resolveStrategies['json-schema-2020-12-apidom']).toBe( + jsonSchema202012ApiDOMStrategy + ); + expect(jsonSchema202012ApiDOMStrategy.name).toBe('json-schema-2020-12-apidom'); + expect(jsonSchema202012ApiDOMStrategy.match(loadFixture('root.json'))).toBe(true); + }); + + test('should normalize to the same object', () => { + const spec = loadFixture('root.json'); + + expect(jsonSchema202012ApiDOMStrategy.normalize(spec)).toBe(spec); + }); + + describe('given JSON Schema 2020-12 document via URL', () => { + test('should resolve internal, $anchor, $id-relative and external $refs', async () => { + mockOrder(); + const { spec, errors } = await SwaggerClient.resolve({ + url: 'https://example.com/schemas/order.json', + allowMetaPatches: false, + }); + + expect(errors).toEqual([]); + expect(spec.properties.customer).toEqual({ + type: 'object', + properties: { name: { type: 'string' }, address }, + }); + expect(spec.properties.status).toEqual({ + $anchor: 'status', + type: 'string', + enum: ['open', 'closed'], + }); + expect(spec.properties.address).toEqual(address); + expect(spec.properties.items.items).toEqual({ + $id: 'items/', + type: 'object', + properties: { + product: { + $schema, + type: 'object', + properties: { sku: { $anchor: 'sku', type: 'string' } }, + $defs: { Sku: { $anchor: 'sku', type: 'string' } }, + }, + price: { + $id: 'https://example.com/schemas/items/price.json', + type: 'number', + minimum: 0, + }, + }, + $defs: { + Price: { + $id: 'https://example.com/schemas/items/price.json', + type: 'number', + minimum: 0, + }, + }, + }); + }); + + test('should leave every $dynamicRef in place', async () => { + mockOrder(); + const { spec } = await SwaggerClient.resolve({ + url: 'https://example.com/schemas/order.json', + }); + + expect(spec.properties.metadata).toEqual({ $dynamicRef: '#meta' }); + }); + + describe('and allowMetaPatches=true', () => { + test('should annotate resolved schemas with absolute $$ref', async () => { + mockOrder(); + const { spec } = await SwaggerClient.resolve({ + url: 'https://example.com/schemas/order.json', + allowMetaPatches: true, + }); + + expect(spec.properties.customer.$$ref).toBe( + 'https://example.com/schemas/order.json#/$defs/Customer' + ); + expect(spec.properties.status.$$ref).toBe( + 'https://example.com/schemas/order.json#status' + ); + expect(spec.properties.address.$$ref).toBe( + 'https://example.com/schemas/external.json#/$defs/Address' + ); + expect(spec.properties.items.items.properties.product.$$ref).toBe( + 'https://example.com/schemas/items/product.json' + ); + expect(spec.properties.items.items.properties.price.$$ref).toBe( + 'https://example.com/schemas/items/price.json' + ); + }); + }); + + describe('and an external $ref cannot be retrieved', () => { + test('should collect the error and resolve the rest', async () => { + mockFixture('root.json', '/schemas/order.json'); + mockFixture('product.json', '/schemas/items/product.json'); + mockAgent + .get('https://example.com') + .intercept({ path: '/schemas/external.json' }) + .reply(404, 'Not Found') + .persist(); + const { spec, errors } = await SwaggerClient.resolve({ + url: 'https://example.com/schemas/order.json', + }); + + expect(spec.properties.address).toEqual({ $ref: 'external.json#/$defs/Address' }); + expect(spec.properties.status).toHaveProperty('$anchor', 'status'); + expect(errors.length).toBeGreaterThan(0); + expect(errors).toContainEqual( + expect.objectContaining({ + message: expect.stringMatching(/^Could not resolve reference/), + $ref: 'external.json#/$defs/Address', + fullPath: ['properties', 'address', '$ref'], + }) + ); + }); + }); + + describe('and useCircularStructures=false', () => { + test('should replace cycles with absolute $refs', async () => { + mockFixture('circular.json', '/schemas/circular.json'); + const { spec, errors } = await SwaggerClient.resolve({ + url: 'https://example.com/schemas/circular.json', + useCircularStructures: false, + }); + + const cycle = { $ref: 'https://example.com/schemas/circular.json#/$defs/Node' }; + + expect(errors).toEqual([]); + // the document root is the dereferenced Node, so the first `next` closes the cycle + expect(spec).toEqual({ + $schema, + type: 'object', + properties: { value: { type: 'string' }, next: cycle }, + $defs: { + Node: { type: 'object', properties: { value: { type: 'string' }, next: cycle } }, + }, + }); + }); + }); + + describe('and useCircularStructures=true', () => { + test('should resolve cycles into circular structures', async () => { + mockFixture('circular.json', '/schemas/circular.json'); + const { spec, errors } = await SwaggerClient.resolve({ + url: 'https://example.com/schemas/circular.json', + useCircularStructures: true, + }); + + expect(errors).toEqual([]); + expect(spec.properties.next.properties.next).toBe(spec.properties.next); + }); + }); + }); + + describe('given JSON Schema 2020-12 document via spec option', () => { + describe('and neither baseDoc nor url option is provided', () => { + test('should resolve using implicit baseURI=DEFAULT_BASE_URL', async () => { + const { spec } = await SwaggerClient.resolve({ + spec: loadFixture('macros.json'), + allowMetaPatches: true, + }); + + expect(spec.properties.pet.$$ref).toBe(`${DEFAULT_BASE_URL}/#/$defs/Pet`); + }); + }); + + describe('and baseDoc option is provided', () => { + test('should resolve external $refs against it', async () => { + mockFixture('external.json', '/schemas/external.json'); + mockFixture('product.json', '/schemas/items/product.json'); + const { spec, errors } = await SwaggerClient.resolve({ + spec: loadFixture('root.json'), + baseDoc: 'https://example.com/schemas/order.json', + }); + + expect(errors).toEqual([]); + expect(spec.properties.address).toEqual(address); + }); + }); + + describe('and the same object is resolved twice', () => { + test('should return the same result', async () => { + const spec = loadFixture('macros.json'); + const first = await SwaggerClient.resolve({ spec }); + const second = await SwaggerClient.resolve({ spec }); + + expect(second).toEqual(first); + }); + }); + + describe('and pathDiscriminator is empty list', () => { + test('should resolve entire spec', async () => { + const { spec } = await SwaggerClient.resolve({ + spec: loadFixture('macros.json'), + pathDiscriminator: [], + }); + + expect(spec.properties.pet).toEqual({ + type: 'object', + properties: { name: { type: 'string' } }, + required: ['name'], + }); + }); + }); + + describe('and pathDiscriminator=[properties, pet]', () => { + test('should resolve within the pathDiscriminator only', async () => { + const { spec } = await SwaggerClient.resolve({ + spec: loadFixture('macros.json'), + pathDiscriminator: ['properties', 'pet'], + }); + + expect(spec.properties.pet).toEqual({ + type: 'object', + properties: { name: { type: 'string' } }, + required: ['name'], + }); + expect(spec.properties.cat.allOf[0]).toEqual({ $ref: '#/$defs/Pet' }); + }); + }); + + describe('and pathDiscriminator compiles into invalid JSON Pointer', () => { + test('should not resolve', async () => { + const spec = loadFixture('macros.json'); + const resolved = await SwaggerClient.resolve({ + spec, + pathDiscriminator: ['path', 'to', 'nothing'], + }); + + expect(resolved).toEqual({ spec, errors: [] }); + }); + }); + + describe('and mode is not set', () => { + test('should merge allOf', async () => { + const { spec } = await SwaggerClient.resolve({ spec: loadFixture('macros.json') }); + + expect(spec.properties.cat).toEqual({ + type: 'object', + properties: { name: { type: 'string' }, lives: { type: 'integer' } }, + required: ['name', 'lives'], + }); + }); + }); + + describe('and mode=strict', () => { + test('should not merge allOf', async () => { + const { spec } = await SwaggerClient.resolve({ + spec: loadFixture('macros.json'), + mode: 'strict', + }); + + expect(spec.properties.cat.allOf).toHaveLength(2); + }); + }); + + describe('and modelPropertyMacro is provided as a function', () => { + test('should call modelPropertyMacro with schema properties', async () => { + const { spec } = await SwaggerClient.resolve({ + spec: loadFixture('macros.json'), + modelPropertyMacro: (property) => `${property.type}-default`, + }); + + expect(spec.properties.pet.properties.name.default).toBe('string-default'); + expect(spec.properties.cat.properties.lives.default).toBe('integer-default'); + }); + + describe('given the function throws error', () => { + test('should collect error', async () => { + const { errors } = await SwaggerClient.resolve({ + spec: loadFixture('macros.json'), + modelPropertyMacro: () => { + throw new Error('this macro throws'); + }, + }); + + expect(errors).toContainEqual( + expect.objectContaining({ + message: expect.stringMatching(/^Error: this macro throws/), + fullPath: ['$defs', 'Pet', 'properties'], + }) + ); + }); + }); + }); + + describe('and parameterMacro is provided as a function', () => { + test('should not call it', async () => { + const parameterMacro = jest.fn(); + await SwaggerClient.resolve({ spec: loadFixture('macros.json'), parameterMacro }); + + expect(parameterMacro).not.toHaveBeenCalled(); + }); + }); + }); + + describe('resolveSubtree', () => { + test('should resolve a subtree with $anchor, $id-relative and external $refs', async () => { + mockFixture('external.json', '/schemas/external.json'); + mockFixture('product.json', '/schemas/items/product.json'); + const spec = loadFixture('root.json'); + const customer = await SwaggerClient.resolveSubtree(spec, ['$defs', 'Customer'], { + baseDoc: 'https://example.com/schemas/order.json', + }); + const status = await SwaggerClient.resolveSubtree(spec, ['properties', 'status'], { + baseDoc: 'https://example.com/schemas/order.json', + }); + const item = await SwaggerClient.resolveSubtree(spec, ['$defs', 'Item'], { + baseDoc: 'https://example.com/schemas/order.json', + }); + + expect(customer).toEqual({ + spec: { + type: 'object', + properties: { + name: { type: 'string' }, + address: { + ...address, + properties: { + ...address.properties, + country: { + ...address.properties.country, + $$ref: 'https://example.com/schemas/external.json#/$defs/Country', + }, + }, + $$ref: 'https://example.com/schemas/external.json#/$defs/Address', + }, + }, + }, + errors: [], + }); + expect(status).toEqual({ + spec: { + $anchor: 'status', + type: 'string', + enum: ['open', 'closed'], + $$ref: 'https://example.com/schemas/order.json#status', + }, + errors: [], + }); + expect(item.errors).toEqual([]); + expect(item.spec.properties.product.properties.sku).toEqual({ + $anchor: 'sku', + type: 'string', + $$ref: 'https://example.com/schemas/items/product.json#sku', + }); + expect(item.spec.properties.price.$$ref).toBe( + 'https://example.com/schemas/items/price.json' + ); + }); + + test('should leave $dynamicRef in place', async () => { + const { spec } = await SwaggerClient.resolveSubtree(loadFixture('root.json'), [ + 'properties', + 'metadata', + ]); + + expect(spec).toEqual({ $dynamicRef: '#meta' }); + }); + + test('should resolve a $ref in the document root', async () => { + const { spec, errors } = await SwaggerClient.resolveSubtree( + loadFixture('circular.json'), + [] + ); + const cycle = { $ref: `${DEFAULT_BASE_URL}/#/$defs/Node` }; + + expect(errors).toEqual([]); + expect(spec).toEqual({ + $schema, + type: 'object', + properties: { value: { type: 'string' }, next: cycle }, + $defs: { + Node: { type: 'object', properties: { value: { type: 'string' }, next: cycle } }, + }, + $$ref: `${DEFAULT_BASE_URL}/#/$defs/Node`, + }); + }); + }); + + describe('given the same schema standalone and in an OpenAPI 3.1 document', () => { + test('should resolve them the same', async () => { + const schema = { + $id: 'https://example.com/schemas/pet.json', + type: 'object', + properties: { + tag: { $ref: '#/$defs/Tag' }, + owner: { $ref: '#owner' }, + nickname: { allOf: [{ $ref: '#/$defs/Tag' }, { minLength: 1 }] }, + }, + $defs: { + Tag: { type: 'string' }, + Owner: { $anchor: 'owner', type: 'object', properties: { name: { type: 'string' } } }, + }, + }; + const options = { + modelPropertyMacro: (property) => `${property.type}-default`, + allowMetaPatches: true, + }; + const standalone = await SwaggerClient.resolve({ + spec: { $schema, ...structuredClone(schema) }, + ...options, + }); + const openapi = await SwaggerClient.resolve({ + spec: { + openapi: '3.1.0', + info: { title: 'Pets', version: '1.0.0' }, + components: { schemas: { Pet: structuredClone(schema) } }, + }, + ...options, + }); + const { $schema: dialect, ...standaloneSchema } = standalone.spec; + + expect(standalone.errors).toEqual([]); + expect(openapi.errors).toEqual([]); + expect(dialect).toBe($schema); + expect(standaloneSchema).toEqual(openapi.spec.components.schemas.Pet); + expect(standaloneSchema.properties.nickname).toEqual({ + type: 'string', + minLength: 1, + default: 'string-default', + }); + expect(standaloneSchema.properties.owner.$$ref).toBe( + 'https://example.com/schemas/pet.json#owner' + ); + }); + }); + + describe('regressions', () => { + const resolveWith = (strategy, options) => makeResolve({ strategies: [strategy] })(options); + + describe('given an OpenAPI 3.1 document with a root $schema', () => { + test('should resolve it with the OpenAPI 3.1 strategy', async () => { + const spec = { + openapi: '3.1.0', + $schema, + info: { title: 'test', version: '1.0.0' }, + paths: { + '/pets': { + get: { + parameters: [{ $ref: '#/components/parameters/limit' }], + responses: { 200: { description: 'OK' } }, + }, + }, + }, + components: { + parameters: { limit: { name: 'limit', in: 'query', schema: { type: 'integer' } } }, + }, + }; + + expect(jsonSchema202012ApiDOMStrategy.match(spec)).toBe(false); + expect(await SwaggerClient.resolve({ spec: structuredClone(spec) })).toEqual( + await resolveWith(openApi31ApiDOMStrategy, { spec: structuredClone(spec) }) + ); + }); + }); + + describe('given an OpenAPI 3.1 document whose external $ref target has a root 2020-12 $schema', () => { + test('should resolve the target as an OpenAPI 3.1 Schema Object', async () => { + mockFixture('openapi-3-1-external-json-schema.json', '/api/openapi.json'); + mockFixture('user.schema.json', '/api/user.schema.json'); + const { spec, errors } = await SwaggerClient.resolve({ + url: 'https://example.com/api/openapi.json', + allowMetaPatches: false, + }); + const userAddress = { + type: 'object', + properties: { street: { type: 'string' } }, + }; + + expect(errors).toEqual([]); + expect(spec.components.schemas.User).toEqual({ + $schema, + $id: 'https://example.com/schemas/user.schema.json', + type: 'object', + properties: { name: { type: 'string' }, address: userAddress }, + $defs: { Address: userAddress }, + }); + expect(spec.components.schemas.Address).toEqual(userAddress); + }); + }); + + describe('given a document with a refused $schema', () => { + test.each([ + ['the http scheme', 'http://json-schema.org/draft/2020-12/schema'], + ['another dialect', 'http://json-schema.org/draft-07/schema#'], + ['a custom meta-schema', 'https://example.com/meta/schema'], + ])('should resolve %s with the generic strategy', async (title, dialect) => { + const spec = { + $schema: dialect, + properties: { pet: { $ref: '#/$defs/Pet' }, tag: { $ref: '#tag' } }, + $defs: { Pet: { type: 'object' }, Tag: { $anchor: 'tag', type: 'string' } }, + }; + + expect(jsonSchema202012ApiDOMStrategy.match(spec)).toBe(false); + expect(await SwaggerClient.resolve({ spec: structuredClone(spec) })).toEqual( + await resolveWith(genericStrategy, { spec: structuredClone(spec) }) + ); + }); + }); + + describe('given a document without a root $schema', () => { + test('should resolve it with the generic strategy', async () => { + const spec = { + properties: { pet: { $ref: '#/$defs/Pet' } }, + $defs: { Pet: { $schema, type: 'object' } }, + }; + + expect(await SwaggerClient.resolve({ spec: structuredClone(spec) })).toEqual( + await resolveWith(genericStrategy, { spec: structuredClone(spec) }) + ); + }); + }); + }); + }); +}); From 0db2ff2628f640d84614759c3c515ee547abd42f Mon Sep 17 00:00:00 2001 From: roberthebel Date: Tue, 27 Oct 2026 04:43:33 +0100 Subject: [PATCH 4/8] docs(skills): record JSON Schema 2020-12 decisions and lessons Record the decisions the 2020-12 work answered (cross-dialect references, unreleased upstream, content-detecting parsers, $ref to $dynamicAnchor) in design-and-delivery.md, and in strategy-design.md refract with the namespace's refract (apidom-ns-arazzo-1 reassigns JSONSchemaElement.refract) and keep the result class on a dereferenced root. FOLLOW-UPS: keyMap/getNodeType are exported on the unreleased ApiDOM branch; new item for apidom-reference's uri selector, which resolves candidate $id chains against the $ref target instead of the document's base URI (OpenAPI 3.1 and 3.2 alike). Co-Authored-By: Claude Opus 5.5 --- .claude/skills/FOLLOW-UPS.md | 16 +++++++++++++++- .../references/design-and-delivery.md | 14 ++++++++++++++ .../references/strategy-design.md | 12 ++++++++---- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/.claude/skills/FOLLOW-UPS.md b/.claude/skills/FOLLOW-UPS.md index 2452891de..d4702df30 100644 --- a/.claude/skills/FOLLOW-UPS.md +++ b/.claude/skills/FOLLOW-UPS.md @@ -31,7 +31,21 @@ ones 3.2 doesn't need. `apidom-ns-json-schema-2020-12/src/index.ts` doesn't re-export them from `src/traversal/visitor.ts` (`apidom-ns-openapi-3-1` re-exports its own). Both apidom-reference's and this repository's JSON Schema dereference strategies need them; recorded in ApiDOM's -`add-spec-version/references/json-schema-dialects.md` as a phase 3 prerequisite. +`add-spec-version/references/json-schema-dialects.md` as a phase 3 prerequisite. 2026-10-26: +exported on the ApiDOM branch `feat/json-schema-2020-12/phase-3` (`1fbd257e2`), not released yet; +`feat/json-schema-2020-12` here uses it as a tarball. + +### 2.3 ⏳ ApiDOM: the `uri` selector misses relative `$id`s nested in relative `$id`s + +apidom-reference's `dereference/strategies/{openapi-3-1,openapi-3-2,json-schema-2020-12}/selectors/uri` +resolves each candidate schema's `$id` chain against the `$ref` target (`resolveSchema$idField(uriStrippedHash, e)`) +instead of the document's base URI. In a document without an absolute root `$id`, a `$ref` to an +embedded resource whose relative `$id` sits under another relative `$id` (`$id: "nested/"` then +`$id: "tag.json"`) finds nothing, falls back to retrieving `nested/tag.json`, and stays a `$ref`. +OpenAPI 3.1 Schema Objects behave the same. Fix in ApiDOM (evaluate the chain against the +retrieval URI of the document holding the candidates); swagger-client's fixture +`json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative-nested` is a +`test.failing` that starts failing once the fix is installed (then make it a regular test). ### 2.2 ⏳ Swagger UI: `fn.resolve` lacks the 3.2 strategy diff --git a/.claude/skills/add-json-schema-support/references/design-and-delivery.md b/.claude/skills/add-json-schema-support/references/design-and-delivery.md index 8a2996ca2..e37a3abab 100644 --- a/.claude/skills/add-json-schema-support/references/design-and-delivery.md +++ b/.claude/skills/add-json-schema-support/references/design-and-delivery.md @@ -28,6 +28,20 @@ Answered (record them in the tracking doc as given): 5. **allOf merging and macros** (2026-10-25): as for OpenAPI 3.1 Schema Objects. The dereference strategy merges `allOf` unless `mode: 'strict'` and applies `modelPropertyMacro`, so a schema resolves the same standalone and inside an OpenAPI 3.1 document. +6. **References that cross dialects** (2026-10-26): out of scope in all four repositories. A + `$ref` from a 2020-12 document into another dialect's document is refracted as 2020-12. +7. **Unreleased upstream** (2026-10-26): a step starts once the ApiDOM PR is ready, on tarballs + packed from its branch (`../../add-oas-support/references/unreleased-upstream.md`), and merges + only after the ApiDOM release, with the tarballs replaced and every gate run again. No + prerelease on npm. +8. **Content-detecting JSON Schema parsers** (2026-10-26): only in this strategy's parser list, + never in one the OpenAPI strategies use: external files of OpenAPI documents often declare a + root `$schema`, and the OpenAPI dereference visitors expect them as generic ApiDOM. A + regression test resolves an OpenAPI 3.1 document whose external `$ref` target has a root + 2020-12 `$schema` (apidom-reference's saturated configuration turns detection off for the + same reason). +9. **`$ref` to a `$dynamicAnchor`** (2026-10-26): resolves like one to an `$anchor`; it comes with + apidom-reference's 2020-12 `$anchor` selector, so use that selector. ## PR Plan diff --git a/.claude/skills/add-json-schema-support/references/strategy-design.md b/.claude/skills/add-json-schema-support/references/strategy-design.md index f37db034c..f18ff0af2 100644 --- a/.claude/skills/add-json-schema-support/references/strategy-design.md +++ b/.claude/skills/add-json-schema-support/references/strategy-design.md @@ -29,8 +29,9 @@ equal to the identifier after removing an empty trailing `#`. Wrap in `try`/`cat returning its input: the OpenAPI normalization plugins (operation ids, parameters, security) have nothing to act on. Because of that the subtree resolver's `skipNormalization` list needs no entry; add one if the strategy ever normalizes. -- `resolve.js` from `openapi-3-1-apidom/resolve.js`, with `JSONSchemaElement.refract(spec)` (and - its `WeakMap` cache), `mediaTypes.latest()` of the namespace, apidom-reference's +- `resolve.js` from `openapi-3-1-apidom/resolve.js`, with the namespace's `refract(spec)` (and + its `WeakMap` cache; not `JSONSchemaElement.refract`, which apidom-ns-arazzo-1 reassigns to its + own refractor when both are loaded), `mediaTypes.latest()` of the namespace, apidom-reference's `json-schema-2020-12` resolve strategy, the parsers below plus JSON, YAML and binary, and the dereference strategy below. Keep the base URI logic, fragments (`pathDiscriminator`), `ReferenceSet`, `circularReplacer`, error collection and the invalid-pointer fallback: Swagger @@ -41,7 +42,7 @@ equal to the identifier after removing an empty trailing `#`. Wrap in `try`/`cat From `openapi-json-3-1` / `openapi-yaml-3-1`: parser names `json-schema-{json,yaml}-2020-12-swagger-client`, the namespace's media types, `JSON.parse` / -js-yaml `JSON_SCHEMA`, `JSONSchemaElement.refract`. Detect with the predicate on the parsed value +js-yaml `JSON_SCHEMA`, the namespace's `refract`. Detect with the predicate on the parsed value rather than a regex over the text: a regex also matches a nested `$schema`. External files without a root `$schema` stay with the generic JSON/YAML parsers; the dereference visitor refracts what a `$ref` points at into a schema. @@ -55,7 +56,10 @@ refracts what a `$ref` points at into a schema. (the namespace's traversal key), with its lineage, cycle, `$$ref` and error handling, and the `$anchor`/`uri` selectors imported from `dereference/strategies/json-schema-2020-12/selectors`. Drop `ReferenceElement`, `PathItemElement`, `LinkElement` and `ExampleElement`: they serve - OpenAPI objects. `$dynamicRef` stays untouched (decision 2). + OpenAPI objects. `$dynamicRef` stays untouched (decision 2). Unlike an OpenAPI root, a JSON + Schema root can have a `$ref`: its replacement keeps the `result` class (as apidom-reference's + 2020-12 visitor does), or every later `$ref` into the document finds no parse result and stays + unresolved (fixture `root-ref`). - `visitors/root.js`: merge `dereference.js`, and per decision 5 `properties.js` (`modelPropertyMacro`) and `all-of.js` (unless `mode: 'strict'`), both moved to `JSONSchema202012Element`. No `parameters.js`. From 512c92ecd858b8d97f9679878361a396c21f1399 Mon Sep 17 00:00:00 2001 From: roberthebel Date: Tue, 27 Oct 2026 08:02:45 +0100 Subject: [PATCH 5/8] fix(json-schema): detect $ref cycles to ancestors of subtrees The dereference lineage held only the pathDiscriminator fragment, so a $ref from the fragment to one of its ancestors was not a cycle: "$ref": "#" in a recursive schema overflowed the stack when Swagger UI resolved ['properties'] or ['properties', 'children'] through resolveSubtree, and ['properties', 'children', 'items'] returned null. Seed the lineage with the document root and every element along the pathDiscriminator, so such a $ref resolves to an absolute $ref, as cycles do everywhere else. Co-Authored-By: Claude Opus 5.5 --- .../json-schema-2020-12-apidom/resolve.js | 15 +++- .../json-schema-2020-12-apidom/index.js | 76 +++++++++++++++++++ 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/src/resolver/strategies/json-schema-2020-12-apidom/resolve.js b/src/resolver/strategies/json-schema-2020-12-apidom/resolve.js index 926e971e1..03c65d3bb 100644 --- a/src/resolver/strategies/json-schema-2020-12-apidom/resolve.js +++ b/src/resolver/strategies/json-schema-2020-12-apidom/resolve.js @@ -98,8 +98,19 @@ const resolveJSONSchema202012Strategy = async (options) => { const refSet = new ReferenceSet({ refs: [jsonSchemaElementReference] }); if (jsonPointer !== '') refSet.rootRef = undefined; // reset root reference as we want fragment to become the root reference - // prepare ancestors; needed for cases where fragment is not the document root - const ancestors = [new Set([fragmentElement])]; + /** + * Prepare ancestors; needed for cases where fragment is not the document root. + * The lineage holds the root and every element along the pathDiscriminator, so a $ref + * from within the fragment to one of its ancestors (`"$ref": "#"` in a recursive schema) + * is detected as a cycle instead of being expanded without end. + */ + const pathElements = [jsonSchemaElement]; + pathDiscriminator.reduce((parentElement, token) => { + const element = jsonPointerEvaluate(parentElement, jsonPointerCompile([token])); + pathElements.push(element); + return element; + }, jsonSchemaElement); + const ancestors = [new Set(pathElements)]; const errors = []; const dereferenced = await dereferenceApiDOM(fragmentElement, { diff --git a/test/resolver/strategies/json-schema-2020-12-apidom/index.js b/test/resolver/strategies/json-schema-2020-12-apidom/index.js index 3ca5f3678..a01cd35c0 100644 --- a/test/resolver/strategies/json-schema-2020-12-apidom/index.js +++ b/test/resolver/strategies/json-schema-2020-12-apidom/index.js @@ -12,6 +12,27 @@ const fixturePath = path.join(__dirname, '__fixtures__'); const loadFixture = (name) => globalThis.loadJsonFile(path.join(fixturePath, name)); const $schema = 'https://json-schema.org/draft/2020-12/schema'; +// recursive schemas whose $refs point to an ancestor of the resolved fragment +const treeBaseDoc = 'https://example.com/schemas/tree.json'; +const tree = () => ({ + $schema, + type: 'object', + properties: { + name: { type: 'string' }, + children: { type: 'array', items: { $ref: '#' } }, + }, +}); +const defsTree = () => ({ + $schema, + properties: { root: { $ref: '#/$defs/Node' } }, + $defs: { + Node: { + type: 'object', + properties: { children: { type: 'array', items: { $ref: '#/$defs/Node' } } }, + }, + }, +}); + const address = { type: 'object', properties: { @@ -288,6 +309,22 @@ describe('resolve', () => { }); }); + describe('and pathDiscriminator points into a schema that references its ancestor', () => { + test('should resolve the reference to the ancestor as a cycle', async () => { + const { spec, errors } = await SwaggerClient.resolve({ + spec: tree(), + baseDoc: treeBaseDoc, + pathDiscriminator: ['properties'], + }); + + expect(errors).toEqual([]); + expect(spec.properties.children).toEqual({ + type: 'array', + items: { $ref: treeBaseDoc }, + }); + }); + }); + describe('and mode is not set', () => { test('should merge allOf', async () => { const { spec } = await SwaggerClient.resolve({ spec: loadFixture('macros.json') }); @@ -433,6 +470,45 @@ describe('resolve', () => { $$ref: `${DEFAULT_BASE_URL}/#/$defs/Node`, }); }); + + describe('given a fragment whose $refs point to its ancestors', () => { + const rootCycle = { $ref: treeBaseDoc }; + + test.each([ + [ + ['properties'], + { name: { type: 'string' }, children: { type: 'array', items: rootCycle } }, + ], + [['properties', 'children'], { type: 'array', items: rootCycle }], + [['properties', 'children', 'items'], rootCycle], + ])( + 'should resolve %j with the $ref to the document root as a cycle', + async (fragmentPath, expected) => { + const result = await SwaggerClient.resolveSubtree(tree(), fragmentPath, { + baseDoc: treeBaseDoc, + }); + + expect(result).toEqual({ spec: expected, errors: [] }); + } + ); + + test('should resolve a $ref to an ancestor in $defs as a cycle', async () => { + const result = await SwaggerClient.resolveSubtree( + defsTree(), + ['$defs', 'Node', 'properties'], + { + baseDoc: treeBaseDoc, + } + ); + + expect(result).toEqual({ + spec: { + children: { type: 'array', items: { $ref: `${treeBaseDoc}#/$defs/Node` } }, + }, + errors: [], + }); + }); + }); }); describe('given the same schema standalone and in an OpenAPI 3.1 document', () => { From ccec9bda9db001cafd9ef83fe35c9cf6582b0a88 Mon Sep 17 00:00:00 2001 From: roberthebel Date: Tue, 27 Oct 2026 08:02:55 +0100 Subject: [PATCH 6/8] fix(json-schema): refuse AsyncAPI and Arazzo documents A document is standalone JSON Schema only when its root has the 2020-12 $schema and none of the API description fields openapi, swagger, asyncapi and arazzo. AsyncAPI and Arazzo documents may declare a root $schema; they keep the generic strategy they had, which regression tests check by comparing with a generic-only resolve. The parsers detect through the same predicate. Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 5 +-- README.md | 4 +-- src/helpers/json-schema-predicates.js | 13 ++++--- test/helpers/json-schema-predicates.js | 32 +++++++++++++++++ .../parsers/json-schema-json-2020-12/index.js | 12 +++++++ .../json-schema-2020-12-apidom/index.js | 36 +++++++++++++++++++ 6 files changed, 93 insertions(+), 9 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 308433452..356266652 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,7 +10,7 @@ This document provides comprehensive guidance for AI assistants working on the s - OpenAPI 3.0.x (3.0.0 through 3.0.4) - Swagger/OpenAPI 2.0 - Legacy Swagger 1.x (via version 2.x branch) -- Standalone JSON Schema 2020-12 documents (resolution only: root `$schema`, no `openapi`/`swagger` field) +- Standalone JSON Schema 2020-12 documents (resolution only: root `$schema`, no `openapi`/`swagger`/`asyncapi`/`arazzo` field) **Current Version**: 3.36.0 @@ -311,7 +311,8 @@ Handles $ref resolution and spec normalization. Uses different strategies based **Strategy Selection** (in `src/resolver/index.js`): Strategies are tried in order until one matches. ApiDOM is preferred for OAS 3.1. The JSON Schema strategy comes right before `generic`; its predicate (`src/helpers/json-schema-predicates.js`) refuses -OpenAPI documents and any root `$schema` other than the 2020-12 identifier. +OpenAPI, Swagger, AsyncAPI and Arazzo documents and any root `$schema` other than the 2020-12 +identifier. **SpecMap** (`src/resolver/specmap/`): Legacy resolution engine used by non-ApiDOM strategies. Handles: diff --git a/README.md b/README.md index 75cb2f67f..49e4cf885 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ Swagger Client Version | Release Date | OpenAPI Spec compatibility ### Standalone JSON Schema Swagger Client also resolves standalone JSON Schema 2020-12 documents: documents whose root `$schema` is -`https://json-schema.org/draft/2020-12/schema` (with or without an empty fragment) and which have no `openapi` -or `swagger` field. Their `$ref`s resolve against `$id` base URIs, `$anchor`s and external documents as in +`https://json-schema.org/draft/2020-12/schema` (with or without an empty fragment) and which have no `openapi`, +`swagger`, `asyncapi` or `arazzo` field. Their `$ref`s resolve against `$id` base URIs, `$anchor`s and external documents as in OpenAPI 3.1 Schema Objects, `allOf` is merged unless `mode: 'strict'`, and every `$dynamicRef` is left in place. Documents with any other `$schema` value keep the generic resolver. diff --git a/src/helpers/json-schema-predicates.js b/src/helpers/json-schema-predicates.js index d790df0f1..d8d592f26 100644 --- a/src/helpers/json-schema-predicates.js +++ b/src/helpers/json-schema-predicates.js @@ -2,20 +2,23 @@ import { isPlainObject } from 'ramda-adjunct'; /** * Standalone JSON Schema documents are recognized by the `$schema` keyword of the document root, - * when the document is not an OpenAPI or Swagger document (their Schema Objects can carry - * `$schema` too). Only the dialect identifier itself matches; an empty fragment is normalised - * because documents add or drop it. Other values (the other URI scheme, custom meta-schemas, - * dialects not supported yet) are refused and keep the generic strategy. + * when the root has none of the API description fields below: OpenAPI, Swagger, AsyncAPI and + * Arazzo documents can carry `$schema` too, and stay with their own (or the generic) strategy. + * Only the dialect identifier itself matches; an empty fragment is normalised because documents + * add or drop it. Other values (the other URI scheme, custom meta-schemas, dialects not + * supported yet) are refused and keep the generic strategy. * * https://json-schema.org/specification-links */ +const API_DESCRIPTION_FIELDS = ['openapi', 'swagger', 'asyncapi', 'arazzo']; + const normalizeDialectIdentifier = (identifier) => identifier.endsWith('#') ? identifier.slice(0, -1) : identifier; const isJSONSchemaDialect = (identifier) => (spec) => { try { if (!isPlainObject(spec)) return false; - if (Object.hasOwn(spec, 'openapi') || Object.hasOwn(spec, 'swagger')) return false; + if (API_DESCRIPTION_FIELDS.some((field) => Object.hasOwn(spec, field))) return false; const { $schema } = spec; return typeof $schema === 'string' && normalizeDialectIdentifier($schema) === identifier; diff --git a/test/helpers/json-schema-predicates.js b/test/helpers/json-schema-predicates.js index f2e98080d..f420a18af 100644 --- a/test/helpers/json-schema-predicates.js +++ b/test/helpers/json-schema-predicates.js @@ -104,6 +104,38 @@ describe('helpers', () => { ).toBe(false); }); + test('should refuse AsyncAPI and Arazzo documents with a root $schema', () => { + expect( + isJSONSchema202012({ + asyncapi: '3.0.0', + $schema: 'https://json-schema.org/draft/2020-12/schema', + info: { title: 'test', version: '1.0.0' }, + }) + ).toBe(false); + expect( + isJSONSchema202012({ + arazzo: '1.0.1', + $schema: 'https://json-schema.org/draft/2020-12/schema', + info: { title: 'test', version: '1.0.0' }, + }) + ).toBe(false); + }); + + test('should refuse an API description field whatever its value', () => { + expect( + isJSONSchema202012({ + asyncapi: null, + $schema: 'https://json-schema.org/draft/2020-12/schema', + }) + ).toBe(false); + expect( + isJSONSchema202012({ + arazzo: undefined, + $schema: 'https://json-schema.org/draft/2020-12/schema', + }) + ).toBe(false); + }); + test('should refuse an OpenAPI 3.1 document with $schema in a Schema Object', () => { expect( isJSONSchema202012({ diff --git a/test/resolver/apidom/reference/parse/parsers/json-schema-json-2020-12/index.js b/test/resolver/apidom/reference/parse/parsers/json-schema-json-2020-12/index.js index 735deaf28..009771e06 100644 --- a/test/resolver/apidom/reference/parse/parsers/json-schema-json-2020-12/index.js +++ b/test/resolver/apidom/reference/parse/parsers/json-schema-json-2020-12/index.js @@ -145,6 +145,18 @@ describe('JSONSchemaJSON2020_12Parser', () => { }); }); + describe('and the document is an AsyncAPI document with root $schema', () => { + test('should return false', async () => { + const file = new File({ + uri: '/path/to/asyncapi.json', + data: '{"asyncapi": "3.0.0", "$schema": "https://json-schema.org/draft/2020-12/schema"}', + }); + const parser = new JSONSchemaJSON2020_12Parser(); + + expect(await parser.canParse(file)).toBe(false); + }); + }); + describe('and file data is not JSON', () => { test('should return false', async () => { const file = new File({ diff --git a/test/resolver/strategies/json-schema-2020-12-apidom/index.js b/test/resolver/strategies/json-schema-2020-12-apidom/index.js index a01cd35c0..c00851720 100644 --- a/test/resolver/strategies/json-schema-2020-12-apidom/index.js +++ b/test/resolver/strategies/json-schema-2020-12-apidom/index.js @@ -644,6 +644,42 @@ describe('resolve', () => { ); }); }); + + describe('given an AsyncAPI or Arazzo document with a root 2020-12 $schema', () => { + test('should resolve an AsyncAPI 3.0 document with the generic strategy', async () => { + const spec = { + asyncapi: '3.0.0', + $schema, + channels: { a: { $ref: '#/components/channels/A' } }, + components: { channels: { A: { address: 'a' } } }, + }; + const resolved = await SwaggerClient.resolve({ spec: structuredClone(spec) }); + + expect(jsonSchema202012ApiDOMStrategy.match(spec)).toBe(false); + expect(resolved).toEqual( + await resolveWith(genericStrategy, { spec: structuredClone(spec) }) + ); + expect(resolved.spec.channels.a).toEqual({ + address: 'a', + $$ref: '#/components/channels/A', + }); + }); + + test('should resolve an Arazzo 1.0.1 document with the generic strategy', async () => { + const spec = { + arazzo: '1.0.1', + $schema, + info: { title: 'test', version: '1.0.0' }, + workflows: [{ workflowId: 'w', inputs: { $ref: '#/components/inputs/In' }, steps: [] }], + components: { inputs: { In: { type: 'object' } } }, + }; + + expect(jsonSchema202012ApiDOMStrategy.match(spec)).toBe(false); + expect(await SwaggerClient.resolve({ spec: structuredClone(spec) })).toEqual( + await resolveWith(genericStrategy, { spec: structuredClone(spec) }) + ); + }); + }); }); }); }); From a56a5c9ca1328b4f563ec46a810b8a9a61b9f69b Mon Sep 17 00:00:00 2001 From: roberthebel Date: Tue, 27 Oct 2026 08:02:55 +0100 Subject: [PATCH 7/8] test(json-schema): pin $anchor lookup in embedded resources apidom-reference's $anchor selector searches embedded $id resources too and takes the first match, so "#tag" can select the embedded resource's schema instead of the referencing resource's (JSON Schema Core 8.2.2). OpenAPI 3.1 Schema Objects behave the same. Pin it with a test.failing fixture that starts failing once ApiDOM stops the search at nested $ids. Co-Authored-By: Claude Opus 5.5 --- .../dereferenced.json | 28 +++++++++++++++++++ .../anchor-embedded-resource/root.json | 17 +++++++++++ .../json-schema/index.js | 15 ++++++++++ 3 files changed, 60 insertions(+) create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-embedded-resource/dereferenced.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-embedded-resource/root.json diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-embedded-resource/dereferenced.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-embedded-resource/dereferenced.json new file mode 100644 index 000000000..ed8689097 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-embedded-resource/dereferenced.json @@ -0,0 +1,28 @@ +[ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/schemas/root.json", + "type": "object", + "properties": { + "tag": { + "$anchor": "tag", + "type": "string" + } + }, + "$defs": { + "Embedded": { + "$id": "embedded.json", + "$defs": { + "Tag": { + "$anchor": "tag", + "type": "number" + } + } + }, + "Tag": { + "$anchor": "tag", + "type": "string" + } + } + } +] diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-embedded-resource/root.json b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-embedded-resource/root.json new file mode 100644 index 000000000..5638a13db --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-embedded-resource/root.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/schemas/root.json", + "type": "object", + "properties": { + "tag": { "$ref": "#tag" } + }, + "$defs": { + "Embedded": { + "$id": "embedded.json", + "$defs": { + "Tag": { "$anchor": "tag", "type": "number" } + } + }, + "Tag": { "$anchor": "tag", "type": "string" } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/index.js b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/index.js index f1db343dd..21cbc2200 100644 --- a/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/index.js +++ b/test/resolver/apidom/reference/dereference/strategies/json-schema-2020-12-swagger-client/json-schema/index.js @@ -122,6 +122,21 @@ describe('dereference', () => { }); }); + describe('given a $ref to an $anchor whose name an embedded schema resource uses too', () => { + /** + * An $anchor belongs to the schema resource containing it (JSON Schema Core 8.2.2), so + * "#tag" from the root resource must select the root's $defs/Tag. apidom-reference's + * $anchor selector (OpenAPI 3.1 and JSON Schema 2020-12 alike) searches embedded $id + * resources too and takes the first match, the embedded one. When ApiDOM stops the + * search at nested $ids, this test starts failing: turn it into a regular test. + */ + test.failing('should select the $anchor of the referencing resource', async () => { + const actual = await dereferenceFixture('anchor-embedded-resource'); + + expect(toValue(actual)).toEqual(loadDereferenced('anchor-embedded-resource')); + }); + }); + describe('given a $ref to a $dynamicAnchor', () => { test('should dereference it like a $ref to an $anchor', async () => { const actual = await dereferenceFixture('dynamic-anchor'); From b4815235c73972631cfe761b6a4a11ce2b23c183 Mon Sep 17 00:00:00 2001 From: roberthebel Date: Tue, 27 Oct 2026 08:02:55 +0100 Subject: [PATCH 8/8] docs(skills): record D18, D19 and the 2020-12 review findings State the matching rule as no openapi, swagger, asyncapi or arazzo field (D18) wherever it appears, record D18 and D19 in design-and-delivery.md, require the pathDiscriminator lineage in strategy-design.md, and in FOLLOW-UPS put 2.3 after 2.2 and add 2.4 for apidom-reference's $anchor selector, which searches embedded schema resources. Co-Authored-By: Claude Opus 5.5 --- .claude/skills/FOLLOW-UPS.md | 20 +++++++++++++++---- .../skills/add-json-schema-support/SKILL.md | 5 +++-- .../graders/03-root-predicate.md | 2 +- .../references/design-and-delivery.md | 11 ++++++++++ .../references/dialects.md | 6 ++++-- .../references/strategy-design.md | 7 +++++-- .../references/review-checklist.md | 4 ++-- 7 files changed, 42 insertions(+), 13 deletions(-) diff --git a/.claude/skills/FOLLOW-UPS.md b/.claude/skills/FOLLOW-UPS.md index d4702df30..45b5c053e 100644 --- a/.claude/skills/FOLLOW-UPS.md +++ b/.claude/skills/FOLLOW-UPS.md @@ -35,6 +35,12 @@ and this repository's JSON Schema dereference strategies need them; recorded in exported on the ApiDOM branch `feat/json-schema-2020-12/phase-3` (`1fbd257e2`), not released yet; `feat/json-schema-2020-12` here uses it as a tarball. +### 2.2 ⏳ Swagger UI: `fn.resolve` lacks the 3.2 strategy + +`src/core/plugins/swagger-client/index.js` in swagger-ui lists 3.1, 3.0, 2.0 and generic for +`fn.resolve`, while `resolveSubtree` (the path Swagger UI uses) has 3.2. `resolveSpec` is +deprecated, so it only affects callers of `fn.resolve`; recorded in swagger-ui's FOLLOW-UPS. + ### 2.3 ⏳ ApiDOM: the `uri` selector misses relative `$id`s nested in relative `$id`s apidom-reference's `dereference/strategies/{openapi-3-1,openapi-3-2,json-schema-2020-12}/selectors/uri` @@ -47,11 +53,17 @@ retrieval URI of the document holding the candidates); swagger-client's fixture `json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative-nested` is a `test.failing` that starts failing once the fix is installed (then make it a regular test). -### 2.2 ⏳ Swagger UI: `fn.resolve` lacks the 3.2 strategy +### 2.4 ⏳ ApiDOM: the `$anchor` selector searches embedded schema resources -`src/core/plugins/swagger-client/index.js` in swagger-ui lists 3.1, 3.0, 2.0 and generic for -`fn.resolve`, while `resolveSubtree` (the path Swagger UI uses) has 3.2. `resolveSpec` is -deprecated, so it only affects callers of `fn.resolve`; recorded in swagger-ui's FOLLOW-UPS. +apidom-reference's `dereference/strategies/{openapi-3-1,openapi-3-2,json-schema-2020-12}/selectors/$anchor` +finds the first schema in pre-order whose `$anchor` (in 2020-12 also `$dynamicAnchor`) matches, including +schemas inside embedded `$id` resources. An anchor belongs to the schema resource containing it +(JSON Schema Core 8.2.2), so when an embedded resource reuses the name, `"$ref": "#tag"` can +select the embedded resource's schema instead of the referencing resource's. OpenAPI 3.1 Schema +Objects behave the same. Fix in the same ApiDOM follow-up as 2.3 (stop the search at nested +`$id`s); swagger-client's fixture +`json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-embedded-resource` is a +`test.failing` that starts failing once the fix is installed (then make it a regular test). ## 3. Evals diff --git a/.claude/skills/add-json-schema-support/SKILL.md b/.claude/skills/add-json-schema-support/SKILL.md index bed913a52..c2956fd5c 100644 --- a/.claude/skills/add-json-schema-support/SKILL.md +++ b/.claude/skills/add-json-schema-support/SKILL.md @@ -1,6 +1,6 @@ --- name: add-json-schema-support -description: Adds resolution of standalone JSON Schema documents (root `$schema`, no `openapi`/`swagger` field) to swagger-client, 2020-12 first, then 2019-09, Draft 7, 6 and 4, as in the "Standalone JSON Schema" design doc. Checks that the installed ApiDOM has the dialect's namespace and apidom-reference strategies, then builds a root-only `$schema` predicate, the `json-schema--apidom` resolve strategy over ApiDOM (so `$ref`s resolve against `$id` base URIs and `$anchor`s), a schema-only dereference strategy, parsers, registration ahead of the generic strategy and tests. Swagger UI resolves `$ref`s in its preview through this strategy. Use whenever someone wants swagger-client, Swagger UI or SwaggerEditor to resolve or dereference `$ref`s in a JSON Schema document or a specific draft, or asks why `$id`/`$anchor` references in a standalone schema don't resolve, even if they don't name this skill. Not for Schema Objects inside OpenAPI documents (that is add-oas-support). +description: Adds resolution of standalone JSON Schema documents (root `$schema`, no `openapi`/`swagger`/`asyncapi`/`arazzo` field) to swagger-client, 2020-12 first, then 2019-09, Draft 7, 6 and 4, as in the "Standalone JSON Schema" design doc. Checks that the installed ApiDOM has the dialect's namespace and apidom-reference strategies, then builds a root-only `$schema` predicate, the `json-schema--apidom` resolve strategy over ApiDOM (so `$ref`s resolve against `$id` base URIs and `$anchor`s), a schema-only dereference strategy, parsers, registration ahead of the generic strategy and tests. Swagger UI resolves `$ref`s in its preview through this strategy. Use whenever someone wants swagger-client, Swagger UI or SwaggerEditor to resolve or dereference `$ref`s in a JSON Schema document or a specific draft, or asks why `$id`/`$anchor` references in a standalone schema don't resolve, even if they don't name this skill. Not for Schema Objects inside OpenAPI documents (that is add-oas-support). --- # Add Standalone JSON Schema Resolution to swagger-client @@ -52,7 +52,8 @@ resolves `$ref`s through swagger-client, so swagger-client owns `$ref` resolutio Read `references/strategy-design.md` and `../add-oas-support/references/review-lessons.md`, then: 1. Predicate `isJSONSchema` in `src/helpers/json-schema-predicates.js` (root only, the - exact identifier with an empty fragment normalised, no `openapi`/`swagger` field). + exact identifier with an empty fragment normalised, no `openapi`, `swagger`, `asyncapi` or + `arazzo` field). 2. Strategy `src/resolver/strategies/json-schema--apidom/` (`index.js`, `resolve.js`, `normalize.js`). 3. Parsers `src/resolver/apidom/reference/parse/parsers/json-schema-{json,yaml}-/`. diff --git a/.claude/skills/add-json-schema-support/evals/json-schema-2020-12-plan/graders/03-root-predicate.md b/.claude/skills/add-json-schema-support/evals/json-schema-2020-12-plan/graders/03-root-predicate.md index fa83e056f..26fa841fc 100644 --- a/.claude/skills/add-json-schema-support/evals/json-schema-2020-12-plan/graders/03-root-predicate.md +++ b/.claude/skills/add-json-schema-support/evals/json-schema-2020-12-plan/graders/03-root-predicate.md @@ -4,4 +4,4 @@ focus: trace weight: 1 --- -PLAN.md matches documents with a root-only $schema predicate (exact 2020-12 identifier, empty fragment normalised, no openapi or swagger field) that leaves the http:// variant, custom meta-schemas and nested $schema to the generic strategy. +PLAN.md matches documents with a root-only $schema predicate (exact 2020-12 identifier, empty fragment normalised, no openapi, swagger, asyncapi or arazzo field) that leaves the http:// variant, custom meta-schemas and nested $schema to the generic strategy. diff --git a/.claude/skills/add-json-schema-support/references/design-and-delivery.md b/.claude/skills/add-json-schema-support/references/design-and-delivery.md index e37a3abab..dd41536fd 100644 --- a/.claude/skills/add-json-schema-support/references/design-and-delivery.md +++ b/.claude/skills/add-json-schema-support/references/design-and-delivery.md @@ -42,6 +42,17 @@ Answered (record them in the tracking doc as given): same reason). 9. **`$ref` to a `$dynamicAnchor`** (2026-10-26): resolves like one to an `$anchor`; it comes with apidom-reference's 2020-12 `$anchor` selector, so use that selector. +10. **API description documents with a root `$schema`** (2026-10-27): a document is standalone + JSON Schema only when its root has the dialect's `$schema` and none of the API description + fields `openapi`, `swagger`, `asyncapi` and `arazzo`. AsyncAPI and Arazzo documents with a + root `$schema` keep the strategy they had (`generic`); regression tests compare them with a + `generic`-only resolve. +11. **`$ref`s to ancestors of a resolved subtree** (2026-10-27): the resolve strategy seeds the + dereference lineage with the document root and every element along `pathDiscriminator`, so + a `$ref` from the fragment to an ancestor (`"$ref": "#"`) is a cycle, not endless expansion. + The JSON Schema strategy gets it in its own PR; the same defect in `openapi-3-1-apidom` and + `openapi-3-2-apidom` is fixed in a separate PR off `main` + (`fix/resolver-subtree-ancestor-cycles`), not in the JSON Schema PR. ## PR Plan diff --git a/.claude/skills/add-json-schema-support/references/dialects.md b/.claude/skills/add-json-schema-support/references/dialects.md index 252d067c0..37c8386f8 100644 --- a/.claude/skills/add-json-schema-support/references/dialects.md +++ b/.claude/skills/add-json-schema-support/references/dialects.md @@ -17,8 +17,10 @@ Draft 5 kept the Draft 4 meta-schema, so Draft 4 documents cover it. ## Matching (same as Swagger UI) -- Only the document root's `$schema`, and only when there is no `openapi` or `swagger` field: - OpenAPI 3.1+ Schema Objects carry `$schema` too. +- Only the document root's `$schema`, and only when the root has none of the API description + fields `openapi`, `swagger`, `asyncapi` and `arazzo` (decision 10 in `design-and-delivery.md`): + OpenAPI 3.1+ Schema Objects carry `$schema` too, and API description documents can declare a + root `$schema`; they keep their own strategy or `generic`. - The identifier exactly, with an empty fragment normalised: up to Draft 7 the identifiers end in `#`, from 2019-09 they don't; documents add or drop it. - Nothing else: the other scheme (`http://json-schema.org/draft/2020-12/schema`), custom diff --git a/.claude/skills/add-json-schema-support/references/strategy-design.md b/.claude/skills/add-json-schema-support/references/strategy-design.md index f18ff0af2..3272e2965 100644 --- a/.claude/skills/add-json-schema-support/references/strategy-design.md +++ b/.claude/skills/add-json-schema-support/references/strategy-design.md @@ -19,7 +19,8 @@ From the installed ApiDOM release (Step 0's `spec-status.mjs` checks all three): ## Predicate: `src/helpers/json-schema-predicates.js` -`isJSONSchema202012(spec)`: a plain object, no `openapi` or `swagger` key, `$schema` a string +`isJSONSchema202012(spec)`: a plain object, none of the keys `openapi`, `swagger`, `asyncapi` and +`arazzo` (whatever their value), `$schema` a string equal to the identifier after removing an empty trailing `#`. Wrap in `try`/`catch` like `openapi-predicates.js`. Matching rules: `dialects.md`. @@ -35,7 +36,9 @@ equal to the identifier after removing an empty trailing `#`. Wrap in `try`/`cat `json-schema-2020-12` resolve strategy, the parsers below plus JSON, YAML and binary, and the dereference strategy below. Keep the base URI logic, fragments (`pathDiscriminator`), `ReferenceSet`, `circularReplacer`, error collection and the invalid-pointer fallback: Swagger - UI resolves subtrees lazily, so fragments are the main path. + UI resolves subtrees lazily, so fragments are the main path. Seed the `ancestors` lineage with + the root and every element along `pathDiscriminator`, not the fragment alone (decision 11): + otherwise `"$ref": "#"` inside a resolved subtree expands until the stack overflows. - `normalize.js`: the identity, exported for the same shape as the other strategies. ## Parsers: `src/resolver/apidom/reference/parse/parsers/json-schema-{json,yaml}-2020-12/` diff --git a/.claude/skills/review-spec-support/references/review-checklist.md b/.claude/skills/review-spec-support/references/review-checklist.md index 00e900307..09eed9628 100644 --- a/.claude/skills/review-spec-support/references/review-checklist.md +++ b/.claude/skills/review-spec-support/references/review-checklist.md @@ -14,8 +14,8 @@ are in `.claude/skills/add-oas-support/references/review-lessons.md` (numbers in - [ ] OpenAPI: the regex accepts every patch of the line and nothing else; `isOpenAPI3` includes it. Run it against the new version, the parent and the next line. - [ ] JSON Schema: root `$schema` only, the exact identifier with an empty fragment normalised, - no `openapi`/`swagger` field; refuses the other scheme, custom meta-schemas and a nested - `$schema`. + no `openapi`, `swagger`, `asyncapi` or `arazzo` field; refuses the other scheme, custom + meta-schemas, a nested `$schema` and API description documents with a root `$schema`. ## 3. Strategy