@redocly/openapi-core 1.0.0-beta.108 → 1.0.0-beta.109
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/benchmark/benches/resolve-with-no-external.bench.js +1 -1
- package/lib/bundle.d.ts +1 -1
- package/lib/bundle.js +4 -4
- package/lib/config/all.js +3 -1
- package/lib/config/config-resolvers.js +1 -1
- package/lib/config/minimal.js +3 -1
- package/lib/config/recommended.js +3 -1
- package/lib/config/rules.js +1 -1
- package/lib/config/types.d.ts +7 -0
- package/lib/config/utils.d.ts +2 -2
- package/lib/config/utils.js +42 -4
- package/lib/decorators/common/registry-dependencies.js +1 -1
- package/lib/format/format.d.ts +1 -1
- package/lib/format/format.js +22 -1
- package/lib/lint.js +2 -2
- package/lib/redocly/registry-api.d.ts +0 -1
- package/lib/redocly/registry-api.js +5 -4
- package/lib/resolve.js +3 -1
- package/lib/rules/common/no-ambiguous-paths.js +1 -1
- package/lib/rules/common/no-identical-paths.js +1 -1
- package/lib/rules/common/operation-2xx-response.js +1 -1
- package/lib/rules/common/operation-4xx-response.js +1 -1
- package/lib/rules/common/operation-operationId.js +1 -1
- package/lib/rules/common/operation-tag-defined.js +1 -1
- package/lib/rules/common/path-not-include-query.js +1 -1
- package/lib/rules/common/security-defined.d.ts +2 -0
- package/lib/rules/common/{operation-security-defined.js → security-defined.js} +18 -4
- package/lib/rules/common/spec.js +12 -1
- package/lib/rules/common/tags-alphabetical.js +1 -1
- package/lib/rules/oas2/index.d.ts +1 -1
- package/lib/rules/oas2/index.js +2 -2
- package/lib/rules/oas2/remove-unused-components.js +1 -1
- package/lib/rules/oas2/request-mime-type.js +1 -1
- package/lib/rules/oas2/response-mime-type.js +1 -1
- package/lib/rules/oas3/index.js +6 -2
- package/lib/rules/oas3/no-empty-servers.js +1 -1
- package/lib/rules/oas3/no-server-variables-empty-enum.js +1 -1
- package/lib/rules/oas3/no-unused-components.js +1 -1
- package/lib/rules/oas3/operation-4xx-problem-details-rfc7807.d.ts +5 -0
- package/lib/rules/oas3/operation-4xx-problem-details-rfc7807.js +36 -0
- package/lib/rules/oas3/remove-unused-components.js +1 -1
- package/lib/rules/oas3/request-mime-type.js +1 -1
- package/lib/rules/oas3/response-mime-type.js +1 -1
- package/lib/rules/oas3/spec-components-invalid-map-name.d.ts +2 -0
- package/lib/rules/oas3/spec-components-invalid-map-name.js +46 -0
- package/lib/rules/other/stats.d.ts +2 -2
- package/lib/rules/other/stats.js +2 -2
- package/lib/types/oas2.js +5 -5
- package/lib/types/oas3.js +27 -20
- package/lib/types/oas3_1.js +3 -3
- package/lib/types/redocly-yaml.js +46 -55
- package/lib/utils.d.ts +3 -3
- package/lib/utils.js +5 -5
- package/lib/visitors.d.ts +11 -11
- package/lib/visitors.js +13 -1
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/bundle.test.ts.snap +3 -3
- package/src/__tests__/fixtures/extension.js +3 -3
- package/src/__tests__/format.test.ts +76 -0
- package/src/__tests__/lint.test.ts +106 -131
- package/src/__tests__/resolve-http.test.ts +1 -1
- package/src/__tests__/resolve.test.ts +9 -9
- package/src/__tests__/walk.test.ts +78 -10
- package/src/benchmark/benches/resolve-with-no-external.bench.ts +1 -1
- package/src/bundle.ts +4 -4
- package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +6 -2
- package/src/config/__tests__/fixtures/plugin-config.yaml +2 -3
- package/src/config/__tests__/fixtures/resolve-config/api/nested-config.yaml +11 -12
- package/src/config/__tests__/fixtures/resolve-config/local-config-with-circular.yaml +7 -8
- package/src/config/__tests__/fixtures/resolve-config/local-config-with-file.yaml +18 -19
- package/src/config/__tests__/fixtures/resolve-config/local-config.yaml +9 -10
- package/src/config/__tests__/fixtures/resolve-remote-configs/nested-remote-config.yaml +3 -4
- package/src/config/__tests__/fixtures/resolve-remote-configs/remote-config.yaml +4 -5
- package/src/config/__tests__/load.test.ts +12 -15
- package/src/config/__tests__/utils.test.ts +64 -4
- package/src/config/all.ts +3 -1
- package/src/config/config-resolvers.ts +2 -2
- package/src/config/load.ts +3 -2
- package/src/config/minimal.ts +3 -1
- package/src/config/recommended.ts +3 -1
- package/src/config/rules.ts +2 -2
- package/src/config/types.ts +11 -0
- package/src/config/utils.ts +102 -13
- package/src/decorators/common/registry-dependencies.ts +1 -1
- package/src/format/format.ts +32 -2
- package/src/lint.ts +2 -2
- package/src/redocly/registry-api.ts +5 -4
- package/src/resolve.ts +3 -1
- package/src/rules/__tests__/utils.test.ts +1 -1
- package/src/rules/common/__tests__/no-enum-type-mismatch.test.ts +1 -0
- package/src/rules/common/__tests__/operation-2xx-response.test.ts +1 -1
- package/src/rules/common/__tests__/operation-4xx-response.test.ts +26 -3
- package/src/rules/common/__tests__/security-defined.test.ts +175 -0
- package/src/rules/common/__tests__/spec.test.ts +79 -0
- package/src/rules/common/assertions/__tests__/utils.test.ts +2 -2
- package/src/rules/common/no-ambiguous-paths.ts +1 -1
- package/src/rules/common/no-identical-paths.ts +1 -1
- package/src/rules/common/operation-2xx-response.ts +1 -1
- package/src/rules/common/operation-4xx-response.ts +1 -1
- package/src/rules/common/operation-operationId.ts +1 -1
- package/src/rules/common/operation-tag-defined.ts +1 -1
- package/src/rules/common/path-not-include-query.ts +1 -1
- package/src/rules/common/{operation-security-defined.ts → security-defined.ts} +19 -4
- package/src/rules/common/spec.ts +15 -1
- package/src/rules/common/tags-alphabetical.ts +1 -1
- package/src/rules/oas2/index.ts +2 -2
- package/src/rules/oas2/remove-unused-components.ts +1 -1
- package/src/rules/oas2/request-mime-type.ts +1 -1
- package/src/rules/oas2/response-mime-type.ts +1 -1
- package/src/rules/oas3/__tests__/operation-4xx-problem-details-rfc7807.test.ts +145 -0
- package/src/rules/oas3/__tests__/spec/spec.test.ts +10 -0
- package/src/rules/oas3/__tests__/spec-components-invalid-map-name.test.ts +217 -0
- package/src/rules/oas3/index.ts +6 -2
- package/src/rules/oas3/no-empty-servers.ts +1 -1
- package/src/rules/oas3/no-server-variables-empty-enum.ts +1 -1
- package/src/rules/oas3/no-unused-components.ts +1 -1
- package/src/rules/oas3/operation-4xx-problem-details-rfc7807.ts +36 -0
- package/src/rules/oas3/remove-unused-components.ts +1 -1
- package/src/rules/oas3/request-mime-type.ts +1 -1
- package/src/rules/oas3/response-mime-type.ts +1 -1
- package/src/rules/oas3/spec-components-invalid-map-name.ts +53 -0
- package/src/rules/other/stats.ts +2 -2
- package/src/types/index.ts +2 -2
- package/src/types/oas2.ts +5 -5
- package/src/types/oas3.ts +27 -20
- package/src/types/oas3_1.ts +3 -3
- package/src/types/redocly-yaml.ts +52 -40
- package/src/utils.ts +11 -7
- package/src/visitors.ts +29 -13
- package/tsconfig.tsbuildinfo +1 -1
- package/lib/rules/common/operation-security-defined.d.ts +0 -2
- package/src/rules/common/__tests__/operation-security-defined.test.ts +0 -69
|
@@ -31,6 +31,7 @@ describe('Oas3 spec', () => {
|
|
|
31
31
|
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
32
32
|
Array [
|
|
33
33
|
Object {
|
|
34
|
+
"from": undefined,
|
|
34
35
|
"location": Array [
|
|
35
36
|
Object {
|
|
36
37
|
"pointer": "#/",
|
|
@@ -44,6 +45,7 @@ describe('Oas3 spec', () => {
|
|
|
44
45
|
"suggest": Array [],
|
|
45
46
|
},
|
|
46
47
|
Object {
|
|
48
|
+
"from": undefined,
|
|
47
49
|
"location": Array [
|
|
48
50
|
Object {
|
|
49
51
|
"pointer": "#/paths/~1test/get/parameters/0",
|
|
@@ -59,4 +61,81 @@ describe('Oas3 spec', () => {
|
|
|
59
61
|
]
|
|
60
62
|
`);
|
|
61
63
|
});
|
|
64
|
+
|
|
65
|
+
it('should report with "referenced from"', async () => {
|
|
66
|
+
const document = parseYamlToDocument(
|
|
67
|
+
outdent`
|
|
68
|
+
openapi: 3.0.0
|
|
69
|
+
components:
|
|
70
|
+
requestBodies:
|
|
71
|
+
TestRequestBody:
|
|
72
|
+
content:
|
|
73
|
+
application/json:
|
|
74
|
+
schema:
|
|
75
|
+
type: object
|
|
76
|
+
schemas:
|
|
77
|
+
TestSchema:
|
|
78
|
+
title: TestSchema
|
|
79
|
+
allOf:
|
|
80
|
+
- $ref: "#/components/requestBodies/TestRequestBody"
|
|
81
|
+
`,
|
|
82
|
+
'foobar.yaml'
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const results = await lintDocument({
|
|
86
|
+
externalRefResolver: new BaseResolver(),
|
|
87
|
+
document,
|
|
88
|
+
config: await makeConfig({ spec: 'error' }),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
92
|
+
Array [
|
|
93
|
+
Object {
|
|
94
|
+
"from": undefined,
|
|
95
|
+
"location": Array [
|
|
96
|
+
Object {
|
|
97
|
+
"pointer": "#/",
|
|
98
|
+
"reportOnKey": true,
|
|
99
|
+
"source": "foobar.yaml",
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
"message": "The field \`paths\` must be present on this level.",
|
|
103
|
+
"ruleId": "spec",
|
|
104
|
+
"severity": "error",
|
|
105
|
+
"suggest": Array [],
|
|
106
|
+
},
|
|
107
|
+
Object {
|
|
108
|
+
"from": undefined,
|
|
109
|
+
"location": Array [
|
|
110
|
+
Object {
|
|
111
|
+
"pointer": "#/",
|
|
112
|
+
"reportOnKey": true,
|
|
113
|
+
"source": "foobar.yaml",
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
"message": "The field \`info\` must be present on this level.",
|
|
117
|
+
"ruleId": "spec",
|
|
118
|
+
"severity": "error",
|
|
119
|
+
"suggest": Array [],
|
|
120
|
+
},
|
|
121
|
+
Object {
|
|
122
|
+
"from": Object {
|
|
123
|
+
"pointer": "#/components/schemas/TestSchema/allOf/0",
|
|
124
|
+
"source": "foobar.yaml",
|
|
125
|
+
},
|
|
126
|
+
"location": Array [
|
|
127
|
+
Object {
|
|
128
|
+
"pointer": "#/components/requestBodies/TestRequestBody/content",
|
|
129
|
+
"reportOnKey": true,
|
|
130
|
+
"source": "foobar.yaml",
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
"message": "Property \`content\` is not expected here.",
|
|
134
|
+
"ruleId": "spec",
|
|
135
|
+
"severity": "error",
|
|
136
|
+
"suggest": Array [],
|
|
137
|
+
},
|
|
138
|
+
]
|
|
139
|
+
`);
|
|
140
|
+
});
|
|
62
141
|
});
|
|
@@ -70,13 +70,13 @@ describe('Oas3 assertions', () => {
|
|
|
70
70
|
},
|
|
71
71
|
];
|
|
72
72
|
|
|
73
|
-
const visitors = buildVisitorObject('
|
|
73
|
+
const visitors = buildVisitorObject('MediaTypesMap', context, () => {}) as any;
|
|
74
74
|
|
|
75
75
|
expect(visitors).toMatchInlineSnapshot(`
|
|
76
76
|
Object {
|
|
77
77
|
"Operation": Object {
|
|
78
78
|
"ResponsesMap": Object {
|
|
79
|
-
"
|
|
79
|
+
"MediaTypesMap": [Function],
|
|
80
80
|
"skip": [Function],
|
|
81
81
|
},
|
|
82
82
|
"skip": [Function],
|
|
@@ -5,7 +5,7 @@ import { Oas2Paths } from '../../typings/swagger';
|
|
|
5
5
|
|
|
6
6
|
export const NoAmbiguousPaths: Oas3Rule | Oas2Rule = () => {
|
|
7
7
|
return {
|
|
8
|
-
|
|
8
|
+
PathsMap(pathMap: Oas3Paths | Oas2Paths, { report, location }: UserContext) {
|
|
9
9
|
const seenPaths: string[] = [];
|
|
10
10
|
|
|
11
11
|
for (const currentPath of Object.keys(pathMap)) {
|
|
@@ -5,7 +5,7 @@ import { Oas2Paths } from '../../typings/swagger';
|
|
|
5
5
|
|
|
6
6
|
export const NoIdenticalPaths: Oas3Rule | Oas2Rule = () => {
|
|
7
7
|
return {
|
|
8
|
-
|
|
8
|
+
PathsMap(pathMap: Oas3Paths | Oas2Paths, { report, location }: UserContext) {
|
|
9
9
|
const pathsMap = new Map<string, string>();
|
|
10
10
|
for (const pathName of Object.keys(pathMap)) {
|
|
11
11
|
const id = pathName.replace(/{.+?}/g, '{VARIABLE}');
|
|
@@ -7,7 +7,7 @@ export const Operation2xxResponse: Oas3Rule | Oas2Rule = () => {
|
|
|
7
7
|
const codes = Object.keys(responses);
|
|
8
8
|
if (!codes.some((code) => code === 'default' || /2[Xx0-9]{2}/.test(code))) {
|
|
9
9
|
report({
|
|
10
|
-
message: 'Operation must have at least one `
|
|
10
|
+
message: 'Operation must have at least one `2XX` response.',
|
|
11
11
|
location: { reportOnKey: true },
|
|
12
12
|
});
|
|
13
13
|
}
|
|
@@ -8,7 +8,7 @@ export const Operation4xxResponse: Oas3Rule | Oas2Rule = () => {
|
|
|
8
8
|
|
|
9
9
|
if (!codes.some((code) => /4[Xx0-9]{2}/.test(code))) {
|
|
10
10
|
report({
|
|
11
|
-
message: 'Operation must have at least one `
|
|
11
|
+
message: 'Operation must have at least one `4XX` response.',
|
|
12
12
|
location: { reportOnKey: true },
|
|
13
13
|
});
|
|
14
14
|
}
|
|
@@ -6,7 +6,7 @@ import { Oas3Operation } from '../../typings/openapi';
|
|
|
6
6
|
|
|
7
7
|
export const OperationOperationId: Oas3Rule | Oas2Rule = () => {
|
|
8
8
|
return {
|
|
9
|
-
|
|
9
|
+
Root: {
|
|
10
10
|
PathItem: {
|
|
11
11
|
Operation(operation: Oas2Operation | Oas3Operation, ctx: UserContext) {
|
|
12
12
|
validateDefinedAndNonEmpty('operationId', operation, ctx);
|
|
@@ -7,7 +7,7 @@ export const OperationTagDefined: Oas3Rule | Oas2Rule = () => {
|
|
|
7
7
|
let definedTags: Set<string>;
|
|
8
8
|
|
|
9
9
|
return {
|
|
10
|
-
|
|
10
|
+
Root(root: Oas2Definition | Oas3Definition) {
|
|
11
11
|
definedTags = new Set((root.tags ?? []).map((t) => t.name));
|
|
12
12
|
},
|
|
13
13
|
Operation(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Oas3Rule, Oas2Rule } from '../../visitors';
|
|
2
2
|
import { Location } from '../../ref-utils';
|
|
3
3
|
import { UserContext } from '../../walk';
|
|
4
|
-
import { Oas2SecurityScheme } from '../../typings/swagger';
|
|
5
|
-
import { Oas3SecurityScheme } from '../../typings/openapi';
|
|
4
|
+
import { Oas2Definition, Oas2Operation, Oas2SecurityScheme } from '../../typings/swagger';
|
|
5
|
+
import { Oas3Definition, Oas3Operation, Oas3SecurityScheme } from '../../typings/openapi';
|
|
6
6
|
|
|
7
|
-
export const
|
|
7
|
+
export const SecurityDefined: Oas3Rule | Oas2Rule = () => {
|
|
8
8
|
const referencedSchemes = new Map<
|
|
9
9
|
string,
|
|
10
10
|
{
|
|
@@ -13,9 +13,11 @@ export const OperationSecurityDefined: Oas3Rule | Oas2Rule = () => {
|
|
|
13
13
|
}
|
|
14
14
|
>();
|
|
15
15
|
|
|
16
|
+
let eachOperationHasSecurity: boolean = true;
|
|
17
|
+
|
|
16
18
|
return {
|
|
17
19
|
DefinitionRoot: {
|
|
18
|
-
leave(
|
|
20
|
+
leave(root: Oas2Definition | Oas3Definition, { report }: UserContext) {
|
|
19
21
|
for (const [name, scheme] of referencedSchemes.entries()) {
|
|
20
22
|
if (scheme.defined) continue;
|
|
21
23
|
for (const reportedFromLocation of scheme.from) {
|
|
@@ -25,6 +27,14 @@ export const OperationSecurityDefined: Oas3Rule | Oas2Rule = () => {
|
|
|
25
27
|
});
|
|
26
28
|
}
|
|
27
29
|
}
|
|
30
|
+
|
|
31
|
+
if (root.security || eachOperationHasSecurity) {
|
|
32
|
+
return;
|
|
33
|
+
} else {
|
|
34
|
+
report({
|
|
35
|
+
message: `Every API should have security defined on the root level or for each operation.`,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
28
38
|
},
|
|
29
39
|
},
|
|
30
40
|
SecurityScheme(_securityScheme: Oas2SecurityScheme | Oas3SecurityScheme, { key }: UserContext) {
|
|
@@ -41,5 +51,10 @@ export const OperationSecurityDefined: Oas3Rule | Oas2Rule = () => {
|
|
|
41
51
|
}
|
|
42
52
|
}
|
|
43
53
|
},
|
|
54
|
+
Operation(operation: Oas2Operation | Oas3Operation) {
|
|
55
|
+
if (!operation?.security) {
|
|
56
|
+
eachOperationHasSecurity = false;
|
|
57
|
+
}
|
|
58
|
+
},
|
|
44
59
|
};
|
|
45
60
|
};
|
package/src/rules/common/spec.ts
CHANGED
|
@@ -6,13 +6,18 @@ import { isPlainObject } from '../../utils';
|
|
|
6
6
|
|
|
7
7
|
export const OasSpec: Oas3Rule | Oas2Rule = () => {
|
|
8
8
|
return {
|
|
9
|
-
any(
|
|
9
|
+
any(
|
|
10
|
+
node: any,
|
|
11
|
+
{ report, type, location, rawLocation, key, resolve, ignoreNextVisitorsOnNode }
|
|
12
|
+
) {
|
|
10
13
|
const nodeType = oasTypeOf(node);
|
|
14
|
+
const refLocation = rawLocation !== location ? rawLocation : undefined;
|
|
11
15
|
|
|
12
16
|
if (type.items) {
|
|
13
17
|
if (nodeType !== 'array') {
|
|
14
18
|
report({
|
|
15
19
|
message: `Expected type \`${type.name}\` (array) but got \`${nodeType}\``,
|
|
20
|
+
from: refLocation,
|
|
16
21
|
});
|
|
17
22
|
ignoreNextVisitorsOnNode();
|
|
18
23
|
}
|
|
@@ -20,6 +25,7 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
|
|
|
20
25
|
} else if (nodeType !== 'object') {
|
|
21
26
|
report({
|
|
22
27
|
message: `Expected type \`${type.name}\` (object) but got \`${nodeType}\``,
|
|
28
|
+
from: refLocation,
|
|
23
29
|
});
|
|
24
30
|
ignoreNextVisitorsOnNode();
|
|
25
31
|
return;
|
|
@@ -32,6 +38,7 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
|
|
|
32
38
|
if (!(node as object).hasOwnProperty(propName)) {
|
|
33
39
|
report({
|
|
34
40
|
message: `The field \`${propName}\` must be present on this level.`,
|
|
41
|
+
from: refLocation,
|
|
35
42
|
location: [{ reportOnKey: true }],
|
|
36
43
|
});
|
|
37
44
|
}
|
|
@@ -49,6 +56,7 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
|
|
|
49
56
|
}
|
|
50
57
|
report({
|
|
51
58
|
message: `The field \`${propName}\` is not allowed here.`,
|
|
59
|
+
from: refLocation,
|
|
52
60
|
location: location.child([propName]).key(),
|
|
53
61
|
});
|
|
54
62
|
}
|
|
@@ -67,6 +75,7 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
|
|
|
67
75
|
message: `Must contain at least one of the following fields: ${type.requiredOneOf?.join(
|
|
68
76
|
', '
|
|
69
77
|
)}.`,
|
|
78
|
+
from: refLocation,
|
|
70
79
|
location: [{ reportOnKey: true }],
|
|
71
80
|
});
|
|
72
81
|
}
|
|
@@ -91,6 +100,7 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
|
|
|
91
100
|
report({
|
|
92
101
|
message: `Property \`${propName}\` is not expected here.`,
|
|
93
102
|
suggest: getSuggest(propName, Object.keys(type.properties)),
|
|
103
|
+
from: refLocation,
|
|
94
104
|
location: propLocation.key(),
|
|
95
105
|
});
|
|
96
106
|
continue;
|
|
@@ -111,12 +121,14 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
|
|
|
111
121
|
message: `\`${propName}\` can be one of the following only: ${propSchema.enum
|
|
112
122
|
.map((i) => `"${i}"`)
|
|
113
123
|
.join(', ')}.`,
|
|
124
|
+
from: refLocation,
|
|
114
125
|
suggest: getSuggest(propValue, propSchema.enum),
|
|
115
126
|
});
|
|
116
127
|
}
|
|
117
128
|
} else if (propSchema.type && !matchesJsonSchemaType(propValue, propSchema.type, false)) {
|
|
118
129
|
report({
|
|
119
130
|
message: `Expected type \`${propSchema.type}\` but got \`${propValueType}\`.`,
|
|
131
|
+
from: refLocation,
|
|
120
132
|
location: propLocation,
|
|
121
133
|
});
|
|
122
134
|
} else if (propValueType === 'array' && propSchema.items?.type) {
|
|
@@ -126,6 +138,7 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
|
|
|
126
138
|
if (!matchesJsonSchemaType(item, itemsType, false)) {
|
|
127
139
|
report({
|
|
128
140
|
message: `Expected type \`${itemsType}\` but got \`${oasTypeOf(item)}\`.`,
|
|
141
|
+
from: refLocation,
|
|
129
142
|
location: propLocation.child([i]),
|
|
130
143
|
});
|
|
131
144
|
}
|
|
@@ -136,6 +149,7 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
|
|
|
136
149
|
if (propSchema.minimum > node[propName]) {
|
|
137
150
|
report({
|
|
138
151
|
message: `The value of the ${propName} field must be greater than or equal to ${propSchema.minimum}`,
|
|
152
|
+
from: refLocation,
|
|
139
153
|
location: location.child([propName]),
|
|
140
154
|
});
|
|
141
155
|
}
|
|
@@ -5,7 +5,7 @@ import { UserContext } from '../../walk';
|
|
|
5
5
|
|
|
6
6
|
export const TagsAlphabetical: Oas3Rule | Oas2Rule = () => {
|
|
7
7
|
return {
|
|
8
|
-
|
|
8
|
+
Root(root: Oas2Definition | Oas3Definition, { report, location }: UserContext) {
|
|
9
9
|
if (!root.tags) return;
|
|
10
10
|
for (let i = 0; i < root.tags.length - 1; i++) {
|
|
11
11
|
if (root.tags[i].name > root.tags[i + 1].name) {
|
package/src/rules/oas2/index.ts
CHANGED
|
@@ -25,7 +25,7 @@ import { OperationDescription } from '../common/operation-description';
|
|
|
25
25
|
import { PathNotIncludeQuery } from '../common/path-not-include-query';
|
|
26
26
|
import { ParameterDescription } from '../common/parameter-description';
|
|
27
27
|
import { OperationSingularTag } from '../common/operation-singular-tag';
|
|
28
|
-
import {
|
|
28
|
+
import { SecurityDefined } from '../common/security-defined';
|
|
29
29
|
import { NoUnresolvedRefs } from '../no-unresolved-refs';
|
|
30
30
|
import { PathHttpVerbsOrder } from '../common/path-http-verbs-order';
|
|
31
31
|
import { NoIdenticalPaths } from '../common/no-identical-paths';
|
|
@@ -71,7 +71,7 @@ export const rules = {
|
|
|
71
71
|
'path-params-defined': PathParamsDefined as Oas2Rule,
|
|
72
72
|
'parameter-description': ParameterDescription as Oas2Rule,
|
|
73
73
|
'operation-singular-tag': OperationSingularTag as Oas2Rule,
|
|
74
|
-
'
|
|
74
|
+
'security-defined': SecurityDefined as Oas2Rule,
|
|
75
75
|
'no-unresolved-refs': NoUnresolvedRefs as Oas2Rule,
|
|
76
76
|
'no-identical-paths': NoIdenticalPaths as Oas2Rule,
|
|
77
77
|
'no-ambiguous-paths': NoAmbiguousPaths as Oas2Rule,
|
|
@@ -5,7 +5,7 @@ import { validateMimeType } from '../../utils';
|
|
|
5
5
|
|
|
6
6
|
export const RequestMimeType: Oas2Rule = ({ allowedValues }) => {
|
|
7
7
|
return {
|
|
8
|
-
|
|
8
|
+
Root(root: Oas2Definition, ctx: UserContext) {
|
|
9
9
|
validateMimeType({ type: 'consumes', value: root }, ctx, allowedValues);
|
|
10
10
|
},
|
|
11
11
|
Operation: {
|
|
@@ -5,7 +5,7 @@ import { validateMimeType } from '../../utils';
|
|
|
5
5
|
|
|
6
6
|
export const ResponseMimeType: Oas2Rule = ({ allowedValues }) => {
|
|
7
7
|
return {
|
|
8
|
-
|
|
8
|
+
Root(root: Oas2Definition, ctx: UserContext) {
|
|
9
9
|
validateMimeType({ type: 'produces', value: root }, ctx, allowedValues);
|
|
10
10
|
},
|
|
11
11
|
Operation: {
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { outdent } from 'outdent';
|
|
2
|
+
import { lintDocument } from '../../../lint';
|
|
3
|
+
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
|
|
4
|
+
import { BaseResolver } from '../../../resolve';
|
|
5
|
+
|
|
6
|
+
describe('Oas3 operation-4xx-problem-details-rfc7807', () => {
|
|
7
|
+
it('should report `4xx` must have content type `application/problem+json` ', async () => {
|
|
8
|
+
const document = parseYamlToDocument(
|
|
9
|
+
outdent`
|
|
10
|
+
openapi: "3.0.0"
|
|
11
|
+
paths:
|
|
12
|
+
/pets:
|
|
13
|
+
get:
|
|
14
|
+
summary: List all pets
|
|
15
|
+
operationId: listPets
|
|
16
|
+
responses:
|
|
17
|
+
'400':
|
|
18
|
+
description: Test
|
|
19
|
+
content:
|
|
20
|
+
application/json:
|
|
21
|
+
schema:
|
|
22
|
+
type: object
|
|
23
|
+
properties:
|
|
24
|
+
type:
|
|
25
|
+
type: string
|
|
26
|
+
title:
|
|
27
|
+
type: string
|
|
28
|
+
`,
|
|
29
|
+
'foobar.yaml'
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const results = await lintDocument({
|
|
33
|
+
externalRefResolver: new BaseResolver(),
|
|
34
|
+
document,
|
|
35
|
+
config: await makeConfig({ 'operation-4xx-problem-details-rfc7807': 'error' }),
|
|
36
|
+
});
|
|
37
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
38
|
+
Array [
|
|
39
|
+
Object {
|
|
40
|
+
"location": Array [
|
|
41
|
+
Object {
|
|
42
|
+
"pointer": "#/paths/~1pets/get/responses/400",
|
|
43
|
+
"reportOnKey": true,
|
|
44
|
+
"source": "foobar.yaml",
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
"message": "Response \`4xx\` must have content-type \`application/problem+json\`.",
|
|
48
|
+
"ruleId": "operation-4xx-problem-details-rfc7807",
|
|
49
|
+
"severity": "error",
|
|
50
|
+
"suggest": Array [],
|
|
51
|
+
},
|
|
52
|
+
]
|
|
53
|
+
`);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should report `application/problem+json` must have `type` property', async () => {
|
|
57
|
+
const document = parseYamlToDocument(
|
|
58
|
+
outdent`
|
|
59
|
+
openapi: "3.0.0"
|
|
60
|
+
paths:
|
|
61
|
+
/pets:
|
|
62
|
+
get:
|
|
63
|
+
summary: List all pets
|
|
64
|
+
operationId: listPets
|
|
65
|
+
responses:
|
|
66
|
+
'400':
|
|
67
|
+
description: Test
|
|
68
|
+
content:
|
|
69
|
+
application/problem+json:
|
|
70
|
+
schema:
|
|
71
|
+
type: object
|
|
72
|
+
properties:
|
|
73
|
+
title:
|
|
74
|
+
type: string
|
|
75
|
+
`,
|
|
76
|
+
'foobar.yaml'
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const results = await lintDocument({
|
|
80
|
+
externalRefResolver: new BaseResolver(),
|
|
81
|
+
document,
|
|
82
|
+
config: await makeConfig({ 'operation-4xx-problem-details-rfc7807': 'error' }),
|
|
83
|
+
});
|
|
84
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
85
|
+
Array [
|
|
86
|
+
Object {
|
|
87
|
+
"location": Array [
|
|
88
|
+
Object {
|
|
89
|
+
"pointer": "#/paths/~1pets/get/responses/400/content/application~1problem+json/schema/properties/type",
|
|
90
|
+
"reportOnKey": true,
|
|
91
|
+
"source": "foobar.yaml",
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
"message": "SchemaProperties object should contain \`type\` field.",
|
|
95
|
+
"ruleId": "operation-4xx-problem-details-rfc7807",
|
|
96
|
+
"severity": "error",
|
|
97
|
+
"suggest": Array [],
|
|
98
|
+
},
|
|
99
|
+
]
|
|
100
|
+
`);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should report `application/problem+json` must have `schema` property', async () => {
|
|
104
|
+
const document = parseYamlToDocument(
|
|
105
|
+
outdent`
|
|
106
|
+
openapi: "3.0.0"
|
|
107
|
+
paths:
|
|
108
|
+
/pets:
|
|
109
|
+
get:
|
|
110
|
+
summary: List all pets
|
|
111
|
+
operationId: listPets
|
|
112
|
+
responses:
|
|
113
|
+
'400':
|
|
114
|
+
description: Test
|
|
115
|
+
content:
|
|
116
|
+
application/problem+json:
|
|
117
|
+
example: asd
|
|
118
|
+
`,
|
|
119
|
+
'foobar.yaml'
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const results = await lintDocument({
|
|
123
|
+
externalRefResolver: new BaseResolver(),
|
|
124
|
+
document,
|
|
125
|
+
config: await makeConfig({ 'operation-4xx-problem-details-rfc7807': 'error' }),
|
|
126
|
+
});
|
|
127
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
128
|
+
Array [
|
|
129
|
+
Object {
|
|
130
|
+
"location": Array [
|
|
131
|
+
Object {
|
|
132
|
+
"pointer": "#/paths/~1pets/get/responses/400/content/application~1problem+json/schema",
|
|
133
|
+
"reportOnKey": true,
|
|
134
|
+
"source": "foobar.yaml",
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
"message": "MediaType object should contain \`schema\` field.",
|
|
138
|
+
"ruleId": "operation-4xx-problem-details-rfc7807",
|
|
139
|
+
"severity": "error",
|
|
140
|
+
"suggest": Array [],
|
|
141
|
+
},
|
|
142
|
+
]
|
|
143
|
+
`);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -47,6 +47,7 @@ describe('Oas3 Structural visitor basic', () => {
|
|
|
47
47
|
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
48
48
|
Array [
|
|
49
49
|
Object {
|
|
50
|
+
"from": undefined,
|
|
50
51
|
"location": Array [
|
|
51
52
|
Object {
|
|
52
53
|
"pointer": "#/info/contact/url",
|
|
@@ -60,6 +61,7 @@ describe('Oas3 Structural visitor basic', () => {
|
|
|
60
61
|
"suggest": Array [],
|
|
61
62
|
},
|
|
62
63
|
Object {
|
|
64
|
+
"from": undefined,
|
|
63
65
|
"location": Array [
|
|
64
66
|
Object {
|
|
65
67
|
"pointer": "#/info/contact/email",
|
|
@@ -73,6 +75,7 @@ describe('Oas3 Structural visitor basic', () => {
|
|
|
73
75
|
"suggest": Array [],
|
|
74
76
|
},
|
|
75
77
|
Object {
|
|
78
|
+
"from": undefined,
|
|
76
79
|
"location": Array [
|
|
77
80
|
Object {
|
|
78
81
|
"pointer": "#/info/license",
|
|
@@ -103,6 +106,7 @@ describe('Oas3 Structural visitor basic', () => {
|
|
|
103
106
|
"suggest": Array [],
|
|
104
107
|
},
|
|
105
108
|
Object {
|
|
109
|
+
"from": undefined,
|
|
106
110
|
"location": Array [
|
|
107
111
|
Object {
|
|
108
112
|
"pointer": "#/servers/0/variables/a/enum/0",
|
|
@@ -116,6 +120,7 @@ describe('Oas3 Structural visitor basic', () => {
|
|
|
116
120
|
"suggest": Array [],
|
|
117
121
|
},
|
|
118
122
|
Object {
|
|
123
|
+
"from": undefined,
|
|
119
124
|
"location": Array [
|
|
120
125
|
Object {
|
|
121
126
|
"pointer": "#/tags/0",
|
|
@@ -129,6 +134,7 @@ describe('Oas3 Structural visitor basic', () => {
|
|
|
129
134
|
"suggest": Array [],
|
|
130
135
|
},
|
|
131
136
|
Object {
|
|
137
|
+
"from": undefined,
|
|
132
138
|
"location": Array [
|
|
133
139
|
Object {
|
|
134
140
|
"pointer": "#/tags/1",
|
|
@@ -175,6 +181,7 @@ describe('Oas3 Structural visitor basic', () => {
|
|
|
175
181
|
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
176
182
|
Array [
|
|
177
183
|
Object {
|
|
184
|
+
"from": undefined,
|
|
178
185
|
"location": Array [
|
|
179
186
|
Object {
|
|
180
187
|
"pointer": "#/components1",
|
|
@@ -203,6 +210,7 @@ describe('Oas3 Structural visitor basic', () => {
|
|
|
203
210
|
"suggest": Array [],
|
|
204
211
|
},
|
|
205
212
|
Object {
|
|
213
|
+
"from": undefined,
|
|
206
214
|
"location": Array [
|
|
207
215
|
Object {
|
|
208
216
|
"pointer": "#/info/contact/test",
|
|
@@ -244,6 +252,7 @@ describe('Oas3 Structural visitor basic', () => {
|
|
|
244
252
|
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
245
253
|
Array [
|
|
246
254
|
Object {
|
|
255
|
+
"from": undefined,
|
|
247
256
|
"location": Array [
|
|
248
257
|
Object {
|
|
249
258
|
"pointer": "#/",
|
|
@@ -270,6 +279,7 @@ describe('Oas3 Structural visitor basic', () => {
|
|
|
270
279
|
"suggest": Array [],
|
|
271
280
|
},
|
|
272
281
|
Object {
|
|
282
|
+
"from": undefined,
|
|
273
283
|
"location": Array [
|
|
274
284
|
Object {
|
|
275
285
|
"pointer": "#/info",
|