diff --git a/package.json b/package.json index f7f2881814..669f332f7c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "openapi-workspaces", "license": "MIT", "private": true, - "version": "0.53.18", + "version": "0.53.19", "workspaces": [ "projects/json-pointer-helpers", "projects/openapi-io", diff --git a/projects/fastify-capture/package.json b/projects/fastify-capture/package.json index 69964a8e6e..1fbe22eb0b 100644 --- a/projects/fastify-capture/package.json +++ b/projects/fastify-capture/package.json @@ -2,7 +2,7 @@ "name": "@useoptic/fastify-capture", "license": "MIT", "packageManager": "yarn@4.0.2", - "version": "0.53.18", + "version": "0.53.19", "main": "build/index.js", "types": "build/index.d.ts", "files": [ diff --git a/projects/json-pointer-helpers/package.json b/projects/json-pointer-helpers/package.json index 5fdfca98f1..d7d3e764e1 100644 --- a/projects/json-pointer-helpers/package.json +++ b/projects/json-pointer-helpers/package.json @@ -2,7 +2,7 @@ "name": "@useoptic/json-pointer-helpers", "license": "MIT", "packageManager": "yarn@4.0.2", - "version": "0.53.18", + "version": "0.53.19", "main": "build/index.js", "types": "build/index.d.ts", "files": [ diff --git a/projects/openapi-io/package.json b/projects/openapi-io/package.json index bbb1fc1f10..d41b29d941 100644 --- a/projects/openapi-io/package.json +++ b/projects/openapi-io/package.json @@ -2,7 +2,7 @@ "name": "@useoptic/openapi-io", "license": "MIT", "packageManager": "yarn@4.0.2", - "version": "0.53.18", + "version": "0.53.19", "main": "build/index.js", "types": "build/index.d.ts", "files": [ diff --git a/projects/openapi-utilities/package.json b/projects/openapi-utilities/package.json index 2ad7ad616f..f02972cca2 100644 --- a/projects/openapi-utilities/package.json +++ b/projects/openapi-utilities/package.json @@ -2,7 +2,7 @@ "name": "@useoptic/openapi-utilities", "license": "MIT", "packageManager": "yarn@4.0.2", - "version": "0.53.18", + "version": "0.53.19", "main": "build/index.js", "types": "build/index.d.ts", "files": [ diff --git a/projects/optic/package.json b/projects/optic/package.json index dfde4c709c..6ff907121b 100644 --- a/projects/optic/package.json +++ b/projects/optic/package.json @@ -2,7 +2,7 @@ "name": "@useoptic/optic", "license": "MIT", "packageManager": "yarn@4.0.2", - "version": "0.53.18", + "version": "0.53.19", "main": "build/index.js", "types": "build/index.d.ts", "files": [ diff --git a/projects/rulesets-base/package.json b/projects/rulesets-base/package.json index c09bb1a724..6fb45ad43d 100644 --- a/projects/rulesets-base/package.json +++ b/projects/rulesets-base/package.json @@ -2,7 +2,7 @@ "name": "@useoptic/rulesets-base", "license": "MIT", "packageManager": "yarn@4.0.2", - "version": "0.53.18", + "version": "0.53.19", "main": "build/index.js", "types": "build/index.d.ts", "files": [ diff --git a/projects/standard-rulesets/package.json b/projects/standard-rulesets/package.json index 4fa252ee9a..6be0efaeed 100644 --- a/projects/standard-rulesets/package.json +++ b/projects/standard-rulesets/package.json @@ -2,7 +2,7 @@ "name": "@useoptic/standard-rulesets", "license": "MIT", "packageManager": "yarn@4.0.2", - "version": "0.53.18", + "version": "0.53.19", "main": "build/index.js", "types": "build/index.d.ts", "files": [ diff --git a/projects/standard-rulesets/src/examples/__tests__/__snapshots__/examples-are-valid-rules.test.ts.snap b/projects/standard-rulesets/src/examples/__tests__/__snapshots__/examples-are-valid-rules.test.ts.snap index 434c2e93e5..7c4dad7653 100644 --- a/projects/standard-rulesets/src/examples/__tests__/__snapshots__/examples-are-valid-rules.test.ts.snap +++ b/projects/standard-rulesets/src/examples/__tests__/__snapshots__/examples-are-valid-rules.test.ts.snap @@ -1168,6 +1168,172 @@ exports[`3.0.x examples ruleset passing property example 1`] = ` ] `; +exports[`3.0.x examples ruleset with duplicate in ids in examples 1`] = ` +[ + { + "change": { + "location": { + "conceptualLocation": { + "inRequest": { + "body": { + "contentType": "application/json", + }, + }, + "method": "post", + "path": "/api/users", + }, + "conceptualPath": [ + "operations", + "/api/users", + "post", + "application/json", + ], + "jsonPath": "/paths/~1api~1users/post/requestBody/content/application~1json", + "kind": "body", + }, + "value": { + "contentType": "application/json", + "flatSchema": { + "oneOf": [ + { + "example": { + "id": "1", + }, + "properties": { + "id": { + "type": "string", + }, + }, + "type": "object", + }, + { + "example": { + "id": "1", + }, + "properties": { + "id": { + "type": "string", + }, + }, + "type": "object", + }, + ], + }, + }, + }, + "condition": undefined, + "docsLink": undefined, + "error": undefined, + "exempted": false, + "expected": undefined, + "isMust": true, + "isShould": false, + "name": "request body examples must match schema", + "passed": true, + "received": undefined, + "severity": 2, + "type": "requirement", + "where": "POST /api/users request body: application/json", + }, + { + "change": { + "location": { + "conceptualLocation": { + "inRequest": { + "body": { + "contentType": "application/json", + }, + }, + "jsonSchemaTrail": [ + "id", + ], + "method": "post", + "path": "/api/users", + }, + "conceptualPath": [ + "operations", + "/api/users", + "post", + "application/json", + "oneOf", + "0", + "id", + ], + "jsonPath": "/paths/~1api~1users/post/requestBody/content/application~1json/schema/oneOf/0/properties/id", + "kind": "field", + }, + "value": { + "flatSchema": { + "type": "string", + }, + "key": "id", + "required": false, + }, + }, + "condition": undefined, + "docsLink": undefined, + "error": undefined, + "expected": undefined, + "isMust": true, + "isShould": false, + "name": "require property examples match schemas", + "passed": true, + "received": undefined, + "severity": 2, + "type": "requirement", + "where": "POST /api/users request body: application/json property: id", + }, + { + "change": { + "location": { + "conceptualLocation": { + "inRequest": { + "body": { + "contentType": "application/json", + }, + }, + "jsonSchemaTrail": [ + "id", + ], + "method": "post", + "path": "/api/users", + }, + "conceptualPath": [ + "operations", + "/api/users", + "post", + "application/json", + "oneOf", + "1", + "id", + ], + "jsonPath": "/paths/~1api~1users/post/requestBody/content/application~1json/schema/oneOf/1/properties/id", + "kind": "field", + }, + "value": { + "flatSchema": { + "type": "string", + }, + "key": "id", + "required": false, + }, + }, + "condition": undefined, + "docsLink": undefined, + "error": undefined, + "expected": undefined, + "isMust": true, + "isShould": false, + "name": "require property examples match schemas", + "passed": true, + "received": undefined, + "severity": 2, + "type": "requirement", + "where": "POST /api/users request body: application/json property: id", + }, +] +`; + exports[`3.1.x examples ruleset examples should default to additional properties false ajv config will be strict on additional properties 1`] = ` { "error": " - example must NOT have additional property 'c'", @@ -2298,3 +2464,169 @@ exports[`3.1.x examples ruleset passing property example 1`] = ` }, ] `; + +exports[`3.1.x examples ruleset with duplicate in ids in examples 1`] = ` +[ + { + "change": { + "location": { + "conceptualLocation": { + "inRequest": { + "body": { + "contentType": "application/json", + }, + }, + "method": "post", + "path": "/api/users", + }, + "conceptualPath": [ + "operations", + "/api/users", + "post", + "application/json", + ], + "jsonPath": "/paths/~1api~1users/post/requestBody/content/application~1json", + "kind": "body", + }, + "value": { + "contentType": "application/json", + "flatSchema": { + "oneOf": [ + { + "example": { + "id": "1", + }, + "properties": { + "id": { + "type": "string", + }, + }, + "type": "object", + }, + { + "example": { + "id": "1", + }, + "properties": { + "id": { + "type": "string", + }, + }, + "type": "object", + }, + ], + }, + }, + }, + "condition": undefined, + "docsLink": undefined, + "error": undefined, + "exempted": false, + "expected": undefined, + "isMust": true, + "isShould": false, + "name": "request body examples must match schema", + "passed": true, + "received": undefined, + "severity": 2, + "type": "requirement", + "where": "POST /api/users request body: application/json", + }, + { + "change": { + "location": { + "conceptualLocation": { + "inRequest": { + "body": { + "contentType": "application/json", + }, + }, + "jsonSchemaTrail": [ + "id", + ], + "method": "post", + "path": "/api/users", + }, + "conceptualPath": [ + "operations", + "/api/users", + "post", + "application/json", + "oneOf", + "0", + "id", + ], + "jsonPath": "/paths/~1api~1users/post/requestBody/content/application~1json/schema/oneOf/0/properties/id", + "kind": "field", + }, + "value": { + "flatSchema": { + "type": "string", + }, + "key": "id", + "required": false, + }, + }, + "condition": undefined, + "docsLink": undefined, + "error": undefined, + "expected": undefined, + "isMust": true, + "isShould": false, + "name": "require property examples match schemas", + "passed": true, + "received": undefined, + "severity": 2, + "type": "requirement", + "where": "POST /api/users request body: application/json property: id", + }, + { + "change": { + "location": { + "conceptualLocation": { + "inRequest": { + "body": { + "contentType": "application/json", + }, + }, + "jsonSchemaTrail": [ + "id", + ], + "method": "post", + "path": "/api/users", + }, + "conceptualPath": [ + "operations", + "/api/users", + "post", + "application/json", + "oneOf", + "1", + "id", + ], + "jsonPath": "/paths/~1api~1users/post/requestBody/content/application~1json/schema/oneOf/1/properties/id", + "kind": "field", + }, + "value": { + "flatSchema": { + "type": "string", + }, + "key": "id", + "required": false, + }, + }, + "condition": undefined, + "docsLink": undefined, + "error": undefined, + "expected": undefined, + "isMust": true, + "isShould": false, + "name": "require property examples match schemas", + "passed": true, + "received": undefined, + "severity": 2, + "type": "requirement", + "where": "POST /api/users request body: application/json property: id", + }, +] +`; diff --git a/projects/standard-rulesets/src/examples/__tests__/examples-are-valid-rules.test.ts b/projects/standard-rulesets/src/examples/__tests__/examples-are-valid-rules.test.ts index 01d08a42d2..0077e10b24 100644 --- a/projects/standard-rulesets/src/examples/__tests__/examples-are-valid-rules.test.ts +++ b/projects/standard-rulesets/src/examples/__tests__/examples-are-valid-rules.test.ts @@ -337,6 +337,43 @@ describe.each(['3.0.x', '3.1.x'] as const)( expect(results.every((result) => result.passed)).toBe(true); }); + test('with duplicate in ids in examples', async () => { + const schemaWithIdExample = { + type: 'object', + properties: { id: { type: 'string' } }, + example: { id: '1' }, + }; + const input: any = { + ...TestHelpers.createEmptySpec(), + paths: { + '/api/users': { + post: { + responses: {}, + requestBody: { + description: '', + content: { + 'application/json': { + schema: { + oneOf: [schemaWithIdExample, schemaWithIdExample], + }, + }, + }, + }, + }, + }, + }, + }; + const results = await TestHelpers.runRulesWithInputs( + [new ExamplesRuleset({ spec_version: version })], + input, + input + ); + expect(results.length > 0).toBe(true); + + expect(results).toMatchSnapshot(); + expect(results.some((result) => result.passed)).toBe(true); + }); + describe('examples should default to additional properties false', () => { test('ajv config will be strict on additional properties', () => { const result = validateSchema( diff --git a/projects/standard-rulesets/src/examples/requireValidExamples.ts b/projects/standard-rulesets/src/examples/requireValidExamples.ts index 289db35871..e9cadfc34f 100644 --- a/projects/standard-rulesets/src/examples/requireValidExamples.ts +++ b/projects/standard-rulesets/src/examples/requireValidExamples.ts @@ -101,6 +101,11 @@ function prepareSchemaForValidation( return; } + // Delete example and examples from schema, not necessary for using the schema and can cause false positives with AJV + // See https://github.com/opticdev/optic/issues/2631 for details + if ('example' in schema) delete schema.example; + if ('examples' in schema) delete schema.examples; + if (!inAllOf) { if (OAS3.isObjectType(schema.type) && !schema.additionalProperties) { schema.additionalProperties = false;