@redocly/openapi-core 1.0.0-beta.106 → 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/lint-with-top-level-rule-report.bench.js +0 -1
- package/lib/benchmark/benches/resolve-with-no-external.bench.js +1 -1
- package/lib/bundle.d.ts +1 -1
- package/lib/bundle.js +9 -6
- package/lib/config/all.js +5 -3
- package/lib/config/config-resolvers.js +32 -14
- package/lib/config/config.d.ts +3 -5
- package/lib/config/config.js +7 -4
- package/lib/config/load.d.ts +7 -0
- package/lib/config/load.js +14 -6
- package/lib/config/minimal.js +7 -4
- package/lib/config/recommended.js +7 -4
- package/lib/config/rules.d.ts +1 -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 +49 -11
- package/lib/decorators/common/registry-dependencies.js +2 -2
- package/lib/env.d.ts +3 -0
- package/lib/env.js +8 -0
- package/lib/format/codeframes.js +16 -10
- package/lib/format/format.d.ts +1 -1
- package/lib/format/format.js +49 -26
- package/lib/index.d.ts +5 -5
- package/lib/index.js +3 -1
- package/lib/js-yaml/index.js +1 -0
- package/lib/lint.js +2 -2
- package/lib/logger.d.ts +10 -0
- package/lib/logger.js +31 -0
- package/lib/output.d.ts +3 -0
- package/lib/output.js +9 -0
- package/lib/redocly/index.js +10 -9
- package/lib/redocly/registry-api-types.d.ts +28 -30
- package/lib/redocly/registry-api.d.ts +3 -3
- package/lib/redocly/registry-api.js +7 -1
- package/lib/ref-utils.js +2 -1
- package/lib/resolve.d.ts +1 -1
- package/lib/resolve.js +4 -2
- package/lib/rules/ajv.d.ts +1 -1
- package/lib/rules/ajv.js +7 -7
- package/lib/rules/common/assertions/asserts.js +4 -4
- package/lib/rules/common/assertions/index.js +1 -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/no-invalid-parameter-examples.js +3 -3
- package/lib/rules/common/no-invalid-schema-examples.js +3 -3
- 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} +19 -5
- package/lib/rules/common/spec.js +14 -3
- 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 +3 -3
- 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 +8 -4
- package/lib/rules/oas3/no-empty-servers.js +1 -1
- package/lib/rules/oas3/no-invalid-media-type-examples.js +2 -2
- package/lib/rules/oas3/no-server-variables-empty-enum.d.ts +2 -0
- package/lib/rules/oas3/{no-servers-empty-enum.js → no-server-variables-empty-enum.js} +5 -5
- package/lib/rules/oas3/no-unused-components.js +2 -2
- 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 +4 -4
- 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/rules/utils.d.ts +3 -2
- package/lib/rules/utils.js +16 -4
- 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 +47 -56
- package/lib/utils.d.ts +6 -1
- package/lib/utils.js +24 -7
- package/lib/visitors.d.ts +12 -12
- package/lib/visitors.js +15 -3
- package/lib/walk.d.ts +2 -1
- package/lib/walk.js +6 -3
- package/package.json +2 -2
- 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__/logger-browser.test.ts +53 -0
- package/src/__tests__/logger.test.ts +47 -0
- package/src/__tests__/output-browser.test.ts +18 -0
- package/src/__tests__/output.test.ts +15 -0
- package/src/__tests__/resolve-http.test.ts +1 -1
- package/src/__tests__/resolve.test.ts +9 -9
- package/src/__tests__/utils-browser.test.ts +11 -0
- package/src/__tests__/utils.test.ts +7 -0
- package/src/__tests__/walk.test.ts +78 -10
- package/src/benchmark/benches/lint-with-top-level-rule-report.bench.ts +0 -1
- package/src/benchmark/benches/resolve-with-no-external.bench.ts +1 -1
- package/src/bundle.ts +10 -7
- package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +12 -6
- package/src/config/__tests__/config.test.ts +35 -0
- 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 +76 -1
- package/src/config/__tests__/utils.test.ts +64 -4
- package/src/config/all.ts +5 -3
- package/src/config/config-resolvers.ts +45 -19
- package/src/config/config.ts +10 -8
- package/src/config/load.ts +31 -7
- package/src/config/minimal.ts +7 -4
- package/src/config/recommended.ts +7 -4
- package/src/config/rules.ts +2 -2
- package/src/config/types.ts +11 -0
- package/src/config/utils.ts +115 -25
- package/src/decorators/common/registry-dependencies.ts +2 -2
- package/src/env.ts +5 -0
- package/src/format/codeframes.ts +15 -9
- package/src/format/format.ts +59 -34
- package/src/index.ts +6 -4
- package/src/js-yaml/index.ts +1 -0
- package/src/lint.ts +2 -2
- package/src/logger.ts +34 -0
- package/src/output.ts +7 -0
- package/src/redocly/index.ts +7 -4
- package/src/redocly/registry-api-types.ts +27 -29
- package/src/redocly/registry-api.ts +18 -7
- package/src/ref-utils.ts +2 -1
- package/src/resolve.ts +7 -5
- package/src/rules/__tests__/utils.test.ts +39 -1
- package/src/rules/ajv.ts +7 -7
- 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/assertions/asserts.ts +4 -4
- package/src/rules/common/assertions/index.ts +1 -1
- package/src/rules/common/no-ambiguous-paths.ts +1 -1
- package/src/rules/common/no-identical-paths.ts +1 -1
- package/src/rules/common/no-invalid-parameter-examples.ts +4 -4
- package/src/rules/common/no-invalid-schema-examples.ts +4 -4
- 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} +20 -5
- package/src/rules/common/spec.ts +17 -3
- 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 +3 -3
- 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__/no-empty-enum-servers.com.test.ts +16 -16
- package/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts +5 -5
- 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 +8 -4
- package/src/rules/oas3/no-empty-servers.ts +1 -1
- package/src/rules/oas3/no-invalid-media-type-examples.ts +3 -3
- package/src/rules/oas3/{no-servers-empty-enum.ts → no-server-variables-empty-enum.ts} +3 -3
- package/src/rules/oas3/no-unused-components.ts +2 -2
- package/src/rules/oas3/operation-4xx-problem-details-rfc7807.ts +36 -0
- package/src/rules/oas3/remove-unused-components.ts +5 -5
- 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/rules/utils.ts +17 -3
- 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 +53 -41
- package/src/utils.ts +31 -4
- package/src/visitors.ts +34 -18
- package/src/walk.ts +15 -11
- package/tsconfig.tsbuildinfo +1 -1
- package/lib/rules/common/operation-security-defined.d.ts +0 -2
- package/lib/rules/oas3/no-servers-empty-enum.d.ts +0 -2
- package/src/rules/common/__tests__/operation-security-defined.test.ts +0 -69
package/src/rules/ajv.ts
CHANGED
|
@@ -8,7 +8,7 @@ export function releaseAjvInstance() {
|
|
|
8
8
|
ajvInstance = null;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function getAjv(resolve: ResolveFn,
|
|
11
|
+
function getAjv(resolve: ResolveFn, allowAdditionalProperties: boolean) {
|
|
12
12
|
if (!ajvInstance) {
|
|
13
13
|
ajvInstance = new Ajv({
|
|
14
14
|
schemaId: '$id',
|
|
@@ -20,7 +20,7 @@ function getAjv(resolve: ResolveFn, disallowAdditionalProperties: boolean) {
|
|
|
20
20
|
discriminator: true,
|
|
21
21
|
allowUnionTypes: true,
|
|
22
22
|
validateFormats: false, // TODO: fix it
|
|
23
|
-
defaultAdditionalProperties:
|
|
23
|
+
defaultAdditionalProperties: allowAdditionalProperties,
|
|
24
24
|
loadSchemaSync(base: string, $ref: string) {
|
|
25
25
|
const resolvedRef = resolve({ $ref }, base.split('#')[0]);
|
|
26
26
|
if (!resolvedRef || !resolvedRef.location) return false;
|
|
@@ -36,9 +36,9 @@ function getAjvValidator(
|
|
|
36
36
|
schema: any,
|
|
37
37
|
loc: Location,
|
|
38
38
|
resolve: ResolveFn,
|
|
39
|
-
|
|
39
|
+
allowAdditionalProperties: boolean
|
|
40
40
|
): ValidateFunction | undefined {
|
|
41
|
-
const ajv = getAjv(resolve,
|
|
41
|
+
const ajv = getAjv(resolve, allowAdditionalProperties);
|
|
42
42
|
|
|
43
43
|
if (!ajv.getSchema(loc.absolutePointer)) {
|
|
44
44
|
ajv.addSchema({ $id: loc.absolutePointer, ...schema }, loc.absolutePointer);
|
|
@@ -53,9 +53,9 @@ export function validateJsonSchema(
|
|
|
53
53
|
schemaLoc: Location,
|
|
54
54
|
instancePath: string,
|
|
55
55
|
resolve: ResolveFn,
|
|
56
|
-
|
|
56
|
+
allowAdditionalProperties: boolean
|
|
57
57
|
): { valid: boolean; errors: (ErrorObject & { suggest?: string[] })[] } {
|
|
58
|
-
const validate = getAjvValidator(schema, schemaLoc, resolve,
|
|
58
|
+
const validate = getAjvValidator(schema, schemaLoc, resolve, allowAdditionalProperties);
|
|
59
59
|
if (!validate) return { valid: true, errors: [] }; // unresolved refs are reported
|
|
60
60
|
|
|
61
61
|
const valid = validate(data, {
|
|
@@ -73,7 +73,7 @@ export function validateJsonSchema(
|
|
|
73
73
|
|
|
74
74
|
function beatifyErrorMessage(error: ErrorObject) {
|
|
75
75
|
let message = error.message;
|
|
76
|
-
|
|
76
|
+
const suggest = error.keyword === 'enum' ? error.params.allowedValues : undefined;
|
|
77
77
|
if (suggest) {
|
|
78
78
|
message += ` ${suggest.map((e: any) => `"${e}"`).join(', ')}`;
|
|
79
79
|
}
|
|
@@ -191,6 +191,7 @@ describe('Oas3 typed enum', () => {
|
|
|
191
191
|
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
192
192
|
Array [
|
|
193
193
|
Object {
|
|
194
|
+
"from": undefined,
|
|
194
195
|
"location": Array [
|
|
195
196
|
Object {
|
|
196
197
|
"pointer": "#/paths/~1some/get/responses/200/content/application~1json/schema",
|
|
@@ -34,7 +34,7 @@ describe('Oas3 operation-2xx-response', () => {
|
|
|
34
34
|
"source": "foobar.yaml",
|
|
35
35
|
},
|
|
36
36
|
],
|
|
37
|
-
"message": "Operation must have at least one \`
|
|
37
|
+
"message": "Operation must have at least one \`2XX\` response.",
|
|
38
38
|
"ruleId": "operation-2xx-response",
|
|
39
39
|
"severity": "error",
|
|
40
40
|
"suggest": Array [],
|
|
@@ -34,7 +34,7 @@ describe('Oas3 operation-4xx-response', () => {
|
|
|
34
34
|
"source": "foobar.yaml",
|
|
35
35
|
},
|
|
36
36
|
],
|
|
37
|
-
"message": "Operation must have at least one \`
|
|
37
|
+
"message": "Operation must have at least one \`4XX\` response.",
|
|
38
38
|
"ruleId": "operation-4xx-response",
|
|
39
39
|
"severity": "error",
|
|
40
40
|
"suggest": Array [],
|
|
@@ -43,7 +43,7 @@ describe('Oas3 operation-4xx-response', () => {
|
|
|
43
43
|
`);
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
it('should not report for present
|
|
46
|
+
it('should not report for present 400 response', async () => {
|
|
47
47
|
const document = parseYamlToDocument(
|
|
48
48
|
outdent`
|
|
49
49
|
openapi: 3.0.0
|
|
@@ -66,6 +66,29 @@ describe('Oas3 operation-4xx-response', () => {
|
|
|
66
66
|
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
+
it('should not report for present 4XX response', async () => {
|
|
70
|
+
const document = parseYamlToDocument(
|
|
71
|
+
outdent`
|
|
72
|
+
openapi: 3.0.0
|
|
73
|
+
paths:
|
|
74
|
+
'/test/':
|
|
75
|
+
put:
|
|
76
|
+
responses:
|
|
77
|
+
4XX:
|
|
78
|
+
description: error response
|
|
79
|
+
`,
|
|
80
|
+
'foobar.yaml'
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const results = await lintDocument({
|
|
84
|
+
externalRefResolver: new BaseResolver(),
|
|
85
|
+
document,
|
|
86
|
+
config: await makeConfig({ 'operation-4xx-response': 'error' }),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
|
|
90
|
+
});
|
|
91
|
+
|
|
69
92
|
it('should report if default is present but missing 4xx response', async () => {
|
|
70
93
|
const document = parseYamlToDocument(
|
|
71
94
|
outdent`
|
|
@@ -96,7 +119,7 @@ describe('Oas3 operation-4xx-response', () => {
|
|
|
96
119
|
"source": "foobar.yaml",
|
|
97
120
|
},
|
|
98
121
|
],
|
|
99
|
-
"message": "Operation must have at least one \`
|
|
122
|
+
"message": "Operation must have at least one \`4XX\` response.",
|
|
100
123
|
"ruleId": "operation-4xx-response",
|
|
101
124
|
"severity": "error",
|
|
102
125
|
"suggest": Array [],
|
|
@@ -0,0 +1,175 @@
|
|
|
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 security-defined', () => {
|
|
7
|
+
it('should report on securityRequirements object if security scheme is not defined in components', async () => {
|
|
8
|
+
const document = parseYamlToDocument(
|
|
9
|
+
outdent`
|
|
10
|
+
openapi: 3.0.0
|
|
11
|
+
paths:
|
|
12
|
+
/pets:
|
|
13
|
+
get:
|
|
14
|
+
security:
|
|
15
|
+
- some: []`,
|
|
16
|
+
'foobar.yaml'
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const results = await lintDocument({
|
|
20
|
+
externalRefResolver: new BaseResolver(),
|
|
21
|
+
document,
|
|
22
|
+
config: await makeConfig({ 'security-defined': 'error' }),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
26
|
+
Array [
|
|
27
|
+
Object {
|
|
28
|
+
"location": Array [
|
|
29
|
+
Object {
|
|
30
|
+
"pointer": "#/paths/~1pets/get/security/0/some",
|
|
31
|
+
"reportOnKey": true,
|
|
32
|
+
"source": "foobar.yaml",
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
"message": "There is no \`some\` security scheme defined.",
|
|
36
|
+
"ruleId": "security-defined",
|
|
37
|
+
"severity": "error",
|
|
38
|
+
"suggest": Array [],
|
|
39
|
+
},
|
|
40
|
+
]
|
|
41
|
+
`);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should not report if security defined with an empty array', async () => {
|
|
45
|
+
const document = parseYamlToDocument(
|
|
46
|
+
outdent`
|
|
47
|
+
openapi: 3.0.0
|
|
48
|
+
security: []
|
|
49
|
+
paths:`,
|
|
50
|
+
'foobar.yaml'
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const results = await lintDocument({
|
|
54
|
+
externalRefResolver: new BaseResolver(),
|
|
55
|
+
document,
|
|
56
|
+
config: await makeConfig({ 'security-defined': 'error' }),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should report if security not defined at all', async () => {
|
|
63
|
+
const document = parseYamlToDocument(
|
|
64
|
+
outdent`
|
|
65
|
+
openapi: 3.0.0
|
|
66
|
+
paths:
|
|
67
|
+
/pets:
|
|
68
|
+
get:
|
|
69
|
+
requestBody:`,
|
|
70
|
+
'foobar.yaml'
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const results = await lintDocument({
|
|
74
|
+
externalRefResolver: new BaseResolver(),
|
|
75
|
+
document,
|
|
76
|
+
config: await makeConfig({ 'security-defined': 'error' }),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
80
|
+
Array [
|
|
81
|
+
Object {
|
|
82
|
+
"location": Array [
|
|
83
|
+
Object {
|
|
84
|
+
"pointer": "#/",
|
|
85
|
+
"reportOnKey": false,
|
|
86
|
+
"source": "foobar.yaml",
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
"message": "Every API should have security defined on the root level or for each operation.",
|
|
90
|
+
"ruleId": "security-defined",
|
|
91
|
+
"severity": "error",
|
|
92
|
+
"suggest": Array [],
|
|
93
|
+
},
|
|
94
|
+
]
|
|
95
|
+
`);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should report if security not defined for each operation', async () => {
|
|
99
|
+
const document = parseYamlToDocument(
|
|
100
|
+
outdent`
|
|
101
|
+
openapi: 3.0.0
|
|
102
|
+
paths:
|
|
103
|
+
/pets:
|
|
104
|
+
get:
|
|
105
|
+
security:
|
|
106
|
+
- some: []
|
|
107
|
+
/cats:
|
|
108
|
+
get:`,
|
|
109
|
+
'foobar.yaml'
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const results = await lintDocument({
|
|
113
|
+
externalRefResolver: new BaseResolver(),
|
|
114
|
+
document,
|
|
115
|
+
config: await makeConfig({ 'security-defined': 'error' }),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
119
|
+
Array [
|
|
120
|
+
Object {
|
|
121
|
+
"location": Array [
|
|
122
|
+
Object {
|
|
123
|
+
"pointer": "#/paths/~1pets/get/security/0/some",
|
|
124
|
+
"reportOnKey": true,
|
|
125
|
+
"source": "foobar.yaml",
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
"message": "There is no \`some\` security scheme defined.",
|
|
129
|
+
"ruleId": "security-defined",
|
|
130
|
+
"severity": "error",
|
|
131
|
+
"suggest": Array [],
|
|
132
|
+
},
|
|
133
|
+
Object {
|
|
134
|
+
"location": Array [
|
|
135
|
+
Object {
|
|
136
|
+
"pointer": "#/",
|
|
137
|
+
"reportOnKey": false,
|
|
138
|
+
"source": "foobar.yaml",
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
"message": "Every API should have security defined on the root level or for each operation.",
|
|
142
|
+
"ruleId": "security-defined",
|
|
143
|
+
"severity": "error",
|
|
144
|
+
"suggest": Array [],
|
|
145
|
+
},
|
|
146
|
+
]
|
|
147
|
+
`);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should not report on securityRequirements object if security scheme is defined in components', async () => {
|
|
151
|
+
const document = parseYamlToDocument(
|
|
152
|
+
outdent`
|
|
153
|
+
openapi: 3.0.0
|
|
154
|
+
paths:
|
|
155
|
+
/pets:
|
|
156
|
+
get:
|
|
157
|
+
security:
|
|
158
|
+
some: []
|
|
159
|
+
components:
|
|
160
|
+
securitySchemes:
|
|
161
|
+
some:
|
|
162
|
+
type: http
|
|
163
|
+
scheme: basic`,
|
|
164
|
+
'foobar.yaml'
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const results = await lintDocument({
|
|
168
|
+
externalRefResolver: new BaseResolver(),
|
|
169
|
+
document,
|
|
170
|
+
config: await makeConfig({ 'security-defined': 'error' }),
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
@@ -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],
|
|
@@ -46,7 +46,7 @@ export const asserts: Asserts = {
|
|
|
46
46
|
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
|
47
47
|
const values = runOnValue(value) ? [value] : value;
|
|
48
48
|
const regx = regexFromString(condition);
|
|
49
|
-
for (
|
|
49
|
+
for (const _val of values) {
|
|
50
50
|
if (!regx?.test(_val)) {
|
|
51
51
|
return { isValid: false, location: runOnValue(value) ? baseLocation : baseLocation.key() };
|
|
52
52
|
}
|
|
@@ -56,7 +56,7 @@ export const asserts: Asserts = {
|
|
|
56
56
|
enum: (value: string | string[], condition: string[], baseLocation: Location) => {
|
|
57
57
|
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
|
58
58
|
const values = runOnValue(value) ? [value] : value;
|
|
59
|
-
for (
|
|
59
|
+
for (const _val of values) {
|
|
60
60
|
if (!condition.includes(_val)) {
|
|
61
61
|
return {
|
|
62
62
|
isValid: false,
|
|
@@ -81,7 +81,7 @@ export const asserts: Asserts = {
|
|
|
81
81
|
disallowed: (value: string | string[], condition: string[], baseLocation: Location) => {
|
|
82
82
|
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
|
83
83
|
const values = runOnValue(value) ? [value] : value;
|
|
84
|
-
for (
|
|
84
|
+
for (const _val of values) {
|
|
85
85
|
if (condition.includes(_val)) {
|
|
86
86
|
return {
|
|
87
87
|
isValid: false,
|
|
@@ -114,7 +114,7 @@ export const asserts: Asserts = {
|
|
|
114
114
|
casing: (value: string | string[], condition: string, baseLocation: Location) => {
|
|
115
115
|
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
|
116
116
|
const values: string[] = runOnValue(value) ? [value] : value;
|
|
117
|
-
for (
|
|
117
|
+
for (const _val of values) {
|
|
118
118
|
let matchCase = false;
|
|
119
119
|
switch (condition) {
|
|
120
120
|
case 'camelCase':
|
|
@@ -3,7 +3,7 @@ import { AssertToApply, buildSubjectVisitor, buildVisitorObject } from './utils'
|
|
|
3
3
|
import { Oas2Rule, Oas3Rule } from '../../../visitors';
|
|
4
4
|
|
|
5
5
|
export const Assertions: Oas3Rule | Oas2Rule = (opts: object) => {
|
|
6
|
-
|
|
6
|
+
const visitors: any[] = [];
|
|
7
7
|
|
|
8
8
|
// As 'Assertions' has an array of asserts,
|
|
9
9
|
// that array spreads into an 'opts' object on init rules phase here
|
|
@@ -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}');
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { UserContext } from '../../walk';
|
|
2
2
|
import { Oas3Parameter } from '../../typings/openapi';
|
|
3
|
-
import { validateExample } from '../utils';
|
|
3
|
+
import { getAdditionalPropertiesOption, validateExample } from '../utils';
|
|
4
4
|
|
|
5
5
|
export const NoInvalidParameterExamples: any = (opts: any) => {
|
|
6
|
-
const
|
|
6
|
+
const allowAdditionalProperties = getAdditionalPropertiesOption(opts) ?? false;
|
|
7
7
|
return {
|
|
8
8
|
Parameter: {
|
|
9
9
|
leave(parameter: Oas3Parameter, ctx: UserContext) {
|
|
@@ -13,7 +13,7 @@ export const NoInvalidParameterExamples: any = (opts: any) => {
|
|
|
13
13
|
parameter.schema!,
|
|
14
14
|
ctx.location.child('example'),
|
|
15
15
|
ctx,
|
|
16
|
-
|
|
16
|
+
allowAdditionalProperties
|
|
17
17
|
);
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -25,7 +25,7 @@ export const NoInvalidParameterExamples: any = (opts: any) => {
|
|
|
25
25
|
parameter.schema!,
|
|
26
26
|
ctx.location.child(['examples', key]),
|
|
27
27
|
ctx,
|
|
28
|
-
|
|
28
|
+
true
|
|
29
29
|
);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { UserContext } from '../../walk';
|
|
2
2
|
import { Oas3_1Schema } from '../../typings/openapi';
|
|
3
|
-
import { validateExample } from '../utils';
|
|
3
|
+
import { getAdditionalPropertiesOption, validateExample } from '../utils';
|
|
4
4
|
|
|
5
5
|
export const NoInvalidSchemaExamples: any = (opts: any) => {
|
|
6
|
-
const
|
|
6
|
+
const allowAdditionalProperties = getAdditionalPropertiesOption(opts) ?? false;
|
|
7
7
|
return {
|
|
8
8
|
Schema: {
|
|
9
9
|
leave(schema: Oas3_1Schema, ctx: UserContext) {
|
|
@@ -14,12 +14,12 @@ export const NoInvalidSchemaExamples: any = (opts: any) => {
|
|
|
14
14
|
schema,
|
|
15
15
|
ctx.location.child(['examples', schema.examples.indexOf(example)]),
|
|
16
16
|
ctx,
|
|
17
|
-
|
|
17
|
+
allowAdditionalProperties
|
|
18
18
|
);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
if (schema.example) {
|
|
22
|
-
validateExample(schema.example, schema, ctx.location.child('example'), ctx,
|
|
22
|
+
validateExample(schema.example, schema, ctx.location.child('example'), ctx, true);
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
},
|
|
@@ -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,11 +1,11 @@
|
|
|
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
|
|
8
|
-
|
|
7
|
+
export const SecurityDefined: Oas3Rule | Oas2Rule = () => {
|
|
8
|
+
const referencedSchemes = new Map<
|
|
9
9
|
string,
|
|
10
10
|
{
|
|
11
11
|
defined?: boolean;
|
|
@@ -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
|
};
|