@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
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { makeConfig, parseYamlToDocument } from '../../../../__tests__/utils';
|
|
2
|
+
import { outdent } from 'outdent';
|
|
3
|
+
import { lintDocument } from '../../../lint';
|
|
4
|
+
import { BaseResolver } from '../../../resolve';
|
|
5
|
+
|
|
6
|
+
describe('Oas3 spec-components-invalid-map-name', () => {
|
|
7
|
+
it('should report about invalid keys inside components', async () => {
|
|
8
|
+
const document = parseYamlToDocument(outdent`
|
|
9
|
+
openapi: 3.0.0
|
|
10
|
+
info:
|
|
11
|
+
version: 3.0.0
|
|
12
|
+
components:
|
|
13
|
+
parameters:
|
|
14
|
+
my Param:
|
|
15
|
+
name: param
|
|
16
|
+
description: param
|
|
17
|
+
in: path
|
|
18
|
+
examples:
|
|
19
|
+
invalid identifier:
|
|
20
|
+
description: 'Some description'
|
|
21
|
+
value: 21
|
|
22
|
+
responses:
|
|
23
|
+
400 status:
|
|
24
|
+
description: bad request
|
|
25
|
+
schemas:
|
|
26
|
+
first schema:
|
|
27
|
+
type: integer
|
|
28
|
+
format: int64
|
|
29
|
+
`);
|
|
30
|
+
const results = await lintDocument({
|
|
31
|
+
externalRefResolver: new BaseResolver(),
|
|
32
|
+
document,
|
|
33
|
+
config: await makeConfig({
|
|
34
|
+
'spec-components-invalid-map-name': 'error',
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(results).toMatchInlineSnapshot(`
|
|
39
|
+
Array [
|
|
40
|
+
Object {
|
|
41
|
+
"location": Array [
|
|
42
|
+
Object {
|
|
43
|
+
"pointer": "#/components/parameters/my Param",
|
|
44
|
+
"reportOnKey": true,
|
|
45
|
+
"source": Source {
|
|
46
|
+
"absoluteRef": "",
|
|
47
|
+
"body": "openapi: 3.0.0
|
|
48
|
+
info:
|
|
49
|
+
version: 3.0.0
|
|
50
|
+
components:
|
|
51
|
+
parameters:
|
|
52
|
+
my Param:
|
|
53
|
+
name: param
|
|
54
|
+
description: param
|
|
55
|
+
in: path
|
|
56
|
+
examples:
|
|
57
|
+
invalid identifier:
|
|
58
|
+
description: 'Some description'
|
|
59
|
+
value: 21
|
|
60
|
+
responses:
|
|
61
|
+
400 status:
|
|
62
|
+
description: bad request
|
|
63
|
+
schemas:
|
|
64
|
+
first schema:
|
|
65
|
+
type: integer
|
|
66
|
+
format: int64 ",
|
|
67
|
+
"mimeType": undefined,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
"message": "The map key in parameters \\"my Param\\" does not match the regular expression \\"^[a-zA-Z0-9\\\\.\\\\-_]+$\\"",
|
|
72
|
+
"ruleId": "spec-components-invalid-map-name",
|
|
73
|
+
"severity": "error",
|
|
74
|
+
"suggest": Array [],
|
|
75
|
+
},
|
|
76
|
+
Object {
|
|
77
|
+
"location": Array [
|
|
78
|
+
Object {
|
|
79
|
+
"pointer": "#/components/schemas/first schema",
|
|
80
|
+
"reportOnKey": true,
|
|
81
|
+
"source": Source {
|
|
82
|
+
"absoluteRef": "",
|
|
83
|
+
"body": "openapi: 3.0.0
|
|
84
|
+
info:
|
|
85
|
+
version: 3.0.0
|
|
86
|
+
components:
|
|
87
|
+
parameters:
|
|
88
|
+
my Param:
|
|
89
|
+
name: param
|
|
90
|
+
description: param
|
|
91
|
+
in: path
|
|
92
|
+
examples:
|
|
93
|
+
invalid identifier:
|
|
94
|
+
description: 'Some description'
|
|
95
|
+
value: 21
|
|
96
|
+
responses:
|
|
97
|
+
400 status:
|
|
98
|
+
description: bad request
|
|
99
|
+
schemas:
|
|
100
|
+
first schema:
|
|
101
|
+
type: integer
|
|
102
|
+
format: int64 ",
|
|
103
|
+
"mimeType": undefined,
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
"message": "The map key in schemas \\"first schema\\" does not match the regular expression \\"^[a-zA-Z0-9\\\\.\\\\-_]+$\\"",
|
|
108
|
+
"ruleId": "spec-components-invalid-map-name",
|
|
109
|
+
"severity": "error",
|
|
110
|
+
"suggest": Array [],
|
|
111
|
+
},
|
|
112
|
+
Object {
|
|
113
|
+
"location": Array [
|
|
114
|
+
Object {
|
|
115
|
+
"pointer": "#/components/responses/400 status",
|
|
116
|
+
"reportOnKey": true,
|
|
117
|
+
"source": Source {
|
|
118
|
+
"absoluteRef": "",
|
|
119
|
+
"body": "openapi: 3.0.0
|
|
120
|
+
info:
|
|
121
|
+
version: 3.0.0
|
|
122
|
+
components:
|
|
123
|
+
parameters:
|
|
124
|
+
my Param:
|
|
125
|
+
name: param
|
|
126
|
+
description: param
|
|
127
|
+
in: path
|
|
128
|
+
examples:
|
|
129
|
+
invalid identifier:
|
|
130
|
+
description: 'Some description'
|
|
131
|
+
value: 21
|
|
132
|
+
responses:
|
|
133
|
+
400 status:
|
|
134
|
+
description: bad request
|
|
135
|
+
schemas:
|
|
136
|
+
first schema:
|
|
137
|
+
type: integer
|
|
138
|
+
format: int64 ",
|
|
139
|
+
"mimeType": undefined,
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
"message": "The map key in responses \\"400 status\\" does not match the regular expression \\"^[a-zA-Z0-9\\\\.\\\\-_]+$\\"",
|
|
144
|
+
"ruleId": "spec-components-invalid-map-name",
|
|
145
|
+
"severity": "error",
|
|
146
|
+
"suggest": Array [],
|
|
147
|
+
},
|
|
148
|
+
Object {
|
|
149
|
+
"location": Array [
|
|
150
|
+
Object {
|
|
151
|
+
"pointer": "#/components/examples/invalid identifier",
|
|
152
|
+
"reportOnKey": true,
|
|
153
|
+
"source": Source {
|
|
154
|
+
"absoluteRef": "",
|
|
155
|
+
"body": "openapi: 3.0.0
|
|
156
|
+
info:
|
|
157
|
+
version: 3.0.0
|
|
158
|
+
components:
|
|
159
|
+
parameters:
|
|
160
|
+
my Param:
|
|
161
|
+
name: param
|
|
162
|
+
description: param
|
|
163
|
+
in: path
|
|
164
|
+
examples:
|
|
165
|
+
invalid identifier:
|
|
166
|
+
description: 'Some description'
|
|
167
|
+
value: 21
|
|
168
|
+
responses:
|
|
169
|
+
400 status:
|
|
170
|
+
description: bad request
|
|
171
|
+
schemas:
|
|
172
|
+
first schema:
|
|
173
|
+
type: integer
|
|
174
|
+
format: int64 ",
|
|
175
|
+
"mimeType": undefined,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
"message": "The map key in examples \\"invalid identifier\\" does not match the regular expression \\"^[a-zA-Z0-9\\\\.\\\\-_]+$\\"",
|
|
180
|
+
"ruleId": "spec-components-invalid-map-name",
|
|
181
|
+
"severity": "error",
|
|
182
|
+
"suggest": Array [],
|
|
183
|
+
},
|
|
184
|
+
]
|
|
185
|
+
`);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should not report a key of the example does not match the regular expression', async () => {
|
|
189
|
+
const document = parseYamlToDocument(outdent`
|
|
190
|
+
openapi: 3.0.0
|
|
191
|
+
info:
|
|
192
|
+
version: 3.0.0
|
|
193
|
+
paths:
|
|
194
|
+
/store/subscribe:
|
|
195
|
+
post:
|
|
196
|
+
parameters:
|
|
197
|
+
- name: petId
|
|
198
|
+
in: path
|
|
199
|
+
schema:
|
|
200
|
+
type: integer
|
|
201
|
+
format: int64
|
|
202
|
+
examples:
|
|
203
|
+
valid-identifier-1.key:
|
|
204
|
+
description: 'Some description'
|
|
205
|
+
value: 21
|
|
206
|
+
`);
|
|
207
|
+
const results = await lintDocument({
|
|
208
|
+
externalRefResolver: new BaseResolver(),
|
|
209
|
+
document,
|
|
210
|
+
config: await makeConfig({
|
|
211
|
+
'spec-components-invalid-map-name': 'error',
|
|
212
|
+
}),
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
expect(results).toMatchInlineSnapshot(`Array []`);
|
|
216
|
+
});
|
|
217
|
+
});
|
package/src/rules/oas3/index.ts
CHANGED
|
@@ -25,7 +25,7 @@ import { NoUnusedComponents } from './no-unused-components';
|
|
|
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 { BooleanParameterPrefixes } from './boolean-parameter-prefixes';
|
|
31
31
|
import { PathsKebabCase } from '../common/paths-kebab-case';
|
|
@@ -37,7 +37,7 @@ import { NoUndefinedServerVariable } from './no-undefined-server-variable';
|
|
|
37
37
|
import { OperationOperationId } from '../common/operation-operationId';
|
|
38
38
|
import { OperationSummary } from '../common/operation-summary';
|
|
39
39
|
import { NoAmbiguousPaths } from '../common/no-ambiguous-paths';
|
|
40
|
-
import {
|
|
40
|
+
import { NoServerVariablesEmptyEnum } from './no-server-variables-empty-enum';
|
|
41
41
|
import { NoHttpVerbsInPaths } from '../common/no-http-verbs-in-paths';
|
|
42
42
|
import { RequestMimeType } from './request-mime-type';
|
|
43
43
|
import { ResponseMimeType } from './response-mime-type';
|
|
@@ -48,6 +48,8 @@ import { NoInvalidParameterExamples } from '../common/no-invalid-parameter-examp
|
|
|
48
48
|
import { ResponseContainsHeader } from '../common/response-contains-header';
|
|
49
49
|
import { ResponseContainsProperty } from './response-contains-property';
|
|
50
50
|
import { ScalarPropertyMissingExample } from '../common/scalar-property-missing-example';
|
|
51
|
+
import { SpecComponentsInvalidMapName } from './spec-components-invalid-map-name';
|
|
52
|
+
import { Operation4xxProblemDetailsRfc7807 } from './operation-4xx-problem-details-rfc7807';
|
|
51
53
|
|
|
52
54
|
export const rules = {
|
|
53
55
|
spec: OasSpec,
|
|
@@ -57,6 +59,7 @@ export const rules = {
|
|
|
57
59
|
'info-license-url': InfoLicenseUrl,
|
|
58
60
|
'operation-2xx-response': Operation2xxResponse,
|
|
59
61
|
'operation-4xx-response': Operation4xxResponse,
|
|
62
|
+
'operation-4xx-problem-details-rfc7807': Operation4xxProblemDetailsRfc7807,
|
|
60
63
|
assertions: Assertions,
|
|
61
64
|
'operation-operationId-unique': OperationIdUnique,
|
|
62
65
|
'operation-parameters-unique': OperationParametersUnique,
|
|
@@ -80,7 +83,7 @@ export const rules = {
|
|
|
80
83
|
'path-params-defined': PathParamsDefined,
|
|
81
84
|
'parameter-description': ParameterDescription,
|
|
82
85
|
'operation-singular-tag': OperationSingularTag,
|
|
83
|
-
'
|
|
86
|
+
'security-defined': SecurityDefined,
|
|
84
87
|
'no-unresolved-refs': NoUnresolvedRefs,
|
|
85
88
|
'paths-kebab-case': PathsKebabCase,
|
|
86
89
|
'boolean-parameter-prefixes': BooleanParameterPrefixes,
|
|
@@ -89,7 +92,7 @@ export const rules = {
|
|
|
89
92
|
'no-identical-paths': NoIdenticalPaths,
|
|
90
93
|
'no-ambiguous-paths': NoAmbiguousPaths,
|
|
91
94
|
'no-undefined-server-variable': NoUndefinedServerVariable,
|
|
92
|
-
'no-
|
|
95
|
+
'no-server-variables-empty-enum': NoServerVariablesEmptyEnum,
|
|
93
96
|
'no-http-verbs-in-paths': NoHttpVerbsInPaths,
|
|
94
97
|
'path-excludes-patterns': PathExcludesPatterns,
|
|
95
98
|
'request-mime-type': RequestMimeType,
|
|
@@ -100,6 +103,7 @@ export const rules = {
|
|
|
100
103
|
'response-contains-header': ResponseContainsHeader,
|
|
101
104
|
'response-contains-property': ResponseContainsProperty,
|
|
102
105
|
'scalar-property-missing-example': ScalarPropertyMissingExample,
|
|
106
|
+
'spec-components-invalid-map-name': SpecComponentsInvalidMapName,
|
|
103
107
|
} as Oas3RuleSet;
|
|
104
108
|
|
|
105
109
|
export const preprocessors = {};
|
|
@@ -2,7 +2,7 @@ import { Oas3Rule } from '../../visitors';
|
|
|
2
2
|
|
|
3
3
|
export const NoEmptyServers: Oas3Rule = () => {
|
|
4
4
|
return {
|
|
5
|
-
|
|
5
|
+
Root(root, { report, location }) {
|
|
6
6
|
if (!root.hasOwnProperty('servers')) {
|
|
7
7
|
report({
|
|
8
8
|
message: 'Servers must be present.',
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Oas3Rule } from '../../visitors';
|
|
2
2
|
import { Location, isRef } from '../../ref-utils';
|
|
3
3
|
import { Oas3Example } from '../../typings/openapi';
|
|
4
|
-
import { validateExample } from '../utils';
|
|
4
|
+
import { getAdditionalPropertiesOption, validateExample } from '../utils';
|
|
5
5
|
import { UserContext } from '../../walk';
|
|
6
6
|
|
|
7
7
|
export const ValidContentExamples: Oas3Rule = (opts) => {
|
|
8
|
-
const
|
|
8
|
+
const allowAdditionalProperties = getAdditionalPropertiesOption(opts) ?? false;
|
|
9
9
|
|
|
10
10
|
return {
|
|
11
11
|
MediaType: {
|
|
@@ -40,7 +40,7 @@ export const ValidContentExamples: Oas3Rule = (opts) => {
|
|
|
40
40
|
mediaType.schema!,
|
|
41
41
|
location,
|
|
42
42
|
ctx,
|
|
43
|
-
|
|
43
|
+
allowAdditionalProperties
|
|
44
44
|
);
|
|
45
45
|
}
|
|
46
46
|
},
|
|
@@ -6,9 +6,9 @@ enum enumError {
|
|
|
6
6
|
invalidDefaultValue = 'invalidDefaultValue',
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export const
|
|
9
|
+
export const NoServerVariablesEmptyEnum: Oas3Rule = () => {
|
|
10
10
|
return {
|
|
11
|
-
|
|
11
|
+
Root(root, { report, location }) {
|
|
12
12
|
if (!root.servers || root.servers.length === 0) return;
|
|
13
13
|
|
|
14
14
|
const invalidVariables: enumError[] = [];
|
|
@@ -48,7 +48,7 @@ function checkEnumVariables(server: Oas3Server): enumError[] | undefined {
|
|
|
48
48
|
if (server.variables && Object.keys(server.variables).length === 0) return;
|
|
49
49
|
|
|
50
50
|
const errors: enumError[] = [];
|
|
51
|
-
for (
|
|
51
|
+
for (const variable in server.variables) {
|
|
52
52
|
const serverVariable = server.variables[variable];
|
|
53
53
|
if (!serverVariable.enum) continue;
|
|
54
54
|
|
|
@@ -2,7 +2,7 @@ import { Oas3Rule } from '../../visitors';
|
|
|
2
2
|
import { Location } from '../../ref-utils';
|
|
3
3
|
|
|
4
4
|
export const NoUnusedComponents: Oas3Rule = () => {
|
|
5
|
-
|
|
5
|
+
const components = new Map<string, { used: boolean; location: Location; name: string }>();
|
|
6
6
|
|
|
7
7
|
function registerComponent(location: Location, name: string): void {
|
|
8
8
|
components.set(location.absolutePointer, {
|
|
@@ -26,7 +26,7 @@ export const NoUnusedComponents: Oas3Rule = () => {
|
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
|
-
|
|
29
|
+
Root: {
|
|
30
30
|
leave(_, { report }) {
|
|
31
31
|
components.forEach((usageInfo) => {
|
|
32
32
|
if (!usageInfo.used) {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Oas3MediaType, Oas3Response, Oas3Schema } from 'core/src/typings/openapi';
|
|
2
|
+
import { Oas3Rule } from '../../visitors';
|
|
3
|
+
import { UserContext } from '../../walk';
|
|
4
|
+
import { validateDefinedAndNonEmpty } from '../utils';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Validation according rfc7807 - https://datatracker.ietf.org/doc/html/rfc7807
|
|
8
|
+
*/
|
|
9
|
+
export const Operation4xxProblemDetailsRfc7807: Oas3Rule = () => {
|
|
10
|
+
return {
|
|
11
|
+
Response: {
|
|
12
|
+
skip(_response: Oas3Response, key: string | number) {
|
|
13
|
+
return !/4[Xx0-9]{2}/.test(`${key}`);
|
|
14
|
+
},
|
|
15
|
+
enter(response: Oas3Response, { report, location }: UserContext) {
|
|
16
|
+
if (!response.content || !response.content['application/problem+json'])
|
|
17
|
+
report({
|
|
18
|
+
message: 'Response `4xx` must have content-type `application/problem+json`.',
|
|
19
|
+
location: location.key(),
|
|
20
|
+
});
|
|
21
|
+
},
|
|
22
|
+
MediaType: {
|
|
23
|
+
skip(_response: Oas3MediaType, key: string | number) {
|
|
24
|
+
return key !== 'application/problem+json';
|
|
25
|
+
},
|
|
26
|
+
enter(media: Oas3MediaType, ctx: UserContext) {
|
|
27
|
+
validateDefinedAndNonEmpty('schema', media, ctx);
|
|
28
|
+
},
|
|
29
|
+
SchemaProperties(schema: Oas3Schema, ctx: UserContext) {
|
|
30
|
+
validateDefinedAndNonEmpty('type', schema, ctx);
|
|
31
|
+
validateDefinedAndNonEmpty('title', schema, ctx);
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
};
|
|
@@ -4,7 +4,7 @@ import { Oas3Components } from '../../typings/openapi';
|
|
|
4
4
|
import { isEmptyObject } from '../../utils';
|
|
5
5
|
|
|
6
6
|
export const RemoveUnusedComponents: Oas3Rule = () => {
|
|
7
|
-
|
|
7
|
+
const components = new Map<
|
|
8
8
|
string,
|
|
9
9
|
{ used: boolean; componentType?: keyof Oas3Components; name: string }
|
|
10
10
|
>();
|
|
@@ -38,19 +38,19 @@ export const RemoveUnusedComponents: Oas3Rule = () => {
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
},
|
|
41
|
-
|
|
41
|
+
Root: {
|
|
42
42
|
leave(root, ctx) {
|
|
43
43
|
const data = ctx.getVisitorData() as { removedCount: number };
|
|
44
44
|
data.removedCount = 0;
|
|
45
45
|
|
|
46
46
|
components.forEach((usageInfo) => {
|
|
47
47
|
const { used, componentType, name } = usageInfo;
|
|
48
|
-
if (!used && componentType) {
|
|
49
|
-
|
|
48
|
+
if (!used && componentType && root.components) {
|
|
49
|
+
const componentChild = root.components[componentType];
|
|
50
50
|
delete componentChild![name];
|
|
51
51
|
data.removedCount++;
|
|
52
52
|
if (isEmptyObject(componentChild)) {
|
|
53
|
-
delete root.components
|
|
53
|
+
delete root.components[componentType];
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
});
|
|
@@ -5,7 +5,7 @@ import { validateMimeTypeOAS3 } from '../../utils';
|
|
|
5
5
|
|
|
6
6
|
export const RequestMimeType: Oas3Rule = ({ allowedValues }) => {
|
|
7
7
|
return {
|
|
8
|
-
|
|
8
|
+
PathsMap: {
|
|
9
9
|
RequestBody: {
|
|
10
10
|
leave(requestBody: Oas3RequestBody, ctx: UserContext) {
|
|
11
11
|
validateMimeTypeOAS3({ type: 'consumes', value: requestBody }, ctx, allowedValues);
|
|
@@ -5,7 +5,7 @@ import { validateMimeTypeOAS3 } from '../../utils';
|
|
|
5
5
|
|
|
6
6
|
export const ResponseMimeType: Oas3Rule = ({ allowedValues }) => {
|
|
7
7
|
return {
|
|
8
|
-
|
|
8
|
+
PathsMap: {
|
|
9
9
|
Response: {
|
|
10
10
|
leave(response: Oas3Response, ctx: UserContext) {
|
|
11
11
|
validateMimeTypeOAS3({ type: 'produces', value: response }, ctx, allowedValues);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Oas3Rule } from '../../visitors';
|
|
2
|
+
import { Problem, UserContext } from '../../walk';
|
|
3
|
+
import { Location } from '../../ref-utils';
|
|
4
|
+
|
|
5
|
+
export const SpecComponentsInvalidMapName: Oas3Rule = () => {
|
|
6
|
+
const KEYS_REGEX = '^[a-zA-Z0-9\\.\\-_]+$';
|
|
7
|
+
|
|
8
|
+
function validateKey(
|
|
9
|
+
key: string | number,
|
|
10
|
+
report: (problem: Problem) => void,
|
|
11
|
+
location: Location,
|
|
12
|
+
component: string
|
|
13
|
+
) {
|
|
14
|
+
if (!new RegExp(KEYS_REGEX).test(key as string)) {
|
|
15
|
+
report({
|
|
16
|
+
message: `The map key in ${component} "${key}" does not match the regular expression "${KEYS_REGEX}"`,
|
|
17
|
+
location: location.key(),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
Components: {
|
|
24
|
+
Parameter(_node, { key, report, location }: UserContext) {
|
|
25
|
+
validateKey(key, report, location, 'parameters');
|
|
26
|
+
},
|
|
27
|
+
Response(_node, { key, report, location }: UserContext) {
|
|
28
|
+
validateKey(key, report, location, 'responses');
|
|
29
|
+
},
|
|
30
|
+
Schema(_node, { key, report, location }: UserContext) {
|
|
31
|
+
validateKey(key, report, location, 'schemas');
|
|
32
|
+
},
|
|
33
|
+
Example(_node, { key, report, location }: UserContext) {
|
|
34
|
+
validateKey(key, report, location, 'examples');
|
|
35
|
+
},
|
|
36
|
+
RequestBody(_node, { key, report, location }: UserContext) {
|
|
37
|
+
validateKey(key, report, location, 'requestBodies');
|
|
38
|
+
},
|
|
39
|
+
Header(_node, { key, report, location }: UserContext) {
|
|
40
|
+
validateKey(key, report, location, 'headers');
|
|
41
|
+
},
|
|
42
|
+
SecurityScheme(_node, { key, report, location }: UserContext) {
|
|
43
|
+
validateKey(key, report, location, 'securitySchemas');
|
|
44
|
+
},
|
|
45
|
+
Link(_node, { key, report, location }: UserContext) {
|
|
46
|
+
validateKey(key, report, location, 'links');
|
|
47
|
+
},
|
|
48
|
+
Callback(_node, { key, report, location }: UserContext) {
|
|
49
|
+
validateKey(key, report, location, 'callbacks');
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
};
|
package/src/rules/other/stats.ts
CHANGED
|
@@ -24,7 +24,7 @@ export const Stats = (statsAccumulator: StatsAccumulator) => {
|
|
|
24
24
|
statsAccumulator.links.items!.add(link.operationId);
|
|
25
25
|
},
|
|
26
26
|
},
|
|
27
|
-
|
|
27
|
+
Root: {
|
|
28
28
|
leave() {
|
|
29
29
|
statsAccumulator.parameters.total = statsAccumulator.parameters.items!.size;
|
|
30
30
|
statsAccumulator.refs.total = statsAccumulator.refs.items!.size;
|
|
@@ -41,7 +41,7 @@ export const Stats = (statsAccumulator: StatsAccumulator) => {
|
|
|
41
41
|
},
|
|
42
42
|
},
|
|
43
43
|
},
|
|
44
|
-
|
|
44
|
+
PathsMap: {
|
|
45
45
|
PathItem: {
|
|
46
46
|
leave() {
|
|
47
47
|
statsAccumulator.pathItems.total++;
|
package/src/rules/utils.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { UserContext } from '../walk';
|
|
|
3
3
|
import { Location } from '../ref-utils';
|
|
4
4
|
import { validateJsonSchema } from './ajv';
|
|
5
5
|
import { Oas3Schema, Referenced } from '../typings/openapi';
|
|
6
|
+
import { showErrorForDeprecatedField, showWarningForDeprecatedField } from '../utils';
|
|
6
7
|
|
|
7
8
|
export function oasTypeOf(value: unknown) {
|
|
8
9
|
if (Array.isArray(value)) {
|
|
@@ -91,7 +92,7 @@ export function validateExample(
|
|
|
91
92
|
schema: Referenced<Oas3Schema>,
|
|
92
93
|
dataLoc: Location,
|
|
93
94
|
{ resolve, location, report }: UserContext,
|
|
94
|
-
|
|
95
|
+
allowAdditionalProperties: boolean
|
|
95
96
|
) {
|
|
96
97
|
try {
|
|
97
98
|
const { valid, errors } = validateJsonSchema(
|
|
@@ -100,10 +101,10 @@ export function validateExample(
|
|
|
100
101
|
location.child('schema'),
|
|
101
102
|
dataLoc.pointer,
|
|
102
103
|
resolve,
|
|
103
|
-
|
|
104
|
+
allowAdditionalProperties
|
|
104
105
|
);
|
|
105
106
|
if (!valid) {
|
|
106
|
-
for (
|
|
107
|
+
for (const error of errors) {
|
|
107
108
|
report({
|
|
108
109
|
message: `Example value must conform to the schema: ${error.message}.`,
|
|
109
110
|
location: {
|
|
@@ -123,3 +124,16 @@ export function validateExample(
|
|
|
123
124
|
});
|
|
124
125
|
}
|
|
125
126
|
}
|
|
127
|
+
|
|
128
|
+
export function getAdditionalPropertiesOption(opts: Record<string, any>): boolean {
|
|
129
|
+
if (opts.disallowAdditionalProperties === undefined) {
|
|
130
|
+
return opts.allowAdditionalProperties;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (opts.allowAdditionalProperties !== undefined) {
|
|
134
|
+
showErrorForDeprecatedField('disallowAdditionalProperties', 'allowAdditionalProperties');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
showWarningForDeprecatedField('disallowAdditionalProperties', 'allowAdditionalProperties');
|
|
138
|
+
return !opts.disallowAdditionalProperties;
|
|
139
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -73,7 +73,7 @@ export function normalizeTypes(
|
|
|
73
73
|
normalizedTypes[typeName] = {
|
|
74
74
|
...types[typeName],
|
|
75
75
|
name: typeName,
|
|
76
|
-
} as
|
|
76
|
+
} as NormalizedNodeType;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
for (const type of Object.values(normalizedTypes)) {
|
|
@@ -81,7 +81,7 @@ export function normalizeTypes(
|
|
|
81
81
|
}
|
|
82
82
|
return normalizedTypes;
|
|
83
83
|
|
|
84
|
-
function normalizeType(type:
|
|
84
|
+
function normalizeType(type: NormalizedNodeType) {
|
|
85
85
|
if (type.additionalProperties) {
|
|
86
86
|
type.additionalProperties = resolveType(type.additionalProperties);
|
|
87
87
|
}
|
package/src/types/oas2.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { NodeType, listOf, mapOf } from '.';
|
|
|
2
2
|
|
|
3
3
|
const responseCodeRegexp = /^[0-9][0-9Xx]{2}$/;
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const Root: NodeType = {
|
|
6
6
|
properties: {
|
|
7
7
|
swagger: { type: 'string' },
|
|
8
8
|
info: 'Info',
|
|
@@ -11,7 +11,7 @@ const DefinitionRoot: NodeType = {
|
|
|
11
11
|
schemes: { type: 'array', items: { type: 'string' } },
|
|
12
12
|
consumes: { type: 'array', items: { type: 'string' } },
|
|
13
13
|
produces: { type: 'array', items: { type: 'string' } },
|
|
14
|
-
paths: '
|
|
14
|
+
paths: 'PathsMap',
|
|
15
15
|
definitions: 'NamedSchemas',
|
|
16
16
|
parameters: 'NamedParameters',
|
|
17
17
|
responses: 'NamedResponses',
|
|
@@ -51,7 +51,7 @@ const License: NodeType = {
|
|
|
51
51
|
required: ['name'],
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
const
|
|
54
|
+
const PathsMap: NodeType = {
|
|
55
55
|
properties: {},
|
|
56
56
|
additionalProperties: (_value: any, key: string) =>
|
|
57
57
|
key.startsWith('/') ? 'PathItem' : undefined,
|
|
@@ -369,14 +369,14 @@ const SecurityRequirement: NodeType = {
|
|
|
369
369
|
};
|
|
370
370
|
|
|
371
371
|
export const Oas2Types: Record<string, NodeType> = {
|
|
372
|
-
|
|
372
|
+
Root,
|
|
373
373
|
Tag,
|
|
374
374
|
ExternalDocs,
|
|
375
375
|
SecurityRequirement,
|
|
376
376
|
Info,
|
|
377
377
|
Contact,
|
|
378
378
|
License,
|
|
379
|
-
|
|
379
|
+
PathsMap,
|
|
380
380
|
PathItem,
|
|
381
381
|
Parameter,
|
|
382
382
|
ParameterItems,
|