fix: show 'referenced from' in the lint error message

This commit is contained in:
Anton Kozachuk
2022-08-29 16:16:53 +03:00
committed by Roman Hotsiy
parent a879b665fc
commit a97be43dce
17 changed files with 212 additions and 21 deletions

View File

@@ -360,7 +360,7 @@ Example value must conform to the schema: type must be string.
78 | x-code-samples:
79 | - lang: 'C#'
referenced from openapi.yaml:73:15
referenced from openapi.yaml:73:15 at #/paths/~1pet~1findByStatus/post/responses/404/content/application~1pdf
Warning was generated by the no-invalid-media-type-examples rule.

View File

@@ -14,7 +14,7 @@ Example validation errored: "nullable" cannot be used without "type".
28 | example: {}
29 | '400':
referenced from openapi.yaml:26:15
referenced from openapi.yaml:26:15 at #/paths/~1test/get/responses/202/content/application~1json
Warning was generated by the no-invalid-media-type-examples rule.

View File

@@ -19,7 +19,7 @@ Example value must conform to the schema: must have required property 'c'.
33 | }
34 | }
referenced from openapi.json:24:35
referenced from openapi.json:24:35 at #/paths/~1test/get/responses/202/content/application~1json
Warning was generated by the no-invalid-media-type-examples rule.
@@ -37,7 +37,7 @@ Example value must conform to the schema: \`a\` property must have required prop
29 | "b": {
30 | "c": "hello"
referenced from openapi.json:24:35
referenced from openapi.json:24:35 at #/paths/~1test/get/responses/202/content/application~1json
Warning was generated by the no-invalid-media-type-examples rule.

View File

@@ -11,7 +11,7 @@ Example value must conform to the schema: type must be string.
| ^^^^^^^^^^^^^^^
2 |
referenced from openapi.yaml:18:13
referenced from openapi.yaml:18:13 at #/paths/~1pet/get/requestBody/content/application~1json
Error was generated by the no-invalid-media-type-examples rule.

View File

@@ -14,7 +14,7 @@ Example value must conform to the schema: type must be array.
35 | responses:
36 | '200':
referenced from openapi.yaml:29:19
referenced from openapi.yaml:29:19 at #/paths/~1my_post/post/requestBody/content/application~1json/schema/properties/my_list
Error was generated by the no-invalid-schema-examples rule.

View File

@@ -14,7 +14,7 @@ Example value must conform to the schema: \`property\` property type must be str
21 | type: object
22 | properties:
referenced from openapi.yaml:19:11
referenced from openapi.yaml:19:11 at #/paths/~1pet/parameters/0/schema
Error was generated by the no-invalid-schema-examples rule.
@@ -30,7 +30,7 @@ Example value must conform to the schema: type must be string.
54 | nested:
55 | allOf:
referenced from openapi.yaml:52:11
referenced from openapi.yaml:52:11 at #/components/schemas/Test/properties/my_list
Error was generated by the no-invalid-schema-examples rule.
@@ -46,7 +46,7 @@ Example value must conform to the schema: type must be string.
73 | Category:
74 | type: object
referenced from openapi.yaml:71:11
referenced from openapi.yaml:71:11 at #/components/schemas/Dog/properties/my_list
Error was generated by the no-invalid-schema-examples rule.
@@ -62,7 +62,7 @@ Example value must conform to the schema: type must be object.
69 | properties:
70 | my_list:
referenced from openapi.yaml:67:7
referenced from openapi.yaml:67:7 at #/components/schemas/Dog
Error was generated by the no-invalid-schema-examples rule.
@@ -78,7 +78,7 @@ Example value must conform to the schema: type must be string.
63 | nested_schema:
64 | oneOf:
referenced from openapi.yaml:61:19
referenced from openapi.yaml:61:19 at #/components/schemas/Test/properties/nested/allOf/1/properties/huntingSkill
Error was generated by the no-invalid-schema-examples rule.
@@ -93,7 +93,7 @@ Example value must conform to the schema: type must be number.
| ^^^^^^^^^^^^^^^^
80 |
referenced from openapi.yaml:77:11
referenced from openapi.yaml:77:11 at #/components/schemas/Category/properties/id
Error was generated by the no-invalid-schema-examples rule.
@@ -109,7 +109,7 @@ Example value must conform to the schema: type must be object.
59 | properties:
60 | huntingSkill:
referenced from openapi.yaml:57:15
referenced from openapi.yaml:57:15 at #/components/schemas/Test/properties/nested/allOf/1
Error was generated by the no-invalid-schema-examples rule.
@@ -125,7 +125,7 @@ Example value must conform to the schema: type must be object.
50 | properties:
51 | my_list:
referenced from openapi.yaml:48:7
referenced from openapi.yaml:48:7 at #/components/schemas/Test
Error was generated by the no-invalid-schema-examples rule.

View File

@@ -16,7 +16,7 @@ Example value must conform to the schema: type must be integer,string.
36 | description: two
37 | type:
referenced from openapi.yaml:29:19
referenced from openapi.yaml:29:19 at #/paths/~1my_post/post/requestBody/content/application~1json/schema/properties/one
Error was generated by the no-invalid-schema-examples rule.
@@ -32,7 +32,7 @@ Example value must conform to the schema: type must be string.
42 | responses:
43 | '200':
referenced from openapi.yaml:36:19
referenced from openapi.yaml:36:19 at #/paths/~1my_post/post/requestBody/content/application~1json/schema/properties/two
Error was generated by the no-invalid-schema-examples rule.

View File

@@ -14,7 +14,7 @@ Example value must conform to the schema: type must be string.
31 | prop_number:
32 | type: number
referenced from openapi.yaml:29:19
referenced from openapi.yaml:29:19 at #/paths/~1my_post/post/requestBody/content/application~1json/schema/properties/prop_string
Error was generated by the no-invalid-schema-examples rule.
@@ -30,7 +30,7 @@ Example value must conform to the schema: type must be number.
34 | prop_integer:
35 | type: integer
referenced from openapi.yaml:32:19
referenced from openapi.yaml:32:19 at #/paths/~1my_post/post/requestBody/content/application~1json/schema/properties/prop_number
Error was generated by the no-invalid-schema-examples rule.
@@ -46,7 +46,7 @@ Example value must conform to the schema: type must be integer.
37 |
38 | responses:
referenced from openapi.yaml:35:19
referenced from openapi.yaml:35:19 at #/paths/~1my_post/post/requestBody/content/application~1json/schema/properties/prop_integer
Error was generated by the no-invalid-schema-examples rule.

View File

@@ -0,0 +1,8 @@
apis:
main:
root: ./openapi.yaml
styleguide:
extends: []
rules:
spec: error

View File

@@ -0,0 +1,13 @@
openapi: 3.0.0
components:
requestBodies:
TestRequestBody:
content:
application/json:
schema:
type: object
schemas:
TestSchema:
title: TestSchema
allOf:
- $ref: "#/components/requestBodies/TestRequestBody"

View File

@@ -0,0 +1,58 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`E2E lint spec-from 1`] = `
validating /openapi.yaml...
[1] openapi.yaml:1:1 at #/
The field \`paths\` must be present on this level.
1 | openapi: 3.0.0
| ^^^^^^^^^^^^^^
2 | components:
| ^^^^^^^^^^^
… | < 10 more lines >
13 | - $ref: "#/components/requestBodies/TestRequestBody"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error was generated by the spec rule.
[2] openapi.yaml:1:1 at #/
The field \`info\` must be present on this level.
1 | openapi: 3.0.0
| ^^^^^^^^^^^^^^
2 | components:
| ^^^^^^^^^^^
… | < 10 more lines >
13 | - $ref: "#/components/requestBodies/TestRequestBody"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error was generated by the spec rule.
[3] openapi.yaml:5:7 at #/components/requestBodies/TestRequestBody/content
Property \`content\` is not expected here.
3 | requestBodies:
4 | TestRequestBody:
5 | content:
| ^^^^^^^
6 | application/json:
7 | schema:
referenced from openapi.yaml:13:11 at #/components/schemas/TestSchema/allOf/0
Error was generated by the spec rule.
/openapi.yaml: validated in <test>ms
❌ Validation failed with 3 errors.
run \`openapi lint --generate-ignore-file\` to add all problems to the ignore file.
`;

