From 60d21c31603eb53ca1f1036a56752388edc33c30 Mon Sep 17 00:00:00 2001 From: Nauman Date: Tue, 4 Apr 2023 14:00:46 +0500 Subject: [PATCH 01/16] docs(repo): add additional style guide examples to readme (#2449) --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 45d53badd..4718b72dc 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ Stoplight has a set of Spectral rulesets that were created to help users get sta - [OWASP Top 10](https://apistylebook.stoplight.io/docs/owasp-top-10) - Set of rules to enforce [OWASP security guidelines](https://owasp.org/www-project-api-security/). - [URL Style Guidelines](https://apistylebook.stoplight.io/docs/url-guidelines) - Set of rules to help developers make better and consistent endpoints. +- [Documentation](https://github.com/stoplightio/spectral-documentation) - Scan an OpenAPI description to make sure you're leveraging enough of its features to help documentation tools like Stoplight Elements, ReDoc, and Swagger UI build the best quality API Reference Documentation possible. There are also rulesets created by many companies to improve their APIs. You can use these as is to lint your OpenAPI descriptions, or use these as a reference to learn more about what rules you would want in your own ruleset: @@ -99,7 +100,9 @@ There are also rulesets created by many companies to improve their APIs. You can - [Tranascom](https://github.com/transcom/mymove/blob/master/swagger-def/.spectral.yml) - Don't even think about using anything other than `application/json`. - [Zalando](https://apistylebook.stoplight.io/docs/zalando-restful-api-guidelines) - Based on [Zalando's RESTFUL API Guidelines](https://github.com/zalando/restful-api-guidelines), covers a wide-range of API topics such as versioning standards, property naming standards, the default format for request/response properties, and more. -Here are [more real-world examples](https://github.com/stoplightio/spectral-rulesets) of Spectral in action. +Check out some additional style guides here: +- [Spectral Rulesets by Stoplight](https://github.com/stoplightio/spectral-rulesets) +- [API Stylebook by Stoplight](https://apistylebook.stoplight.io) ## ⚙️ Integrations From d3bebe44d3f3e543592327ffbdf4393e2989ae5c Mon Sep 17 00:00:00 2001 From: Pam Goodrich <91907863+pamgoodrich@users.noreply.github.com> Date: Thu, 6 Apr 2023 12:45:36 -0600 Subject: [PATCH 02/16] docs(repo): linting with ssgs (#2450) --- README.md | 1 + docs/guides/2-cli.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/README.md b/README.md index 4718b72dc..cd990c5cd 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ There are also rulesets created by many companies to improve their APIs. You can - [Zalando](https://apistylebook.stoplight.io/docs/zalando-restful-api-guidelines) - Based on [Zalando's RESTFUL API Guidelines](https://github.com/zalando/restful-api-guidelines), covers a wide-range of API topics such as versioning standards, property naming standards, the default format for request/response properties, and more. Check out some additional style guides here: + - [Spectral Rulesets by Stoplight](https://github.com/stoplightio/spectral-rulesets) - [API Stylebook by Stoplight](https://apistylebook.stoplight.io) diff --git a/docs/guides/2-cli.md b/docs/guides/2-cli.md index e4707dd3e..120a0f499 100644 --- a/docs/guides/2-cli.md +++ b/docs/guides/2-cli.md @@ -60,6 +60,8 @@ Here you can build a [custom ruleset](../getting-started/3-rulesets.md), or exte - [OpenAPI ruleset](../reference/openapi-rules.md) - [AsyncAPI ruleset](../reference/asyncapi-rules.md) +> If you use rules created or updated in a hosted [Stoplight API project](https://docs.stoplight.io/docs/platform/branches/pam-716-updated-landing-page/c433d678d027a-create-rules) with the Spectral CLI, you must publish the project from Stoplight before rule updates are used for linting. + ## Error Results Spectral has a few different error severities: `error`, `warn`, `info`, and `hint`, and they're in order from highest to lowest. By default, all results are shown regardless of severity, but since v5.0, only the presence of errors causes a failure status code of 1. Seeing results and getting a failure code for it are now two different things. From deb85e218ab959c5a504575ffb8a0641deb0eb72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Apr 2023 22:27:19 +0200 Subject: [PATCH 03/16] chore(deps): bump vm2 from 3.9.11 to 3.9.17 (#2458) Bumps [vm2](https://github.com/patriksimek/vm2) from 3.9.11 to 3.9.17. - [Release notes](https://github.com/patriksimek/vm2/releases) - [Changelog](https://github.com/patriksimek/vm2/blob/master/CHANGELOG.md) - [Commits](https://github.com/patriksimek/vm2/compare/3.9.11...3.9.17) --- updated-dependencies: - dependency-name: vm2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 72539fb9e..222eb04ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13172,14 +13172,14 @@ __metadata: linkType: hard "vm2@npm:^3.9.8": - version: 3.9.11 - resolution: "vm2@npm:3.9.11" + version: 3.9.17 + resolution: "vm2@npm:3.9.17" dependencies: acorn: ^8.7.0 acorn-walk: ^8.2.0 bin: vm2: bin/vm2 - checksum: aab39e6e4b59146d24abacd79f490e854a6e058a8b23d93d2be5aca7720778e2605d2cc028ccc4a5f50d3d91b0c38be9a6247a80d2da1a6de09425cc437770b4 + checksum: 9a03740a40ab2be5e3348a95fb31512da1a3c85318febb07e5299fa103ff05bcd7b6f458211fa38a1281dc27beccd04ff90355fc1d34fe2ee6ca10d0bb8c6f35 languageName: node linkType: hard From 45e817ffb9b682779c8e20153405879d9205454d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ro=C5=BCek?= Date: Thu, 20 Apr 2023 22:33:14 +0200 Subject: [PATCH 04/16] fix(ruleset-migrator): transform functions under overrides (#2459) --- .../overrides-variant-2/output.cjs | 24 +++++++ .../overrides-variant-2/output.mjs | 24 +++++++ .../overrides-variant-2/ruleset.yaml | 16 +++++ .../src/transformers/rules.ts | 2 +- .../new-rule-legacy-ruleset.scenario | 62 +++++++++++++++++++ 5 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 packages/ruleset-migrator/src/__tests__/__fixtures__/overrides-variant-2/output.cjs create mode 100644 packages/ruleset-migrator/src/__tests__/__fixtures__/overrides-variant-2/output.mjs create mode 100644 packages/ruleset-migrator/src/__tests__/__fixtures__/overrides-variant-2/ruleset.yaml create mode 100644 test-harness/scenarios/overrides/new-rule-legacy-ruleset.scenario diff --git a/packages/ruleset-migrator/src/__tests__/__fixtures__/overrides-variant-2/output.cjs b/packages/ruleset-migrator/src/__tests__/__fixtures__/overrides-variant-2/output.cjs new file mode 100644 index 000000000..f72b51d08 --- /dev/null +++ b/packages/ruleset-migrator/src/__tests__/__fixtures__/overrides-variant-2/output.cjs @@ -0,0 +1,24 @@ +const { truthy } = require('@stoplight/spectral-functions'); +const { oas } = require('@stoplight/spectral-rulesets'); +module.exports = { + extends: [oas], + aliases: { + OperationObject: ['#PathItem[get,put,post,delete,options,head,patch,trace]'], + PathItem: ['$.paths[*]'], + }, + overrides: [ + { + files: ['*'], + rules: { + 'operation-description': { + given: '#OperationObject', + then: { + field: 'summary', + function: truthy, + }, + severity: 'warn', + }, + }, + }, + ], +}; diff --git a/packages/ruleset-migrator/src/__tests__/__fixtures__/overrides-variant-2/output.mjs b/packages/ruleset-migrator/src/__tests__/__fixtures__/overrides-variant-2/output.mjs new file mode 100644 index 000000000..dfc6f6d57 --- /dev/null +++ b/packages/ruleset-migrator/src/__tests__/__fixtures__/overrides-variant-2/output.mjs @@ -0,0 +1,24 @@ +import { truthy } from '@stoplight/spectral-functions'; +import { oas } from '@stoplight/spectral-rulesets'; +export default { + extends: [oas], + aliases: { + OperationObject: ['#PathItem[get,put,post,delete,options,head,patch,trace]'], + PathItem: ['$.paths[*]'], + }, + overrides: [ + { + files: ['*'], + rules: { + 'operation-description': { + given: '#OperationObject', + then: { + field: 'summary', + function: truthy, + }, + severity: 'warn', + }, + }, + }, + ], +}; diff --git a/packages/ruleset-migrator/src/__tests__/__fixtures__/overrides-variant-2/ruleset.yaml b/packages/ruleset-migrator/src/__tests__/__fixtures__/overrides-variant-2/ruleset.yaml new file mode 100644 index 000000000..7a7e9d7cf --- /dev/null +++ b/packages/ruleset-migrator/src/__tests__/__fixtures__/overrides-variant-2/ruleset.yaml @@ -0,0 +1,16 @@ +extends: + - 'spectral:oas' +aliases: + OperationObject: + - '#PathItem[get,put,post,delete,options,head,patch,trace]' + PathItem: + - $.paths[*] +overrides: + - files: ['*'] + rules: + operation-description: + given: '#OperationObject' + then: + field: 'summary' + function: 'truthy' + severity: warn diff --git a/packages/ruleset-migrator/src/transformers/rules.ts b/packages/ruleset-migrator/src/transformers/rules.ts index 74bc05c21..33693d550 100644 --- a/packages/ruleset-migrator/src/transformers/rules.ts +++ b/packages/ruleset-migrator/src/transformers/rules.ts @@ -85,7 +85,7 @@ const transformer: Transformer = function (hooks) { ]); hooks.add([ - /^\/rules\/[^/]+\/then\/(?:[0-9]+\/)?function$/, + /^(?:\/overrides\/\d+)?\/rules\/[^/]+\/then\/(?:\d+\/)?function$/, (value, ctx): namedTypes.Identifier | namedTypes.UnaryExpression => { assertString(value); diff --git a/test-harness/scenarios/overrides/new-rule-legacy-ruleset.scenario b/test-harness/scenarios/overrides/new-rule-legacy-ruleset.scenario new file mode 100644 index 000000000..e4fb03303 --- /dev/null +++ b/test-harness/scenarios/overrides/new-rule-legacy-ruleset.scenario @@ -0,0 +1,62 @@ +====test==== +Respect overrides with rules-only +====asset:spectral.yaml==== +aliases: + OperationObject: + - "#PathItem[get,put,post,delete,options,head,patch,trace]" + PathItem: + - $.paths[*] +rules: + operation-description: + given: '#OperationObject' + then: + field: 'description' + function: 'truthy' + severity: error +overrides: + - files: ["v2/**/*.json"] + rules: + summary-description: + given: '#OperationObject' + then: + field: 'summary' + function: 'truthy' + severity: warn +====asset:v2/document.json==== +{ + "openapi": "3.1.0", + "info": { + "description": "", + "title": "" + }, + "paths": { + "/": { + "get": {} + } + } +} +====asset:legacy/document.json==== +{ + "openapi": "3.1.0", + "info": { + "description": "", + "title": "" + }, + "paths": { + "/": { + "get": {} + } + } +} +====command==== +{bin} lint **/*.json --ruleset {asset:spectral.yaml} --fail-on-unmatched-globs +====stdout==== + +{asset:legacy/document.json} + 9:13 error operation-description "get.description" property must be truthy paths./.get + +{asset:v2/document.json} + 9:13 error operation-description "get.description" property must be truthy paths./.get + 9:13 warning summary-description "get.summary" property must be truthy paths./.get + +✖ 3 problems (2 errors, 1 warning, 0 infos, 0 hints) From 141b033a7aceb653ff91bd7ac7fb168f733ebf59 Mon Sep 17 00:00:00 2001 From: stoplight-bot Date: Fri, 21 Apr 2023 12:10:34 +0000 Subject: [PATCH 05/16] chore(release): 1.9.3 [skip ci] # [@stoplight/spectral-ruleset-migrator-v1.9.3](https://github.com/stoplightio/spectral/compare/@stoplight/spectral-ruleset-migrator-v1.9.2...@stoplight/spectral-ruleset-migrator-v1.9.3) (2023-04-21) ### Bug Fixes * **ruleset-migrator:** transform functions under overrides ([#2459](https://github.com/stoplightio/spectral/issues/2459)) ([45e817f](https://github.com/stoplightio/spectral/commit/45e817ffb9b682779c8e20153405879d9205454d)) --- packages/ruleset-migrator/CHANGELOG.md | 7 +++++++ packages/ruleset-migrator/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/ruleset-migrator/CHANGELOG.md b/packages/ruleset-migrator/CHANGELOG.md index f7ab63f06..bea2e694f 100644 --- a/packages/ruleset-migrator/CHANGELOG.md +++ b/packages/ruleset-migrator/CHANGELOG.md @@ -1,3 +1,10 @@ +# [@stoplight/spectral-ruleset-migrator-v1.9.3](https://github.com/stoplightio/spectral/compare/@stoplight/spectral-ruleset-migrator-v1.9.2...@stoplight/spectral-ruleset-migrator-v1.9.3) (2023-04-21) + + +### Bug Fixes + +* **ruleset-migrator:** transform functions under overrides ([#2459](https://github.com/stoplightio/spectral/issues/2459)) ([45e817f](https://github.com/stoplightio/spectral/commit/45e817ffb9b682779c8e20153405879d9205454d)) + # [@stoplight/spectral-ruleset-migrator-v1.9.2](https://github.com/stoplightio/spectral/compare/@stoplight/spectral-ruleset-migrator-v1.9.1...@stoplight/spectral-ruleset-migrator-v1.9.2) (2023-02-22) diff --git a/packages/ruleset-migrator/package.json b/packages/ruleset-migrator/package.json index 6121002dd..3e9e22c0b 100644 --- a/packages/ruleset-migrator/package.json +++ b/packages/ruleset-migrator/package.json @@ -1,6 +1,6 @@ { "name": "@stoplight/spectral-ruleset-migrator", - "version": "1.9.2", + "version": "1.9.3", "homepage": "https://github.com/stoplightio/spectral", "bugs": "https://github.com/stoplightio/spectral/issues", "author": "Stoplight ", From 5bd7e430c98cca31368a45b92df7602a3da32c4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Apr 2023 09:25:24 +0200 Subject: [PATCH 06/16] chore(deps-dev): bump xml2js from 0.4.23 to 0.5.0 (#2453) Bumps [xml2js](https://github.com/Leonidas-from-XIV/node-xml2js) from 0.4.23 to 0.5.0. - [Release notes](https://github.com/Leonidas-from-XIV/node-xml2js/releases) - [Commits](https://github.com/Leonidas-from-XIV/node-xml2js/commits/0.5.0) --- updated-dependencies: - dependency-name: xml2js dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/cli/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 1af38ee84..d4b11f105 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -65,7 +65,7 @@ "nock": "^13.1.3", "node-html-parser": "^4.1.5", "pkg": "^5.8.0", - "xml2js": "^0.4.23" + "xml2js": "^0.5.0" }, "pkg": { "scripts": [ diff --git a/yarn.lock b/yarn.lock index 222eb04ea..99849c982 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2526,7 +2526,7 @@ __metadata: strip-ansi: 6.0 text-table: 0.2 tslib: ^2.3.0 - xml2js: ^0.4.23 + xml2js: ^0.5.0 yargs: 17.3.1 bin: spectral: ./dist/index.js @@ -13362,13 +13362,13 @@ __metadata: languageName: node linkType: hard -"xml2js@npm:^0.4.23": - version: 0.4.23 - resolution: "xml2js@npm:0.4.23" +"xml2js@npm:^0.5.0": + version: 0.5.0 + resolution: "xml2js@npm:0.5.0" dependencies: sax: ">=0.6.0" xmlbuilder: ~11.0.0 - checksum: ca0cf2dfbf6deeaae878a891c8fbc0db6fd04398087084edf143cdc83d0509ad0fe199b890f62f39c4415cf60268a27a6aed0d343f0658f8779bd7add690fa98 + checksum: 1aa71d62e5bc2d89138e3929b9ea46459157727759cbc62ef99484b778641c0cd21fb637696c052d901a22f82d092a3e740a16b4ce218e81ac59b933535124ea languageName: node linkType: hard From 92dab78d0c07e6919c0485cadbe5aa2391a53e8b Mon Sep 17 00:00:00 2001 From: Phil Adams Date: Tue, 25 Apr 2023 03:31:05 -0500 Subject: [PATCH 07/16] fix(rulesets): avoid false errors from ajv (#2408) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(rulesets): avoid false errors from ajv This commit modifies the oasExample function so that example fields are removed from the schema to be used for validation. This is needed because the presence of an "example" field in a schema confuses ajv in certain scenarios. References: - https://github.com/stoplightio/spectral/issues/2081 - https://github.com/stoplightio/spectral/issues/2140 - https://github.com/ajv-validator/ajv/issues/1426 * docs(repo): fix lint warning in README Signed-off-by: Phil Adams * chore(rulesets): use traverse --------- Signed-off-by: Phil Adams Co-authored-by: Jakub Rożek --- package.json | 3 +- .../functions/__tests__/oasExample.test.ts | 451 ++++++++++++++++++ .../rulesets/src/oas/functions/oasExample.ts | 22 + 3 files changed, 475 insertions(+), 1 deletion(-) create mode 100644 packages/rulesets/src/oas/functions/__tests__/oasExample.test.ts diff --git a/package.json b/package.json index a2bee2699..300531ede 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,8 @@ "test.karma": "karma start", "prepare": "husky install", "prerelease": "patch-package", - "release": "yarn prerelease && yarn workspaces foreach run release" + "release": "yarn prerelease && yarn workspaces foreach run release", + "jest": "jest" }, "workspaces": { "packages": [ diff --git a/packages/rulesets/src/oas/functions/__tests__/oasExample.test.ts b/packages/rulesets/src/oas/functions/__tests__/oasExample.test.ts new file mode 100644 index 000000000..899e5b517 --- /dev/null +++ b/packages/rulesets/src/oas/functions/__tests__/oasExample.test.ts @@ -0,0 +1,451 @@ +import { oas3, oas3_0 } from '@stoplight/spectral-formats'; +import { DeepPartial } from '@stoplight/types'; +import oasExample, { Options as ExampleOptions } from '../oasExample'; +import { RulesetFunctionContext } from '@stoplight/spectral-core/src'; + +const schemaOpts: ExampleOptions = { + schemaField: '$', + oasVersion: 3, + type: 'schema', +}; +const mediaOpts: ExampleOptions = { + schemaField: 'schema', + oasVersion: 3, + type: 'media', +}; +const docFormats = { + formats: new Set([oas3, oas3_0]), +}; + +/** + * Runs the oasExample() custom rule function to perform a single test. + * @param target the object (media type or schema) containing an example/default value + * @param ruleOptions the options to be passed to oasExample() + * @param context the spectral context object to pass to oasExample() + * @returns an array of errors, or [] if no errors occurred + */ +function runRule(testData: Record, ruleOptions: ExampleOptions) { + const context: DeepPartial = { + path: [], + documentInventory: {}, + document: docFormats, + }; + + return oasExample(testData, ruleOptions, context as RulesetFunctionContext); +} + +describe('oasExample', () => { + describe('should return no errors', () => { + describe('example/default value in schema', () => { + test('valid "example" object', () => { + const schema = { + type: 'object', + properties: { + foo: { + type: 'number', + }, + bar: { + type: 'string', + }, + }, + required: ['foo'], + example: { + foo: 38, + bar: 'foo', + }, + }; + + const results = runRule(schema, schemaOpts); + expect(results).toHaveLength(0); + }); + test('valid "default" string', () => { + const schema = { + type: 'string', + pattern: 'xyz-.*', + minLength: 4, + maxLength: 6, + default: 'xyz-99', + }; + + const results = runRule(schema, schemaOpts); + expect(results).toHaveLength(0); + }); + test('valid "example" integer', () => { + const schema = { + type: 'integer', + example: 74, + }; + + const results = runRule(schema, schemaOpts); + expect(results).toHaveLength(0); + }); + test('scenario: "resolves to more than one schema"', () => { + // This test data is from https://github.com/stoplightio/spectral/issues/2081 and + // demonstrates a scenario in which ajv returns the dreaded + // "reference <...> resolves to more than one schema" false error. + // Without the fix to the oasExample() function, this test will fail. + // The reason that it fails is due to the way in which ajv handles unknown + // properties found in the schema (e.g. "example" - it's not actually part of JSONSchema), + // and the way it gives special treatment to the "id" property. Ajv gets confused by + // the fact that there are multiple example objects that each contain a property named "id" + // with the value 'bf23bc970b78d27691e8' (repeating example values is probably not an uncommon + // use-case for openapi authors if you think about it). + // So, without the fix to oasExample(), the test below will fail with this result: + // [ + // { + // "message": "reference \"bf23bc970b78d27691e8\" resolves to more than one schema", + // "path": ["example"] + // } + // ] + // However, if you rename the "id" properties to something else, the rule returns []. + // Likewise, if you change the value of "id" in one of the examples (so they are no longer equal) + // the rule returns []. + // And of course, with the fix to oasExample() in place, the rule will also return []. + const schema = { + type: 'object', + required: ['items'], + allOf: [ + { + type: 'object', + properties: { + items: { + type: 'array', + items: { + type: 'object', + required: ['id', 'url'], + properties: { + id: { + type: 'string', + }, + url: { + type: 'string', + format: 'uri', + }, + }, + example: { + id: 'bf23bc970b78d27691e8', + url: 'https://api.example.com/banking/accounts/bf23bc970b78d27691e8', + }, + }, + }, + }, + }, + ], + example: { + items: [ + { + id: 'bf23bc970b78d27691e8', + url: 'https://api.example.com/banking/accounts/bf23bc970b78d27691e8', + }, + { + id: '8d27691e8bf23bc970b7', + url: 'https://api.example.com/banking/accounts/8d27691e8bf23bc970b7', + }, + ], + }, + }; + + const results = runRule(schema, schemaOpts); + expect(results).toHaveLength(0); + }); + }); + describe('example/examples value in mediatype', () => { + test('valid "example" object', () => { + const mediaType = { + schema: { + type: 'object', + properties: { + foo: { + type: 'number', + }, + bar: { + type: 'string', + }, + }, + required: ['foo'], + }, + example: { + foo: 38, + bar: 'foo', + }, + }; + + const results = runRule(mediaType, mediaOpts); + expect(results).toHaveLength(0); + }); + test('valid "examples" object', () => { + const mediaType = { + schema: { + type: 'object', + properties: { + foo: { + type: 'number', + }, + bar: { + type: 'string', + }, + }, + required: ['foo'], + }, + examples: { + first: { + value: { + foo: 38, + bar: 'foo', + }, + }, + second: { + value: { + foo: 26, + bar: 'baz', + }, + }, + }, + }; + + const results = runRule(mediaType, mediaOpts); + expect(results).toHaveLength(0); + }); + test('valid "example" string', () => { + const mediaType = { + schema: { + type: 'string', + pattern: 'xyz-.*', + minLength: 4, + maxLength: 8, + }, + example: 'xyz-9999', + }; + + const results = runRule(mediaType, mediaOpts); + expect(results).toHaveLength(0); + }); + test('valid "examples" string', () => { + const mediaType = { + schema: { + type: 'string', + pattern: 'id-.*', + minLength: 4, + maxLength: 8, + }, + examples: { + first: { + value: 'id-1', + }, + second: { + value: 'id-99999', + }, + third: { + value: 'id-38', + }, + }, + }; + + const results = runRule(mediaType, mediaOpts); + expect(results).toHaveLength(0); + }); + test('scenario: "resolves to more than one schema"', () => { + // This test data was adapted from https://github.com/stoplightio/spectral/issues/2140. + const mediaType = { + schema: { + properties: { + bars: { + description: 'Array of bars!', + type: 'array', + items: { + oneOf: [ + { + type: 'object', + description: 'a real bar!', + required: ['id'], + properties: { + id: { + description: 'The ID for this real bar', + type: 'string', + }, + }, + example: { + id: '6d353a0f-aeb1-4ae1-832e-1110d10981bb', + }, + }, + { + description: 'not a real bar!', + not: { + type: 'object', + description: 'a real bar!', + required: ['id'], + properties: { + id: { + description: 'The ID for this real bar', + type: 'string', + }, + }, + example: { + id: '6d353a0f-aeb1-4ae1-832e-1110d10981bb', + }, + }, + }, + ], + }, + }, + }, + }, + example: { + bars: [{ id: '6d353a0f-aeb1-4ae1-832e-1110d10981bb' }], + }, + }; + + const results = runRule(mediaType, mediaOpts); + expect(results).toHaveLength(0); + }); + }); + }); + describe('should return errors', () => { + describe('example/default value in schema', () => { + test('invalid "example" object', () => { + const schema = { + type: 'object', + properties: { + foo: { + type: 'number', + }, + bar: { + type: 'string', + }, + }, + required: ['foo', 'bar'], + example: { + foo: 38, + bar: 26, + }, + }; + + const results = runRule(schema, schemaOpts); + expect(results).toHaveLength(1); + + expect(results[0].path.join('.')).toBe('example.bar'); + expect(results[0].message).toBe(`"bar" property type must be string`); + }); + test('invalid "default" string', () => { + const schema = { + type: 'string', + pattern: 'xyz-.*', + minLength: 4, + maxLength: 8, + default: 'xyz-99999', + }; + + const results = runRule(schema, schemaOpts); + expect(results).toHaveLength(1); + expect(results[0].message).toBe(`"default" property must not have more than 8 characters`); + expect(results[0].path.join('.')).toBe('default'); + }); + }); + describe('example/examples value in mediatype', () => { + test('invalid "example" object', () => { + const mediaType = { + schema: { + type: 'object', + properties: { + foo: { + type: 'number', + }, + bar: { + type: 'string', + }, + }, + required: ['foo', 'bar'], + }, + example: { + foo: 38, + }, + }; + + const results = runRule(mediaType, mediaOpts); + expect(results).toHaveLength(1); + expect(results[0].message).toBe(`"example" property must have required property "bar"`); + expect(results[0].path.join('.')).toBe('example'); + }); + test('invalid "examples" object', () => { + const mediaType = { + schema: { + type: 'object', + properties: { + foo: { + type: 'number', + }, + bar: { + type: 'string', + }, + }, + required: ['foo', 'bar'], + }, + examples: { + first: { + value: { + foo: 38, + }, + }, + second: { + value: { + foo: 'bar', + bar: 'foo', + }, + }, + }, + }; + + const results = runRule(mediaType, mediaOpts); + expect(results).toHaveLength(2); + + expect(results[0].message).toBe(`"value" property must have required property "bar"`); + expect(results[0].path.join('.')).toBe('examples.first.value'); + + expect(results[1].message).toBe(`"foo" property type must be number`); + expect(results[1].path.join('.')).toBe('examples.second.value.foo'); + }); + test('invalid "example" string', () => { + const mediaType = { + schema: { + type: 'string', + pattern: 'xyz-.*', + minLength: 4, + maxLength: 8, + }, + example: 'xyz-99999', + }; + + const results = runRule(mediaType, mediaOpts); + expect(results).toHaveLength(1); + expect(results[0].message).toBe(`"example" property must not have more than 8 characters`); + expect(results[0].path.join('.')).toBe('example'); + }); + test('invalid "examples" string', () => { + const mediaType = { + schema: { + type: 'string', + pattern: 'xyz-.*', + minLength: 4, + maxLength: 8, + default: 'xyz-99', + }, + examples: { + first: { + value: 'xyz-99999', + }, + second: { + value: 38, + }, + }, + }; + + const results = runRule(mediaType, mediaOpts); + expect(results).toHaveLength(2); + expect(results[0].message).toBe(`"value" property must not have more than 8 characters`); + expect(results[0].path.join('.')).toBe('examples.first.value'); + expect(results[1].message).toBe(`"value" property type must be string`); + expect(results[1].path.join('.')).toBe('examples.second.value'); + }); + }); + }); +}); diff --git a/packages/rulesets/src/oas/functions/oasExample.ts b/packages/rulesets/src/oas/functions/oasExample.ts index 1ad1dcd9b..6af1154f5 100644 --- a/packages/rulesets/src/oas/functions/oasExample.ts +++ b/packages/rulesets/src/oas/functions/oasExample.ts @@ -3,6 +3,7 @@ import type { Dictionary, JsonPath, Optional } from '@stoplight/types'; import oasSchema, { Options as SchemaOptions } from './oasSchema'; import { createRulesetFunction, IFunctionResult } from '@stoplight/spectral-core'; import { oas2 } from '@stoplight/spectral-formats'; +import traverse from 'json-schema-traverse'; export type Options = { oasVersion: 2 | 3; @@ -110,6 +111,21 @@ function* getSchemaValidationItems( } } +/** + * Modifies 'schema' (and all its sub-schemas) to remove all "example" fields. + * In this context, "sub-schemas" refers to all schemas reachable from 'schema' + * (e.g. properties, additionalProperties, allOf/anyOf/oneOf, not, items, etc.) + * @param schema the schema to be "de-examplified" + * @returns 'schema' with example fields removed + */ +function deExamplify(schema: Record): void { + traverse(schema, (fragment => { + if ('example' in fragment) { + delete fragment.example; + } + })); +} + export default createRulesetFunction, Options>( { input: { @@ -149,6 +165,12 @@ export default createRulesetFunction, Options>( delete schemaOpts.schema.required; } + // Make a deep copy of the schema and then remove all the "example" fields from it. + // This is to avoid problems down in "ajv" which does the actual schema validation. + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + schemaOpts.schema = JSON.parse(JSON.stringify(schemaOpts.schema)); + deExamplify(schemaOpts.schema); + for (const validationItem of validationItems) { const result = oasSchema(validationItem.value, schemaOpts, { ...context, From 07022871b93d29d9a5f58ab0bf70e982b7558399 Mon Sep 17 00:00:00 2001 From: Phil Adams Date: Tue, 25 Apr 2023 10:33:28 +0200 Subject: [PATCH 08/16] chore(functions): extra comment on schema fn missing ref error logic (#2408) --- packages/functions/src/schema/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/functions/src/schema/index.ts b/packages/functions/src/schema/index.ts index a7262517d..955c799b9 100644 --- a/packages/functions/src/schema/index.ts +++ b/packages/functions/src/schema/index.ts @@ -69,7 +69,10 @@ export default createRulesetFunction( // let's ignore any $ref errors if schema fn is provided with already resolved content, // if our resolver fails to resolve them, // ajv is unlikely to do it either, since it won't have access to the whole document, but a small portion of it - if (!rule.resolved || !(ex instanceof MissingRefError)) { + // We specifically check that "rule" is truthy below because "rule" might be undefined/null if this + // code is called from testcases. + const ignoreError = rule?.resolved && ex instanceof MissingRefError; + if (!ignoreError) { results.push({ message: ex.message, path, From ff7d654f6e8976aaf78ec9fded3b9d828c593ce6 Mon Sep 17 00:00:00 2001 From: stoplight-bot Date: Tue, 25 Apr 2023 08:42:39 +0000 Subject: [PATCH 09/16] chore(release): 1.15.1 [skip ci] # [@stoplight/spectral-rulesets-v1.15.1](https://github.com/stoplightio/spectral/compare/@stoplight/spectral-rulesets-v1.15.0...@stoplight/spectral-rulesets-v1.15.1) (2023-04-25) ### Bug Fixes * **rulesets:** avoid false errors from ajv ([#2408](https://github.com/stoplightio/spectral/issues/2408)) ([92dab78](https://github.com/stoplightio/spectral/commit/92dab78d0c07e6919c0485cadbe5aa2391a53e8b)) --- packages/rulesets/CHANGELOG.md | 7 +++++++ packages/rulesets/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/rulesets/CHANGELOG.md b/packages/rulesets/CHANGELOG.md index d1757129e..882de575f 100644 --- a/packages/rulesets/CHANGELOG.md +++ b/packages/rulesets/CHANGELOG.md @@ -1,3 +1,10 @@ +# [@stoplight/spectral-rulesets-v1.15.1](https://github.com/stoplightio/spectral/compare/@stoplight/spectral-rulesets-v1.15.0...@stoplight/spectral-rulesets-v1.15.1) (2023-04-25) + + +### Bug Fixes + +* **rulesets:** avoid false errors from ajv ([#2408](https://github.com/stoplightio/spectral/issues/2408)) ([92dab78](https://github.com/stoplightio/spectral/commit/92dab78d0c07e6919c0485cadbe5aa2391a53e8b)) + # [@stoplight/spectral-rulesets-v1.15.0](https://github.com/stoplightio/spectral/compare/@stoplight/spectral-rulesets-v1.14.1...@stoplight/spectral-rulesets-v1.15.0) (2023-02-03) diff --git a/packages/rulesets/package.json b/packages/rulesets/package.json index ab7a5a787..c0e4804ff 100644 --- a/packages/rulesets/package.json +++ b/packages/rulesets/package.json @@ -1,6 +1,6 @@ { "name": "@stoplight/spectral-rulesets", - "version": "1.15.0", + "version": "1.15.1", "homepage": "https://github.com/stoplightio/spectral", "bugs": "https://github.com/stoplightio/spectral/issues", "author": "Stoplight ", From de16b4cbd56cd9836609ab79487a6e3e06df964d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ro=C5=BCek?= Date: Tue, 25 Apr 2023 10:47:12 +0200 Subject: [PATCH 10/16] feat(core): relax formats validation (#2151) --- .../core/src/ruleset/meta/json-extensions.json | 17 +---------------- .../validation/__tests__/validation.test.ts | 6 +++--- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/packages/core/src/ruleset/meta/json-extensions.json b/packages/core/src/ruleset/meta/json-extensions.json index b57698d83..f3c6186ec 100644 --- a/packages/core/src/ruleset/meta/json-extensions.json +++ b/packages/core/src/ruleset/meta/json-extensions.json @@ -36,22 +36,7 @@ }, "Format": { "$anchor": "format", - "enum": [ - "oas2", - "oas3", - "oas3.0", - "oas3.1", - "asyncapi2", - "json-schema", - "json-schema-loose", - "json-schema-draft4", - "json-schema-draft6", - "json-schema-draft7", - "json-schema-draft-2019-09", - "json-schema-2019-09", - "json-schema-draft-2020-12", - "json-schema-2020-12" - ], + "type": "string", "errorMessage": "must be a valid format" }, "Functions": { diff --git a/packages/core/src/ruleset/validation/__tests__/validation.test.ts b/packages/core/src/ruleset/validation/__tests__/validation.test.ts index 001427667..3cfab69b9 100644 --- a/packages/core/src/ruleset/validation/__tests__/validation.test.ts +++ b/packages/core/src/ruleset/validation/__tests__/validation.test.ts @@ -941,14 +941,14 @@ describe('JSON Ruleset Validation', () => { it.each<[unknown, RulesetValidationError[]]>([ [ - [2, 'a'], + [2, null], [ new RulesetValidationError('invalid-format', 'must be a valid format', ['formats', '0']), new RulesetValidationError('invalid-format', 'must be a valid format', ['formats', '1']), ], ], [2, [new RulesetValidationError('invalid-ruleset-definition', 'must be an array of formats', ['formats'])]], - [[''], [new RulesetValidationError('invalid-format', 'must be a valid format', ['formats', '0'])]], + [[null], [new RulesetValidationError('invalid-format', 'must be a valid format', ['formats', '0'])]], ])('recognizes invalid ruleset %p formats syntax', (formats, errors) => { expect( assertValidRuleset.bind( @@ -985,7 +985,7 @@ describe('JSON Ruleset Validation', () => { it.each<[unknown, RulesetValidationError[]]>([ [ - [2, 'a'], + [2, null], [ new RulesetValidationError('invalid-format', 'must be a valid format', ['rules', 'rule', 'formats', '0']), new RulesetValidationError('invalid-format', 'must be a valid format', ['rules', 'rule', 'formats', '1']), From 66b3ca704136d5d8a34211e72e2d8a2c522261e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ro=C5=BCek?= Date: Sat, 19 Nov 2022 12:58:02 +0100 Subject: [PATCH 11/16] fix(core): more accurate ruleset error paths --- packages/core/src/ruleset/function.ts | 23 ++++++++++++++++--- packages/core/src/ruleset/ruleset.ts | 4 ++-- .../validation/__tests__/validation.test.ts | 7 +++++- .../core/src/ruleset/validation/errors.ts | 2 +- .../ruleset/validation/validators/function.ts | 2 +- 5 files changed, 30 insertions(+), 8 deletions(-) diff --git a/packages/core/src/ruleset/function.ts b/packages/core/src/ruleset/function.ts index b187fc13b..8360ba90c 100644 --- a/packages/core/src/ruleset/function.ts +++ b/packages/core/src/ruleset/function.ts @@ -22,10 +22,27 @@ export class RulesetFunctionValidationError extends RulesetValidationError { super( 'invalid-function-options', RulesetFunctionValidationError.printMessage(fn, error), - error.instancePath.slice(1).split('/'), + RulesetFunctionValidationError.getPath(error), ); } + private static getPath(error: ErrorObject): string[] { + const path: string[] = [ + 'functionOptions', + ...(error.instancePath === '' ? [] : error.instancePath.slice(1).split('/')), + ]; + + switch (error.keyword) { + case 'additionalProperties': { + const additionalProperty = (error as AdditionalPropertiesError).params.additionalProperty; + path.push(additionalProperty); + break; + } + } + + return path; + } + private static printMessage(fn: string, error: ErrorObject): string { switch (error.keyword) { case 'type': { @@ -157,7 +174,7 @@ export function createRulesetFunction( throw new RulesetValidationError( 'invalid-function-options', `"${fn.name || ''}" function does not accept any options`, - [], + ['functionOptions'], ); } else if ( 'errors' in validateOptions && @@ -171,7 +188,7 @@ export function createRulesetFunction( throw new RulesetValidationError( 'invalid-function-options', `"functionOptions" of "${fn.name || ''}" function must be valid`, - [], + ['functionOptions'], ); } }; diff --git a/packages/core/src/ruleset/ruleset.ts b/packages/core/src/ruleset/ruleset.ts index 560957485..8cc01a72e 100644 --- a/packages/core/src/ruleset/ruleset.ts +++ b/packages/core/src/ruleset/ruleset.ts @@ -63,10 +63,10 @@ export class Ruleset { if (isPlainObject(maybeDefinition) && 'extends' in maybeDefinition) { const { extends: _, ...def } = maybeDefinition; // we don't want to validate extends - this is going to happen later on (line 29) - assertValidRuleset({ extends: [], ...def }); + assertValidRuleset({ extends: [], ...def }, 'js'); definition = maybeDefinition as RulesetDefinition; } else { - assertValidRuleset(maybeDefinition); + assertValidRuleset(maybeDefinition, 'js'); definition = maybeDefinition; } diff --git a/packages/core/src/ruleset/validation/__tests__/validation.test.ts b/packages/core/src/ruleset/validation/__tests__/validation.test.ts index 3cfab69b9..59a88f911 100644 --- a/packages/core/src/ruleset/validation/__tests__/validation.test.ts +++ b/packages/core/src/ruleset/validation/__tests__/validation.test.ts @@ -773,7 +773,12 @@ describe('JS Ruleset Validation', () => { }), ).toThrowAggregateError( new AggregateError([ - new RulesetValidationError('undefined-function', 'Function is not defined', ['rules', 'rule', 'then']), + new RulesetValidationError('undefined-function', 'Function is not defined', [ + 'rules', + 'rule', + 'then', + 'function', + ]), ]), ); }); diff --git a/packages/core/src/ruleset/validation/errors.ts b/packages/core/src/ruleset/validation/errors.ts index af8660783..3c92aa7d0 100644 --- a/packages/core/src/ruleset/validation/errors.ts +++ b/packages/core/src/ruleset/validation/errors.ts @@ -18,7 +18,7 @@ export type RulesetValidationErrorCode = | 'undefined-alias'; interface IRulesetValidationSingleError extends Pick { - code: RulesetValidationErrorCode; + readonly code: RulesetValidationErrorCode; } export class RulesetValidationError extends Error implements IRulesetValidationSingleError { diff --git a/packages/core/src/ruleset/validation/validators/function.ts b/packages/core/src/ruleset/validation/validators/function.ts index 834425227..bf6249eba 100644 --- a/packages/core/src/ruleset/validation/validators/function.ts +++ b/packages/core/src/ruleset/validation/validators/function.ts @@ -24,7 +24,7 @@ export function validateFunction( validator(opts); } catch (ex) { if (ex instanceof ReferenceError) { - return new RulesetValidationError('undefined-function', ex.message, toParsedPath(path)); + return new RulesetValidationError('undefined-function', ex.message, [...toParsedPath(path), 'function']); } return wrapError(ex, path); From 88533dc2811df3bf011c366f8dfd0f41052579a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ro=C5=BCek?= Date: Sat, 19 Nov 2022 12:59:08 +0100 Subject: [PATCH 12/16] test(functions): update error paths --- .../src/__tests__/__helpers__/tester.ts | 41 +++++-------------- .../src/__tests__/alphabetical.test.ts | 30 ++++++++++---- .../functions/src/__tests__/casing.test.ts | 22 ++++++---- .../functions/src/__tests__/defined.test.ts | 7 +++- .../src/__tests__/enumeration.test.ts | 8 ++-- .../functions/src/__tests__/falsy.test.ts | 7 +++- .../functions/src/__tests__/length.test.ts | 24 +++++++---- .../functions/src/__tests__/pattern.test.ts | 24 ++++++----- .../functions/src/__tests__/schema.test.ts | 22 ++++++---- .../functions/src/__tests__/truthy.test.ts | 7 +++- .../unreferencedReusableObject.test.ts | 12 +++--- packages/functions/src/__tests__/xor.test.ts | 22 ++++++---- 12 files changed, 136 insertions(+), 90 deletions(-) diff --git a/packages/functions/src/__tests__/__helpers__/tester.ts b/packages/functions/src/__tests__/__helpers__/tester.ts index fe1355623..bc77112c5 100644 --- a/packages/functions/src/__tests__/__helpers__/tester.ts +++ b/packages/functions/src/__tests__/__helpers__/tester.ts @@ -6,15 +6,8 @@ import { IRuleResult, RulesetFunction, RulesetFunctionWithValidator, - RulesetValidationError, } from '@stoplight/spectral-core'; -import { isError } from 'lodash'; - -function isAggregateError(maybeAggregateError: unknown): maybeAggregateError is Error & { errors: unknown[] } { - return isError(maybeAggregateError) && maybeAggregateError.constructor.name === 'AggregateError'; -} - export default async function ( // eslint-disable-next-line @typescript-eslint/no-explicit-any fn: RulesetFunction | RulesetFunctionWithValidator, @@ -23,31 +16,19 @@ export default async function ( rule?: Partial> & { then?: Partial }, ): Promise[]> { const s = new Spectral(); - try { - s.setRuleset({ - rules: { - 'my-rule': { - given: '$', - ...rule, - then: { - ...(rule?.then as Ruleset['rules']['then']), - function: fn, - functionOptions: opts, - }, + s.setRuleset({ + rules: { + 'my-rule': { + given: '$', + ...rule, + then: { + ...(rule?.then as Ruleset['rules']['then']), + function: fn, + functionOptions: opts, }, }, - }); - } catch (ex) { - if (isAggregateError(ex)) { - for (const e of ex.errors) { - if (e instanceof RulesetValidationError) { - e.path.length = 0; - } - } - } - - throw ex; - } + }, + }); const results = await s.run(input instanceof Document ? input : JSON.stringify(input)); return results diff --git a/packages/functions/src/__tests__/alphabetical.test.ts b/packages/functions/src/__tests__/alphabetical.test.ts index fe6c20073..d8ba91d86 100644 --- a/packages/functions/src/__tests__/alphabetical.test.ts +++ b/packages/functions/src/__tests__/alphabetical.test.ts @@ -132,17 +132,33 @@ describe('Core Functions / Alphabetical', () => { expect(await runAlphabetical([], opts)).toEqual([]); }); - it.each<[unknown, string]>([ - [{ foo: true }, '"alphabetical" function does not support "foo" option'], + it.each<[unknown, RulesetValidationError]>([ + [ + { foo: true }, + new RulesetValidationError( + 'invalid-function-options', + '"alphabetical" function does not support "foo" option', + ['rules', 'my-rule', 'then', 'functionOptions', 'foo'], + ), + ], [ 2, - '"alphabetical" function has invalid options specified. Example valid options: null (no options), { "keyedBy": "my-key" }', + new RulesetValidationError( + 'invalid-function-options', + '"alphabetical" function has invalid options specified. Example valid options: null (no options), { "keyedBy": "my-key" }', + ['rules', 'my-rule', 'then', 'functionOptions'], + ), + ], + [ + { keyedBy: 2 }, + new RulesetValidationError( + 'invalid-function-options', + '"alphabetical" function and its "keyedBy" option accepts only the following types: string', + ['rules', 'my-rule', 'then', 'functionOptions', 'keyedBy'], + ), ], - [{ keyedBy: 2 }, '"alphabetical" function and its "keyedBy" option accepts only the following types: string'], ])('given invalid %p options, should throw', async (opts, error) => { - await expect(runAlphabetical([], opts)).rejects.toThrowAggregateError( - new AggregateError([new RulesetValidationError('invalid-function-options', error, [])]), - ); + await expect(runAlphabetical([], opts)).rejects.toThrowAggregateError(new AggregateError([error])); }); }); }); diff --git a/packages/functions/src/__tests__/casing.test.ts b/packages/functions/src/__tests__/casing.test.ts index b78605265..b0c2e03a5 100644 --- a/packages/functions/src/__tests__/casing.test.ts +++ b/packages/functions/src/__tests__/casing.test.ts @@ -381,13 +381,21 @@ describe('Core Functions / Casing', () => { new RulesetValidationError( 'invalid-function-options', '"casing" function and its "type" option accept the following values: flat, camel, pascal, kebab, cobol, snake, macro', - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'type'], ), ], ], [ { type: 'macro', foo: true }, - [new RulesetValidationError('invalid-function-options', '"casing" function does not support "foo" option', [])], + [ + new RulesetValidationError('invalid-function-options', '"casing" function does not support "foo" option', [ + 'rules', + 'my-rule', + 'then', + 'functionOptions', + 'foo', + ]), + ], ], [ { @@ -399,7 +407,7 @@ describe('Core Functions / Casing', () => { new RulesetValidationError( 'invalid-function-options', '"casing" function is missing "separator.char" option', - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'separator'], ), ], ], @@ -413,7 +421,7 @@ describe('Core Functions / Casing', () => { new RulesetValidationError( 'invalid-function-options', '"casing" function is missing "separator.char" option', - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'separator'], ), ], ], @@ -423,7 +431,7 @@ describe('Core Functions / Casing', () => { new RulesetValidationError( 'invalid-function-options', '"casing" function does not support "separator.foo" option', - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'separator', 'foo'], ), ], ], @@ -438,7 +446,7 @@ describe('Core Functions / Casing', () => { new RulesetValidationError( 'invalid-function-options', '"casing" function and its "separator.char" option accepts only char, i.e. "I" or "/"', - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'separator', 'char'], ), ], ], @@ -453,7 +461,7 @@ describe('Core Functions / Casing', () => { new RulesetValidationError( 'invalid-function-options', '"casing" function and its "separator.char" option accepts only char, i.e. "I" or "/"', - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'separator', 'char'], ), ], ], diff --git a/packages/functions/src/__tests__/defined.test.ts b/packages/functions/src/__tests__/defined.test.ts index 05d3e54b9..56ebc51a5 100644 --- a/packages/functions/src/__tests__/defined.test.ts +++ b/packages/functions/src/__tests__/defined.test.ts @@ -25,7 +25,12 @@ describe('Core Functions / Defined', () => { it.each([{}, 2])('given invalid %p options, should throw', async opts => { await expect(runDefined([], opts)).rejects.toThrowAggregateError( new AggregateError([ - new RulesetValidationError('invalid-function-options', '"defined" function does not accept any options', []), + new RulesetValidationError('invalid-function-options', '"defined" function does not accept any options', [ + 'rules', + 'my-rule', + 'then', + 'functionOptions', + ]), ]), ); }); diff --git a/packages/functions/src/__tests__/enumeration.test.ts b/packages/functions/src/__tests__/enumeration.test.ts index ab92c140c..019cd6610 100644 --- a/packages/functions/src/__tests__/enumeration.test.ts +++ b/packages/functions/src/__tests__/enumeration.test.ts @@ -44,7 +44,7 @@ describe('Core Functions / Enumeration', () => { new RulesetValidationError( 'invalid-function-options', '"enumeration" function does not support "foo" option', - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'foo'], ), ], ], @@ -56,7 +56,7 @@ describe('Core Functions / Enumeration', () => { new RulesetValidationError( 'invalid-function-options', '"enumeration" and its "values" option support only arrays of primitive values, i.e. ["Berlin", "London", "Paris"]', - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'values'], ), ], ], @@ -66,7 +66,7 @@ describe('Core Functions / Enumeration', () => { new RulesetValidationError( 'invalid-function-options', '"enumeration" function has invalid options specified. Example valid options: { "values": ["Berlin", "London", "Paris"] }, { "values": [2, 3, 5, 8, 13, 21] }', - [], + ['rules', 'my-rule', 'then', 'functionOptions'], ), ], ], @@ -76,7 +76,7 @@ describe('Core Functions / Enumeration', () => { new RulesetValidationError( 'invalid-function-options', '"enumeration" function has invalid options specified. Example valid options: { "values": ["Berlin", "London", "Paris"] }, { "values": [2, 3, 5, 8, 13, 21] }', - [], + ['rules', 'my-rule', 'then', 'functionOptions'], ), ], ], diff --git a/packages/functions/src/__tests__/falsy.test.ts b/packages/functions/src/__tests__/falsy.test.ts index 7799b442b..a63c9878a 100644 --- a/packages/functions/src/__tests__/falsy.test.ts +++ b/packages/functions/src/__tests__/falsy.test.ts @@ -25,7 +25,12 @@ describe('Core Functions / Falsy', () => { it.each([{}, 2])('given invalid %p options, should throw', async opts => { await expect(runFalsy([], opts)).rejects.toThrowAggregateError( new AggregateError([ - new RulesetValidationError('invalid-function-options', '"falsy" function does not accept any options', []), + new RulesetValidationError('invalid-function-options', '"falsy" function does not accept any options', [ + 'rules', + 'my-rule', + 'then', + 'functionOptions', + ]), ]), ); }); diff --git a/packages/functions/src/__tests__/length.test.ts b/packages/functions/src/__tests__/length.test.ts index 9e3fff231..4ab9cb38f 100644 --- a/packages/functions/src/__tests__/length.test.ts +++ b/packages/functions/src/__tests__/length.test.ts @@ -61,7 +61,7 @@ describe('Core Functions / Length', () => { new RulesetValidationError( 'invalid-function-options', '"length" function has invalid options specified. Example valid options: { "min": 2 }, { "max": 5 }, { "min": 0, "max": 10 }', - [], + ['rules', 'my-rule', 'then', 'functionOptions'], ), ], ], @@ -71,7 +71,7 @@ describe('Core Functions / Length', () => { new RulesetValidationError( 'invalid-function-options', '"length" function has invalid options specified. Example valid options: { "min": 2 }, { "max": 5 }, { "min": 0, "max": 10 }', - [], + ['rules', 'my-rule', 'then', 'functionOptions'], ), ], ], @@ -80,15 +80,23 @@ describe('Core Functions / Length', () => { min: 2, foo: true, }, - [new RulesetValidationError('invalid-function-options', '"length" function does not support "foo" option', [])], + [ + new RulesetValidationError('invalid-function-options', '"length" function does not support "foo" option', [ + 'rules', + 'my-rule', + 'then', + 'functionOptions', + 'foo', + ]), + ], ], [ { min: '2' }, [ new RulesetValidationError( 'invalid-function-options', - '"length" function and its "min" option accepts only the following types: number', - [], + `"length" function and its "min" option accepts only the following types: number`, + ['rules', 'my-rule', 'then', 'functionOptions', 'min'], ), ], ], @@ -99,7 +107,7 @@ describe('Core Functions / Length', () => { new RulesetValidationError( 'invalid-function-options', `"length" function and its "max" option accepts only the following types: number`, - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'max'], ), ], ], @@ -109,12 +117,12 @@ describe('Core Functions / Length', () => { new RulesetValidationError( 'invalid-function-options', `"length" function and its "min" option accepts only the following types: number`, - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'min'], ), new RulesetValidationError( 'invalid-function-options', `"length" function and its "max" option accepts only the following types: number`, - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'max'], ), ], ], diff --git a/packages/functions/src/__tests__/pattern.test.ts b/packages/functions/src/__tests__/pattern.test.ts index 74b98e084..9cc030cc7 100644 --- a/packages/functions/src/__tests__/pattern.test.ts +++ b/packages/functions/src/__tests__/pattern.test.ts @@ -59,7 +59,7 @@ describe('Core Functions / Pattern', () => { new RulesetValidationError( 'invalid-function-options', '"pattern" function has invalid options specified. Example valid options: { "match": "^Stoplight" }, { "notMatch": "Swagger" }, { "match": "Stoplight", "notMatch": "Swagger" }', - [], + ['rules', 'my-rule', 'then', 'functionOptions'], ), ], ], @@ -69,18 +69,20 @@ describe('Core Functions / Pattern', () => { new RulesetValidationError( 'invalid-function-options', `"pattern" function has invalid options specified. Example valid options: { "match": "^Stoplight" }, { "notMatch": "Swagger" }, { "match": "Stoplight", "notMatch": "Swagger" }`, - [], + ['rules', 'my-rule', 'then', 'functionOptions'], ), ], ], [ { foo: true }, [ - new RulesetValidationError( - 'invalid-function-options', - '"pattern" function does not support "foo" option', - [], - ), + new RulesetValidationError('invalid-function-options', '"pattern" function does not support "foo" option', [ + 'rules', + 'my-rule', + 'then', + 'functionOptions', + 'foo', + ]), ], ], [ @@ -89,7 +91,7 @@ describe('Core Functions / Pattern', () => { new RulesetValidationError( 'invalid-function-options', '"pattern" function and its "match" option must be string or RegExp instance', - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'match'], ), ], ], @@ -99,7 +101,7 @@ describe('Core Functions / Pattern', () => { new RulesetValidationError( 'invalid-function-options', '"pattern" function and its "notMatch" option must be string or RegExp instance', - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'notMatch'], ), ], ], @@ -109,12 +111,12 @@ describe('Core Functions / Pattern', () => { new RulesetValidationError( 'invalid-function-options', `"pattern" function and its "match" option must be string or RegExp instance`, - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'match'], ), new RulesetValidationError( 'invalid-function-options', `"pattern" function and its "notMatch" option must be string or RegExp instance`, - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'notMatch'], ), ], ], diff --git a/packages/functions/src/__tests__/schema.test.ts b/packages/functions/src/__tests__/schema.test.ts index ee7b53950..ec45b5fd0 100644 --- a/packages/functions/src/__tests__/schema.test.ts +++ b/packages/functions/src/__tests__/schema.test.ts @@ -485,13 +485,21 @@ describe('Core Functions / Schema', () => { new RulesetValidationError( 'invalid-function-options', '"schema" function has invalid options specified. Example valid options: { "schema": { /* any JSON Schema can be defined here */ } , { "schema": { "type": "object" }, "dialect": "auto" }', - [], + ['rules', 'my-rule', 'then', 'functionOptions'], ), ], ], [ { schema: { type: 'object' }, foo: true }, - [new RulesetValidationError('invalid-function-options', '"schema" function does not support "foo" option', [])], + [ + new RulesetValidationError('invalid-function-options', '"schema" function does not support "foo" option', [ + 'rules', + 'my-rule', + 'then', + 'functionOptions', + 'foo', + ]), + ], ], [ { schema: { type: 'object' }, oasVersion: 1 }, @@ -499,7 +507,7 @@ describe('Core Functions / Schema', () => { new RulesetValidationError( 'invalid-function-options', '"schema" function does not support "oasVersion" option', - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'oasVersion'], ), ], ], @@ -509,7 +517,7 @@ describe('Core Functions / Schema', () => { new RulesetValidationError( 'invalid-function-options', '"schema" function and its "dialect" option accepts only the following values: "auto", "draft4", "draft6", "draft7", "draft2019-09", "draft2020-12"', - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'dialect'], ), ], ], @@ -519,7 +527,7 @@ describe('Core Functions / Schema', () => { new RulesetValidationError( 'invalid-function-options', '"schema" function and its "allErrors" option accepts only the following types: boolean', - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'allErrors'], ), ], ], @@ -529,12 +537,12 @@ describe('Core Functions / Schema', () => { new RulesetValidationError( 'invalid-function-options', `"schema" function and its "schema" option accepts only the following types: object`, - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'schema'], ), new RulesetValidationError( 'invalid-function-options', `"schema" function and its "allErrors" option accepts only the following types: boolean`, - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'allErrors'], ), ], ], diff --git a/packages/functions/src/__tests__/truthy.test.ts b/packages/functions/src/__tests__/truthy.test.ts index b135382a2..32da4fb4a 100644 --- a/packages/functions/src/__tests__/truthy.test.ts +++ b/packages/functions/src/__tests__/truthy.test.ts @@ -25,7 +25,12 @@ describe('Core Functions / Truthy', () => { it.each([{}, 2])('given invalid %p options, should throw', async opts => { await expect(runTruthy([], opts)).rejects.toThrowAggregateError( new AggregateError([ - new RulesetValidationError('invalid-function-options', '"truthy" function does not accept any options', []), + new RulesetValidationError('invalid-function-options', '"truthy" function does not accept any options', [ + 'rules', + 'my-rule', + 'then', + 'functionOptions', + ]), ]), ); }); diff --git a/packages/functions/src/__tests__/unreferencedReusableObject.test.ts b/packages/functions/src/__tests__/unreferencedReusableObject.test.ts index 7fdcbe80b..3f1deb95c 100644 --- a/packages/functions/src/__tests__/unreferencedReusableObject.test.ts +++ b/packages/functions/src/__tests__/unreferencedReusableObject.test.ts @@ -34,7 +34,7 @@ describe('Core Functions / UnreferencedReusableObject', () => { new RulesetValidationError( 'invalid-function-options', '"unreferencedReusableObject" function has invalid options specified. Example valid options: { "reusableObjectsLocation": "#/components/schemas" }, { "reusableObjectsLocation": "#/$defs" }', - [], + ['rules', 'my-rule', 'then', 'functionOptions'], ), ], ], @@ -44,7 +44,7 @@ describe('Core Functions / UnreferencedReusableObject', () => { new RulesetValidationError( 'invalid-function-options', '"unreferencedReusableObject" function has invalid options specified. Example valid options: { "reusableObjectsLocation": "#/components/schemas" }, { "reusableObjectsLocation": "#/$defs" }', - [], + ['rules', 'my-rule', 'then', 'functionOptions'], ), ], ], @@ -54,7 +54,7 @@ describe('Core Functions / UnreferencedReusableObject', () => { new RulesetValidationError( 'invalid-function-options', '"unreferencedReusableObject" function is missing "reusableObjectsLocation" option. Example valid options: { "reusableObjectsLocation": "#/components/schemas" }, { "reusableObjectsLocation": "#/$defs" }', - [], + ['rules', 'my-rule', 'then', 'functionOptions'], ), ], ], @@ -67,7 +67,7 @@ describe('Core Functions / UnreferencedReusableObject', () => { new RulesetValidationError( 'invalid-function-options', '"unreferencedReusableObject" function does not support "foo" option', - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'foo'], ), ], ], @@ -79,7 +79,7 @@ describe('Core Functions / UnreferencedReusableObject', () => { new RulesetValidationError( 'invalid-function-options', '"unreferencedReusableObject" and its "reusableObjectsLocation" option support only valid JSON Pointer fragments, i.e. "#", "#/foo", "#/paths/~1user"', - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'reusableObjectsLocation'], ), ], ], @@ -91,7 +91,7 @@ describe('Core Functions / UnreferencedReusableObject', () => { new RulesetValidationError( 'invalid-function-options', '"unreferencedReusableObject" and its "reusableObjectsLocation" option support only valid JSON Pointer fragments, i.e. "#", "#/foo", "#/paths/~1user"', - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'reusableObjectsLocation'], ), ], ], diff --git a/packages/functions/src/__tests__/xor.test.ts b/packages/functions/src/__tests__/xor.test.ts index 2bf7deed7..6ea29e233 100644 --- a/packages/functions/src/__tests__/xor.test.ts +++ b/packages/functions/src/__tests__/xor.test.ts @@ -73,7 +73,7 @@ describe('Core Functions / Xor', () => { new RulesetValidationError( 'invalid-function-options', '"xor" function has invalid options specified. Example valid options: { "properties": ["id", "name"] }, { "properties": ["country", "street"] }', - [], + ['rules', 'my-rule', 'then', 'functionOptions'], ), ], ], @@ -83,13 +83,21 @@ describe('Core Functions / Xor', () => { new RulesetValidationError( 'invalid-function-options', '"xor" function has invalid options specified. Example valid options: { "properties": ["id", "name"] }, { "properties": ["country", "street"] }', - [], + ['rules', 'my-rule', 'then', 'functionOptions'], ), ], ], [ { properties: ['foo', 'bar'], foo: true }, - [new RulesetValidationError('invalid-function-options', '"xor" function does not support "foo" option', [])], + [ + new RulesetValidationError('invalid-function-options', '"xor" function does not support "foo" option', [ + 'rules', + 'my-rule', + 'then', + 'functionOptions', + 'foo', + ]), + ], ], [ { properties: ['foo', 'bar', 'baz'] }, @@ -97,7 +105,7 @@ describe('Core Functions / Xor', () => { new RulesetValidationError( 'invalid-function-options', '"xor" and its "properties" option support 2-item tuples, i.e. ["id", "name"]', - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'properties'], ), ], ], @@ -107,7 +115,7 @@ describe('Core Functions / Xor', () => { new RulesetValidationError( 'invalid-function-options', '"xor" and its "properties" option support 2-item tuples, i.e. ["id", "name"]', - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'properties'], ), ], ], @@ -117,7 +125,7 @@ describe('Core Functions / Xor', () => { new RulesetValidationError( 'invalid-function-options', '"xor" and its "properties" option support 2-item tuples, i.e. ["id", "name"]', - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'properties'], ), ], ], @@ -127,7 +135,7 @@ describe('Core Functions / Xor', () => { new RulesetValidationError( 'invalid-function-options', '"xor" and its "properties" option support 2-item tuples, i.e. ["id", "name"]', - [], + ['rules', 'my-rule', 'then', 'functionOptions', 'properties'], ), ], ], From 4730e43e7b699326ece5deffebb446d26b5b9533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ro=C5=BCek?= Date: Sat, 19 Nov 2022 12:59:36 +0100 Subject: [PATCH 13/16] chore(repo): update compilerOptions.paths --- tsconfig.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tsconfig.json b/tsconfig.json index 858e0c868..8f7094cec 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,8 @@ "baseUrl": ".", "paths": { "@stoplight/spectral-core": ["packages/core/src/index.ts"], + "@stoplight/spectral-core/ruleset": ["packages/core/src/ruleset/index.ts"], + "@stoplight/spectral-core/ruleset/validation": ["packages/core/src/ruleset/validation/index.ts"], "@stoplight/spectral-formats": ["packages/formats/src/index.ts"], "@stoplight/spectral-functions": ["packages/functions/src/index.ts"], "@stoplight/spectral-parsers": ["packages/parsers/src/index.ts"], From f2bddb5362821af7047e23840a68f13040bf0449 Mon Sep 17 00:00:00 2001 From: stoplight-bot Date: Tue, 25 Apr 2023 08:55:28 +0000 Subject: [PATCH 14/16] chore(release): 1.18.0 [skip ci] # [@stoplight/spectral-core-v1.18.0](https://github.com/stoplightio/spectral/compare/@stoplight/spectral-core-v1.17.0...@stoplight/spectral-core-v1.18.0) (2023-04-25) ### Bug Fixes * **core:** more accurate ruleset error paths ([66b3ca7](https://github.com/stoplightio/spectral/commit/66b3ca704136d5d8a34211e72e2d8a2c522261e4)) ### Features * **core:** relax formats validation ([#2151](https://github.com/stoplightio/spectral/issues/2151)) ([de16b4c](https://github.com/stoplightio/spectral/commit/de16b4cbd56cd9836609ab79487a6e3e06df964d)) --- packages/core/CHANGELOG.md | 12 ++++++++++++ packages/core/package.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 3065a2438..254e08254 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,3 +1,15 @@ +# [@stoplight/spectral-core-v1.18.0](https://github.com/stoplightio/spectral/compare/@stoplight/spectral-core-v1.17.0...@stoplight/spectral-core-v1.18.0) (2023-04-25) + + +### Bug Fixes + +* **core:** more accurate ruleset error paths ([66b3ca7](https://github.com/stoplightio/spectral/commit/66b3ca704136d5d8a34211e72e2d8a2c522261e4)) + + +### Features + +* **core:** relax formats validation ([#2151](https://github.com/stoplightio/spectral/issues/2151)) ([de16b4c](https://github.com/stoplightio/spectral/commit/de16b4cbd56cd9836609ab79487a6e3e06df964d)) + # [@stoplight/spectral-core-v1.17.0](https://github.com/stoplightio/spectral/compare/@stoplight/spectral-core-v1.16.1...@stoplight/spectral-core-v1.17.0) (2023-03-23) diff --git a/packages/core/package.json b/packages/core/package.json index 72b5ffd92..89c4d8d4c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@stoplight/spectral-core", - "version": "1.17.0", + "version": "1.18.0", "sideEffects": false, "homepage": "https://github.com/stoplightio/spectral", "bugs": "https://github.com/stoplightio/spectral/issues", From 9ceabca80969885c240349d6ebba15c09a4f8697 Mon Sep 17 00:00:00 2001 From: Calem Roelofs <96050034+CalemRoelofsSB@users.noreply.github.com> Date: Tue, 25 Apr 2023 17:21:46 +0100 Subject: [PATCH 15/16] feat(rulesets): add traits array path to headers rule (#2460) --- ...syncapi-headers-schema-type-object.test.ts | 41 +++++++++++++++++++ packages/rulesets/src/asyncapi/index.ts | 1 + 2 files changed, 42 insertions(+) diff --git a/packages/rulesets/src/asyncapi/__tests__/asyncapi-headers-schema-type-object.test.ts b/packages/rulesets/src/asyncapi/__tests__/asyncapi-headers-schema-type-object.test.ts index 36e99de4e..c0b971b06 100644 --- a/packages/rulesets/src/asyncapi/__tests__/asyncapi-headers-schema-type-object.test.ts +++ b/packages/rulesets/src/asyncapi/__tests__/asyncapi-headers-schema-type-object.test.ts @@ -26,6 +26,18 @@ const document = { message: cloneDeep(headersBearer), }, }, + 'users/{userId}/loggedIn': { + publish: { + message: { + traits: [cloneDeep(headersBearer)], + }, + }, + subscribe: { + message: { + traits: [cloneDeep(headersBearer)], + }, + }, + }, }, components: { messageTraits: { @@ -131,5 +143,34 @@ testRule('asyncapi-headers-schema-type-object', [ }, ], }, + + { + name: `channels.{channel}.${property}.message.traits.[*].headers lacks "type" property`, + document: produce(document, draft => { + draft.channels['users/{userId}/loggedIn'][property].message.traits[0].headers = { const: 'Hello World!' }; + }), + errors: [ + { + message: 'Headers schema type must be "object" ("headers" property must have required property "type").', + path: ['channels', 'users/{userId}/loggedIn', property, 'message', 'traits', '0', 'headers'], + severity: DiagnosticSeverity.Error, + }, + ], + }, + + { + name: `channels.{channel}.${property}.message.traits.[*].headers is not of type "object"`, + document: produce(document, draft => { + draft.channels['users/{userId}/loggedIn'][property].message.traits[0].headers = { type: 'integer' }; + }), + errors: [ + { + message: + 'Headers schema type must be "object" ("type" property must be equal to one of the allowed values: "object". Did you mean "object"?).', + path: ['channels', 'users/{userId}/loggedIn', property, 'message', 'traits', '0', 'headers', 'type'], + severity: DiagnosticSeverity.Error, + }, + ], + }, ]), ]); diff --git a/packages/rulesets/src/asyncapi/index.ts b/packages/rulesets/src/asyncapi/index.ts index 94221b93f..eb20fa1fb 100644 --- a/packages/rulesets/src/asyncapi/index.ts +++ b/packages/rulesets/src/asyncapi/index.ts @@ -90,6 +90,7 @@ export default { '$.components.messageTraits.*.headers', '$.components.messages.*.headers', '$.channels.*.[publish,subscribe].message.headers', + '$.channels.*.[publish,subscribe].message.traits[*].headers', ], then: { function: schema, From 66f769001f045eb841f4fa35821a83ce0b661074 Mon Sep 17 00:00:00 2001 From: stoplight-bot Date: Tue, 25 Apr 2023 16:27:28 +0000 Subject: [PATCH 16/16] chore(release): 1.16.0 [skip ci] # [@stoplight/spectral-rulesets-v1.16.0](https://github.com/stoplightio/spectral/compare/@stoplight/spectral-rulesets-v1.15.1...@stoplight/spectral-rulesets-v1.16.0) (2023-04-25) ### Features * **rulesets:** add traits array path to headers rule ([#2460](https://github.com/stoplightio/spectral/issues/2460)) ([9ceabca](https://github.com/stoplightio/spectral/commit/9ceabca80969885c240349d6ebba15c09a4f8697)) --- packages/rulesets/CHANGELOG.md | 7 +++++++ packages/rulesets/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/rulesets/CHANGELOG.md b/packages/rulesets/CHANGELOG.md index 882de575f..30e256336 100644 --- a/packages/rulesets/CHANGELOG.md +++ b/packages/rulesets/CHANGELOG.md @@ -1,3 +1,10 @@ +# [@stoplight/spectral-rulesets-v1.16.0](https://github.com/stoplightio/spectral/compare/@stoplight/spectral-rulesets-v1.15.1...@stoplight/spectral-rulesets-v1.16.0) (2023-04-25) + + +### Features + +* **rulesets:** add traits array path to headers rule ([#2460](https://github.com/stoplightio/spectral/issues/2460)) ([9ceabca](https://github.com/stoplightio/spectral/commit/9ceabca80969885c240349d6ebba15c09a4f8697)) + # [@stoplight/spectral-rulesets-v1.15.1](https://github.com/stoplightio/spectral/compare/@stoplight/spectral-rulesets-v1.15.0...@stoplight/spectral-rulesets-v1.15.1) (2023-04-25) diff --git a/packages/rulesets/package.json b/packages/rulesets/package.json index c0e4804ff..fd2bf92aa 100644 --- a/packages/rulesets/package.json +++ b/packages/rulesets/package.json @@ -1,6 +1,6 @@ { "name": "@stoplight/spectral-rulesets", - "version": "1.15.1", + "version": "1.16.0", "homepage": "https://github.com/stoplightio/spectral", "bugs": "https://github.com/stoplightio/spectral/issues", "author": "Stoplight ",