View File

@@ -29,6 +29,7 @@ describe('lint', () => {
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
Array [
Object {
"from": undefined,
"location": Array [
Object {
"pointer": "#/info/license",
@@ -78,6 +79,7 @@ describe('lint', () => {
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
Array [
Object {
"from": undefined,
"location": Array [
Object {
"pointer": "#/apis",
@@ -91,6 +93,7 @@ describe('lint', () => {
"suggest": Array [],
},
Object {
"from": undefined,
"location": Array [
Object {
"pointer": "#/features.openapi/layout",
@@ -124,6 +127,7 @@ describe('lint', () => {
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
Array [
Object {
"from": undefined,
"location": Array [
Object {
"pointer": "#/api",
@@ -139,6 +143,7 @@ describe('lint', () => {
],
},
Object {
"from": undefined,
"location": Array [
Object {
"pointer": "#/syleguide",
@@ -193,6 +198,7 @@ describe('lint', () => {
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
Array [
Object {
"from": undefined,
"location": Array [
Object {
"pointer": "#/apis/styleguide",
@@ -206,6 +212,7 @@ describe('lint', () => {
"suggest": Array [],
},
Object {
"from": undefined,
"location": Array [
Object {
"pointer": "#/apis/styleguide/plugins",

View File

@@ -262,8 +262,9 @@ function formatFrom(cwd: string, location?: LocationObject) {
const relativePath = path.relative(cwd, location.source.absoluteRef);
const loc = getLineColLocation(location);
const fileWithLoc = `${relativePath}:${loc.start.line}:${loc.start.col}`;
const atPointer = location.pointer ? colorize.gray(`at ${location.pointer}`) : '';
return `referenced from ${colorize.blue(fileWithLoc)}\n\n`;
return `referenced from ${colorize.blue(fileWithLoc)} ${atPointer} \n\n`;
}
function formatDidYouMean(problem: NormalizedProblem) {

View File

@@ -191,6 +191,7 @@ describe('Oas3 typed enum', () => {
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
Array [
Object {
"from": undefined,
"location": Array [
Object {
"pointer": "#/paths/~1some/get/responses/200/content/application~1json/schema",

View File

@@ -31,6 +31,7 @@ describe('Oas3 spec', () => {
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
Array [
Object {
"from": undefined,
"location": Array [
Object {
"pointer": "#/",
@@ -44,6 +45,7 @@ describe('Oas3 spec', () => {
"suggest": Array [],
},
Object {
"from": undefined,
"location": Array [
Object {
"pointer": "#/paths/~1test/get/parameters/0",
@@ -59,4 +61,81 @@ describe('Oas3 spec', () => {
]
`);
});
it('should report with "referenced from"', async () => {
const document = parseYamlToDocument(
outdent`
openapi: 3.0.0
components:
requestBodies:
TestRequestBody:
content:
application/json:
schema:
type: object
schemas:
TestSchema:
title: TestSchema
allOf:
- $ref: "#/components/requestBodies/TestRequestBody"
`,
'foobar.yaml'
);
const results = await lintDocument({
externalRefResolver: new BaseResolver(),
document,
config: await makeConfig({ spec: 'error' }),
});
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
Array [
Object {
"from": undefined,
"location": Array [
Object {
"pointer": "#/",
"reportOnKey": true,
"source": "foobar.yaml",
},
],
"message": "The field \`paths\` must be present on this level.",
"ruleId": "spec",
"severity": "error",
"suggest": Array [],
},
Object {
"from": undefined,
"location": Array [
Object {
"pointer": "#/",
"reportOnKey": true,
"source": "foobar.yaml",
},
],
"message": "The field \`info\` must be present on this level.",
"ruleId": "spec",
"severity": "error",
"suggest": Array [],
},
Object {
"from": Object {
"pointer": "#/components/schemas/TestSchema/allOf/0",
"source": "foobar.yaml",
},
"location": Array [
Object {
"pointer": "#/components/requestBodies/TestRequestBody/content",
"reportOnKey": true,
"source": "foobar.yaml",
},
],
"message": "Property \`content\` is not expected here.",
"ruleId": "spec",
"severity": "error",
"suggest": Array [],
},
]
`);
});
});

View File

@@ -6,13 +6,18 @@ import { isPlainObject } from '../../utils';
export const OasSpec: Oas3Rule | Oas2Rule = () => {
return {
any(node: any, { report, type, location, key, resolve, ignoreNextVisitorsOnNode }) {
any(
node: any,
{ report, type, location, rawLocation, key, resolve, ignoreNextVisitorsOnNode }
) {
const nodeType = oasTypeOf(node);
const refLocation = rawLocation !== location ? rawLocation : undefined;
if (type.items) {
if (nodeType !== 'array') {
report({
message: `Expected type \`${type.name}\` (array) but got \`${nodeType}\``,
from: refLocation,
});
ignoreNextVisitorsOnNode();
}
@@ -20,6 +25,7 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
} else if (nodeType !== 'object') {
report({
message: `Expected type \`${type.name}\` (object) but got \`${nodeType}\``,
from: refLocation,
});
ignoreNextVisitorsOnNode();
return;
@@ -32,6 +38,7 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
if (!(node as object).hasOwnProperty(propName)) {
report({
message: `The field \`${propName}\` must be present on this level.`,
from: refLocation,
location: [{ reportOnKey: true }],
});
}
@@ -49,6 +56,7 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
}
report({
message: `The field \`${propName}\` is not allowed here.`,
from: refLocation,
location: location.child([propName]).key(),
});
}
@@ -67,6 +75,7 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
message: `Must contain at least one of the following fields: ${type.requiredOneOf?.join(
', '
)}.`,
from: refLocation,
location: [{ reportOnKey: true }],
});
}
@@ -91,6 +100,7 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
report({
message: `Property \`${propName}\` is not expected here.`,
suggest: getSuggest(propName, Object.keys(type.properties)),
from: refLocation,
location: propLocation.key(),
});
continue;
@@ -111,12 +121,14 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
message: `\`${propName}\` can be one of the following only: ${propSchema.enum
.map((i) => `"${i}"`)
.join(', ')}.`,
from: refLocation,
suggest: getSuggest(propValue, propSchema.enum),
});
}
} else if (propSchema.type && !matchesJsonSchemaType(propValue, propSchema.type, false)) {
report({
message: `Expected type \`${propSchema.type}\` but got \`${propValueType}\`.`,
from: refLocation,
location: propLocation,
});
} else if (propValueType === 'array' && propSchema.items?.type) {
@@ -126,6 +138,7 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
if (!matchesJsonSchemaType(item, itemsType, false)) {
report({
message: `Expected type \`${itemsType}\` but got \`${oasTypeOf(item)}\`.`,
from: refLocation,
location: propLocation.child([i]),
});
}
@@ -136,6 +149,7 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
if (propSchema.minimum > node[propName]) {
report({
message: `The value of the ${propName} field must be greater than or equal to ${propSchema.minimum}`,
from: refLocation,
location: location.child([propName]),
});
}

View File

@@ -47,6 +47,7 @@ describe('Oas3 Structural visitor basic', () => {
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
Array [
Object {
"from": undefined,
"location": Array [
Object {
"pointer": "#/info/contact/url",
@@ -60,6 +61,7 @@ describe('Oas3 Structural visitor basic', () => {
"suggest": Array [],
},
Object {
"from": undefined,
"location": Array [
Object {
"pointer": "#/info/contact/email",
@@ -73,6 +75,7 @@ describe('Oas3 Structural visitor basic', () => {
"suggest": Array [],
},
Object {
"from": undefined,
"location": Array [
Object {
"pointer": "#/info/license",
@@ -103,6 +106,7 @@ describe('Oas3 Structural visitor basic', () => {
"suggest": Array [],
},
Object {
"from": undefined,
"location": Array [
Object {
"pointer": "#/servers/0/variables/a/enum/0",
@@ -116,6 +120,7 @@ describe('Oas3 Structural visitor basic', () => {
"suggest": Array [],
},
Object {
"from": undefined,
"location": Array [
Object {
"pointer": "#/tags/0",
@@ -129,6 +134,7 @@ describe('Oas3 Structural visitor basic', () => {
"suggest": Array [],
},
Object {
"from": undefined,
"location": Array [
Object {
"pointer": "#/tags/1",
@@ -175,6 +181,7 @@ describe('Oas3 Structural visitor basic', () => {
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
Array [
Object {
"from": undefined,
"location": Array [
Object {
"pointer": "#/components1",
@@ -203,6 +210,7 @@ describe('Oas3 Structural visitor basic', () => {
"suggest": Array [],
},
Object {
"from": undefined,
"location": Array [
Object {
"pointer": "#/info/contact/test",
@@ -244,6 +252,7 @@ describe('Oas3 Structural visitor basic', () => {
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
Array [
Object {
"from": undefined,
"location": Array [
Object {
"pointer": "#/",
@@ -270,6 +279,7 @@ describe('Oas3 Structural visitor basic', () => {
"suggest": Array [],
},
Object {
"from": undefined,
"location": Array [
Object {
"pointer": "#/info",