@redocly/openapi-core 1.13.0 → 1.15.0
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/CHANGELOG.md +16 -0
- package/lib/rules/common/no-invalid-parameter-examples.js +1 -1
- package/lib/rules/common/no-invalid-schema-examples.js +1 -1
- package/lib/rules/common/security-defined.js +18 -6
- package/lib/rules/oas3/no-invalid-media-type-examples.js +1 -1
- package/lib/types/redocly-yaml.d.ts +0 -1
- package/lib/types/redocly-yaml.js +3 -7
- package/package.json +2 -2
- package/src/__tests__/lint.test.ts +15 -36
- package/src/config/__tests__/load.test.ts +13 -13
- package/src/rules/common/__tests__/no-invalid-parameter-examples.test.ts +53 -0
- package/src/rules/common/__tests__/no-invalid-schema-examples.test.ts +51 -0
- package/src/rules/common/__tests__/security-defined.test.ts +103 -0
- package/src/rules/common/no-invalid-parameter-examples.ts +1 -1
- package/src/rules/common/no-invalid-schema-examples.ts +1 -1
- package/src/rules/common/security-defined.ts +31 -8
- package/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts +52 -0
- package/src/rules/oas3/no-invalid-media-type-examples.ts +1 -1
- package/src/types/redocly-yaml.ts +3 -13
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @redocly/openapi-core
|
|
2
2
|
|
|
3
|
+
## 1.15.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Made `redocly.yaml` validation consistent with the general Redocly config.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Fixed `no-invalid-media-type-examples`, `no-invalid-parameter-examples`, and `no-invalid-schema-examples` rules which allowed falsy example values to pass for any schema.
|
|
12
|
+
|
|
13
|
+
## 1.14.0
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- Added the ability to exclude some operations or entire paths from the `security-defined` rule.
|
|
18
|
+
|
|
3
19
|
## 1.13.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
|
@@ -8,7 +8,7 @@ const NoInvalidParameterExamples = (opts) => {
|
|
|
8
8
|
return {
|
|
9
9
|
Parameter: {
|
|
10
10
|
leave(parameter, ctx) {
|
|
11
|
-
if (parameter.example) {
|
|
11
|
+
if (parameter.example !== undefined) {
|
|
12
12
|
(0, utils_1.validateExample)(parameter.example, parameter.schema, ctx.location.child('example'), ctx, allowAdditionalProperties);
|
|
13
13
|
}
|
|
14
14
|
if (parameter.examples) {
|
|
@@ -13,7 +13,7 @@ const NoInvalidSchemaExamples = (opts) => {
|
|
|
13
13
|
(0, utils_1.validateExample)(example, schema, ctx.location.child(['examples', schema.examples.indexOf(example)]), ctx, allowAdditionalProperties);
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
if (schema.example) {
|
|
16
|
+
if (schema.example !== undefined) {
|
|
17
17
|
(0, utils_1.validateExample)(schema.example, schema, ctx.location.child('example'), ctx, true);
|
|
18
18
|
}
|
|
19
19
|
},
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SecurityDefined = void 0;
|
|
4
|
-
const SecurityDefined = () => {
|
|
4
|
+
const SecurityDefined = (opts) => {
|
|
5
5
|
const referencedSchemes = new Map();
|
|
6
6
|
const operationsWithoutSecurity = [];
|
|
7
7
|
let eachOperationHasSecurity = true;
|
|
8
|
+
let path;
|
|
8
9
|
return {
|
|
9
10
|
Root: {
|
|
10
11
|
leave(root, { report }) {
|
|
@@ -46,11 +47,22 @@ const SecurityDefined = () => {
|
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
},
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
50
|
+
PathItem: {
|
|
51
|
+
enter(pathItem, { key }) {
|
|
52
|
+
path = key;
|
|
53
|
+
},
|
|
54
|
+
Operation(operation, { location, key }) {
|
|
55
|
+
var _a;
|
|
56
|
+
const isException = (_a = opts.exceptions) === null || _a === void 0 ? void 0 : _a.some((item) => {
|
|
57
|
+
var _a;
|
|
58
|
+
return item.path === path &&
|
|
59
|
+
(!item.methods || ((_a = item.methods) === null || _a === void 0 ? void 0 : _a.some((method) => method.toLowerCase() === key)));
|
|
60
|
+
});
|
|
61
|
+
if (!(operation === null || operation === void 0 ? void 0 : operation.security) && !isException) {
|
|
62
|
+
eachOperationHasSecurity = false;
|
|
63
|
+
operationsWithoutSecurity.push(location);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
54
66
|
},
|
|
55
67
|
};
|
|
56
68
|
};
|
|
@@ -12,7 +12,7 @@ const ValidContentExamples = (opts) => {
|
|
|
12
12
|
const { location, resolve } = ctx;
|
|
13
13
|
if (!mediaType.schema)
|
|
14
14
|
return;
|
|
15
|
-
if (mediaType.example) {
|
|
15
|
+
if (mediaType.example !== undefined) {
|
|
16
16
|
resolveAndValidateExample(mediaType.example, location.child('example'));
|
|
17
17
|
}
|
|
18
18
|
else if (mediaType.examples) {
|
|
@@ -19,7 +19,6 @@ export type Oas3_1NodeType = typeof oas3_1NodeTypesList[number];
|
|
|
19
19
|
export declare const createConfigTypes: (extraSchemas: JSONSchema) => {
|
|
20
20
|
ConfigRoot: NodeType;
|
|
21
21
|
ConfigApisProperties: NodeType;
|
|
22
|
-
ConfigRootTheme: NodeType;
|
|
23
22
|
};
|
|
24
23
|
export declare const ConfigTypes: Record<string, NodeType>;
|
|
25
24
|
export {};
|
|
@@ -220,7 +220,7 @@ const ConfigStyleguide = {
|
|
|
220
220
|
async2Decorators: { type: 'object' },
|
|
221
221
|
},
|
|
222
222
|
};
|
|
223
|
-
const createConfigRoot = (nodeTypes) => (Object.assign(Object.assign({}, nodeTypes.rootRedoclyConfigSchema), { properties: Object.assign(Object.assign(Object.assign({}, nodeTypes.rootRedoclyConfigSchema.properties), ConfigStyleguide.properties), { apis: 'ConfigApis',
|
|
223
|
+
const createConfigRoot = (nodeTypes) => (Object.assign(Object.assign({}, nodeTypes.rootRedoclyConfigSchema), { properties: Object.assign(Object.assign(Object.assign({}, nodeTypes.rootRedoclyConfigSchema.properties), ConfigStyleguide.properties), { apis: 'ConfigApis', 'features.openapi': 'ConfigReferenceDocs', 'features.mockServer': 'ConfigMockServer', organization: { type: 'string' }, region: { enum: ['us', 'eu'] }, telemetry: { enum: ['on', 'off'] }, resolve: {
|
|
224
224
|
properties: {
|
|
225
225
|
http: 'ConfigHTTP',
|
|
226
226
|
doNotResolveExamples: { type: 'boolean' },
|
|
@@ -259,10 +259,6 @@ const ConfigHTTP = {
|
|
|
259
259
|
},
|
|
260
260
|
},
|
|
261
261
|
};
|
|
262
|
-
const createConfigRootTheme = (nodeTypes) => {
|
|
263
|
-
var _a;
|
|
264
|
-
return (Object.assign(Object.assign({}, nodeTypes['rootRedoclyConfigSchema.theme']), { properties: Object.assign(Object.assign({}, (_a = nodeTypes['rootRedoclyConfigSchema.theme']) === null || _a === void 0 ? void 0 : _a.properties), { openapi: 'ConfigReferenceDocs' }) }));
|
|
265
|
-
};
|
|
266
262
|
const Rules = {
|
|
267
263
|
properties: {},
|
|
268
264
|
additionalProperties: (value, key) => {
|
|
@@ -765,8 +761,8 @@ const GenerateCodeSamples = {
|
|
|
765
761
|
},
|
|
766
762
|
required: ['languages'],
|
|
767
763
|
};
|
|
764
|
+
// TODO: deprecated
|
|
768
765
|
const ConfigReferenceDocs = {
|
|
769
|
-
// TODO: partially invalid @Viacheslav
|
|
770
766
|
properties: {
|
|
771
767
|
theme: 'ConfigTheme',
|
|
772
768
|
corsProxyUrl: { type: 'string' },
|
|
@@ -904,7 +900,7 @@ const ConfigMockServer = {
|
|
|
904
900
|
const createConfigTypes = (extraSchemas) => {
|
|
905
901
|
// Create types based on external schemas
|
|
906
902
|
const nodeTypes = (0, json_schema_adapter_1.getNodeTypesFromJSONSchema)('rootRedoclyConfigSchema', extraSchemas);
|
|
907
|
-
return Object.assign(Object.assign(Object.assign({}, CoreConfigTypes), { ConfigRoot: createConfigRoot(nodeTypes), ConfigApisProperties: createConfigApisProperties(nodeTypes)
|
|
903
|
+
return Object.assign(Object.assign(Object.assign({}, CoreConfigTypes), { ConfigRoot: createConfigRoot(nodeTypes), ConfigApisProperties: createConfigApisProperties(nodeTypes) }), nodeTypes);
|
|
908
904
|
};
|
|
909
905
|
exports.createConfigTypes = createConfigTypes;
|
|
910
906
|
const CoreConfigTypes = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/openapi-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"engines": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@redocly/ajv": "^8.11.0",
|
|
38
|
-
"@redocly/config": "^0.
|
|
38
|
+
"@redocly/config": "^0.6.0",
|
|
39
39
|
"colorette": "^1.2.0",
|
|
40
40
|
"js-levenshtein": "^1.1.6",
|
|
41
41
|
"js-yaml": "^4.1.0",
|
|
@@ -369,26 +369,8 @@ describe('lint', () => {
|
|
|
369
369
|
min: 3
|
|
370
370
|
theme:
|
|
371
371
|
openapi:
|
|
372
|
-
showConsole: true
|
|
373
|
-
layout:
|
|
374
|
-
scope: section
|
|
375
|
-
routingStrategy: browser
|
|
376
|
-
theme:
|
|
377
|
-
rightPanel:
|
|
378
|
-
backgroundColor: '#263238'
|
|
379
|
-
links:
|
|
380
|
-
color: '#6CC496'
|
|
381
|
-
theme:
|
|
382
|
-
openapi:
|
|
383
|
-
showConsole: true
|
|
384
|
-
layout:
|
|
385
|
-
scope: section
|
|
386
|
-
routingStrategy: browser
|
|
387
|
-
theme:
|
|
388
|
-
rightPanel:
|
|
389
|
-
backgroundColor: '#263238'
|
|
390
|
-
links:
|
|
391
|
-
color: '#6CC496'
|
|
372
|
+
showConsole: true # Not expected anymore
|
|
373
|
+
layout: wrong-option
|
|
392
374
|
`,
|
|
393
375
|
''
|
|
394
376
|
);
|
|
@@ -414,12 +396,12 @@ describe('lint', () => {
|
|
|
414
396
|
"from": undefined,
|
|
415
397
|
"location": [
|
|
416
398
|
{
|
|
417
|
-
"pointer": "#/theme/openapi/
|
|
418
|
-
"reportOnKey":
|
|
399
|
+
"pointer": "#/theme/openapi/showConsole",
|
|
400
|
+
"reportOnKey": true,
|
|
419
401
|
"source": "",
|
|
420
402
|
},
|
|
421
403
|
],
|
|
422
|
-
"message": "\`
|
|
404
|
+
"message": "Property \`showConsole\` is not expected here.",
|
|
423
405
|
"ruleId": "configuration spec",
|
|
424
406
|
"severity": "error",
|
|
425
407
|
"suggest": [],
|
|
@@ -428,18 +410,15 @@ describe('lint', () => {
|
|
|
428
410
|
"from": undefined,
|
|
429
411
|
"location": [
|
|
430
412
|
{
|
|
431
|
-
"pointer": "#/theme/openapi/
|
|
432
|
-
"reportOnKey":
|
|
413
|
+
"pointer": "#/theme/openapi/layout",
|
|
414
|
+
"reportOnKey": false,
|
|
433
415
|
"source": "",
|
|
434
416
|
},
|
|
435
417
|
],
|
|
436
|
-
"message": "
|
|
418
|
+
"message": "\`layout\` can be one of the following only: "stacked", "three-panel".",
|
|
437
419
|
"ruleId": "configuration spec",
|
|
438
420
|
"severity": "error",
|
|
439
|
-
"suggest": [
|
|
440
|
-
"schema",
|
|
441
|
-
"shape",
|
|
442
|
-
],
|
|
421
|
+
"suggest": [],
|
|
443
422
|
},
|
|
444
423
|
]
|
|
445
424
|
`);
|
|
@@ -806,12 +785,12 @@ describe('lint', () => {
|
|
|
806
785
|
"from": undefined,
|
|
807
786
|
"location": [
|
|
808
787
|
{
|
|
809
|
-
"pointer": "#/apis/with-theme/theme/
|
|
810
|
-
"reportOnKey":
|
|
788
|
+
"pointer": "#/apis/with-theme/theme/not-expected",
|
|
789
|
+
"reportOnKey": true,
|
|
811
790
|
"source": "",
|
|
812
791
|
},
|
|
813
792
|
],
|
|
814
|
-
"message": "
|
|
793
|
+
"message": "Property \`not-expected\` is not expected here.",
|
|
815
794
|
"ruleId": "configuration spec",
|
|
816
795
|
"severity": "error",
|
|
817
796
|
"suggest": [],
|
|
@@ -820,12 +799,12 @@ describe('lint', () => {
|
|
|
820
799
|
"from": undefined,
|
|
821
800
|
"location": [
|
|
822
801
|
{
|
|
823
|
-
"pointer": "#/apis/with-theme/theme/
|
|
824
|
-
"reportOnKey":
|
|
802
|
+
"pointer": "#/apis/with-theme/theme/openapi",
|
|
803
|
+
"reportOnKey": false,
|
|
825
804
|
"source": "",
|
|
826
805
|
},
|
|
827
806
|
],
|
|
828
|
-
"message": "
|
|
807
|
+
"message": "Expected type \`rootRedoclyConfigSchema.apis_additionalProperties.theme.openapi\` (object) but got \`string\`",
|
|
829
808
|
"ruleId": "configuration spec",
|
|
830
809
|
"severity": "error",
|
|
831
810
|
"suggest": [],
|
|
@@ -149,19 +149,6 @@ describe('getConfig', () => {
|
|
|
149
149
|
"severity": "warn",
|
|
150
150
|
"suggest": [],
|
|
151
151
|
},
|
|
152
|
-
{
|
|
153
|
-
"location": [
|
|
154
|
-
{
|
|
155
|
-
"pointer": "#/theme",
|
|
156
|
-
"reportOnKey": false,
|
|
157
|
-
"source": "fixtures/resolve-refs-in-config/config-with-refs.yaml",
|
|
158
|
-
},
|
|
159
|
-
],
|
|
160
|
-
"message": "Can't resolve $ref: ENOENT: no such file or directory 'fixtures/resolve-refs-in-config/wrong-ref.yaml'",
|
|
161
|
-
"ruleId": "configuration no-unresolved-refs",
|
|
162
|
-
"severity": "warn",
|
|
163
|
-
"suggest": [],
|
|
164
|
-
},
|
|
165
152
|
{
|
|
166
153
|
"from": {
|
|
167
154
|
"pointer": "#/rules",
|
|
@@ -179,6 +166,19 @@ describe('getConfig', () => {
|
|
|
179
166
|
"severity": "warn",
|
|
180
167
|
"suggest": [],
|
|
181
168
|
},
|
|
169
|
+
{
|
|
170
|
+
"location": [
|
|
171
|
+
{
|
|
172
|
+
"pointer": "#/theme",
|
|
173
|
+
"reportOnKey": false,
|
|
174
|
+
"source": "fixtures/resolve-refs-in-config/config-with-refs.yaml",
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
"message": "Can't resolve $ref: ENOENT: no such file or directory 'fixtures/resolve-refs-in-config/wrong-ref.yaml'",
|
|
178
|
+
"ruleId": "configuration no-unresolved-refs",
|
|
179
|
+
"severity": "warn",
|
|
180
|
+
"suggest": [],
|
|
181
|
+
},
|
|
182
182
|
]
|
|
183
183
|
`);
|
|
184
184
|
});
|
|
@@ -0,0 +1,53 @@
|
|
|
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('no-invalid-parameter-examples', () => {
|
|
7
|
+
it('should report on invalid falsy example', async () => {
|
|
8
|
+
const document = parseYamlToDocument(
|
|
9
|
+
outdent`
|
|
10
|
+
openapi: 3.1.0
|
|
11
|
+
paths:
|
|
12
|
+
/results:
|
|
13
|
+
get:
|
|
14
|
+
parameters:
|
|
15
|
+
- name: username
|
|
16
|
+
in: query
|
|
17
|
+
schema:
|
|
18
|
+
type: string
|
|
19
|
+
maxLength: 15
|
|
20
|
+
example: false
|
|
21
|
+
`,
|
|
22
|
+
'foobar.yaml'
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const results = await lintDocument({
|
|
26
|
+
externalRefResolver: new BaseResolver(),
|
|
27
|
+
document,
|
|
28
|
+
config: await makeConfig({ 'no-invalid-parameter-examples': 'error' }),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
32
|
+
[
|
|
33
|
+
{
|
|
34
|
+
"from": {
|
|
35
|
+
"pointer": "#/paths/~1results/get/parameters/0",
|
|
36
|
+
"source": "foobar.yaml",
|
|
37
|
+
},
|
|
38
|
+
"location": [
|
|
39
|
+
{
|
|
40
|
+
"pointer": "#/paths/~1results/get/parameters/0/example",
|
|
41
|
+
"reportOnKey": false,
|
|
42
|
+
"source": "foobar.yaml",
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
"message": "Example value must conform to the schema: type must be string.",
|
|
46
|
+
"ruleId": "no-invalid-parameter-examples",
|
|
47
|
+
"severity": "error",
|
|
48
|
+
"suggest": [],
|
|
49
|
+
},
|
|
50
|
+
]
|
|
51
|
+
`);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
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('no-invalid-schema-examples', () => {
|
|
7
|
+
it('should report on invalid falsy example', async () => {
|
|
8
|
+
const document = parseYamlToDocument(
|
|
9
|
+
outdent`
|
|
10
|
+
openapi: 3.1.0
|
|
11
|
+
components:
|
|
12
|
+
schemas:
|
|
13
|
+
Car:
|
|
14
|
+
type: object
|
|
15
|
+
properties:
|
|
16
|
+
color:
|
|
17
|
+
type: string
|
|
18
|
+
example: 0
|
|
19
|
+
`,
|
|
20
|
+
'foobar.yaml'
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const results = await lintDocument({
|
|
24
|
+
externalRefResolver: new BaseResolver(),
|
|
25
|
+
document,
|
|
26
|
+
config: await makeConfig({ 'no-invalid-schema-examples': 'error' }),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
30
|
+
[
|
|
31
|
+
{
|
|
32
|
+
"from": {
|
|
33
|
+
"pointer": "#/components/schemas/Car/properties/color",
|
|
34
|
+
"source": "foobar.yaml",
|
|
35
|
+
},
|
|
36
|
+
"location": [
|
|
37
|
+
{
|
|
38
|
+
"pointer": "#/components/schemas/Car/properties/color/example",
|
|
39
|
+
"reportOnKey": false,
|
|
40
|
+
"source": "foobar.yaml",
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
"message": "Example value must conform to the schema: type must be string.",
|
|
44
|
+
"ruleId": "no-invalid-schema-examples",
|
|
45
|
+
"severity": "error",
|
|
46
|
+
"suggest": [],
|
|
47
|
+
},
|
|
48
|
+
]
|
|
49
|
+
`);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -172,4 +172,107 @@ describe('Oas3 security-defined', () => {
|
|
|
172
172
|
|
|
173
173
|
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
|
174
174
|
});
|
|
175
|
+
|
|
176
|
+
it('should not report if a pathItem is explicitly excluded in the option', async () => {
|
|
177
|
+
const document = parseYamlToDocument(
|
|
178
|
+
outdent`
|
|
179
|
+
openapi: 3.1.0
|
|
180
|
+
paths:
|
|
181
|
+
/excluded:
|
|
182
|
+
get:
|
|
183
|
+
description: Should be skipped.
|
|
184
|
+
post:
|
|
185
|
+
description: Should be skipped.`,
|
|
186
|
+
'foobar.yaml'
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const results = await lintDocument({
|
|
190
|
+
externalRefResolver: new BaseResolver(),
|
|
191
|
+
document,
|
|
192
|
+
config: await makeConfig({
|
|
193
|
+
'security-defined': { exceptions: [{ path: '/excluded' }] },
|
|
194
|
+
}),
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should report only those operations without security defined that are not excluded in the options', async () => {
|
|
201
|
+
const document = parseYamlToDocument(
|
|
202
|
+
outdent`
|
|
203
|
+
openapi: 3.1.0
|
|
204
|
+
paths:
|
|
205
|
+
/partially-excluded:
|
|
206
|
+
get:
|
|
207
|
+
description: Should be skipped.
|
|
208
|
+
post:
|
|
209
|
+
description: Has security.
|
|
210
|
+
security: []
|
|
211
|
+
delete:
|
|
212
|
+
description: Should have security defined.`,
|
|
213
|
+
'foobar.yaml'
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const results = await lintDocument({
|
|
217
|
+
externalRefResolver: new BaseResolver(),
|
|
218
|
+
document,
|
|
219
|
+
config: await makeConfig({
|
|
220
|
+
'security-defined': { exceptions: [{ path: '/partially-excluded', methods: ['GET'] }] },
|
|
221
|
+
}),
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
225
|
+
[
|
|
226
|
+
{
|
|
227
|
+
"location": [
|
|
228
|
+
{
|
|
229
|
+
"pointer": "#/paths/~1partially-excluded/delete",
|
|
230
|
+
"reportOnKey": true,
|
|
231
|
+
"source": "foobar.yaml",
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
"message": "Every operation should have security defined on it or on the root level.",
|
|
235
|
+
"ruleId": "security-defined",
|
|
236
|
+
"severity": "error",
|
|
237
|
+
"suggest": [],
|
|
238
|
+
},
|
|
239
|
+
]
|
|
240
|
+
`);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('should report operations from path items that are not excluded', async () => {
|
|
244
|
+
const document = parseYamlToDocument(
|
|
245
|
+
outdent`
|
|
246
|
+
openapi: 3.1.0
|
|
247
|
+
paths:
|
|
248
|
+
/not-excluded:
|
|
249
|
+
get:
|
|
250
|
+
summary: Should have security defined.`,
|
|
251
|
+
'foobar.yaml'
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
const results = await lintDocument({
|
|
255
|
+
externalRefResolver: new BaseResolver(),
|
|
256
|
+
document,
|
|
257
|
+
config: await makeConfig({ 'security-defined': { exceptions: [{ path: '/excluded' }] } }),
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
261
|
+
[
|
|
262
|
+
{
|
|
263
|
+
"location": [
|
|
264
|
+
{
|
|
265
|
+
"pointer": "#/paths/~1not-excluded/get",
|
|
266
|
+
"reportOnKey": true,
|
|
267
|
+
"source": "foobar.yaml",
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
"message": "Every operation should have security defined on it or on the root level.",
|
|
271
|
+
"ruleId": "security-defined",
|
|
272
|
+
"severity": "error",
|
|
273
|
+
"suggest": [],
|
|
274
|
+
},
|
|
275
|
+
]
|
|
276
|
+
`);
|
|
277
|
+
});
|
|
175
278
|
});
|
|
@@ -7,7 +7,7 @@ export const NoInvalidParameterExamples: any = (opts: any) => {
|
|
|
7
7
|
return {
|
|
8
8
|
Parameter: {
|
|
9
9
|
leave(parameter: Oas3Parameter, ctx: UserContext) {
|
|
10
|
-
if (parameter.example) {
|
|
10
|
+
if (parameter.example !== undefined) {
|
|
11
11
|
validateExample(
|
|
12
12
|
parameter.example,
|
|
13
13
|
parameter.schema!,
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { Oas3Rule, Oas2Rule } from '../../visitors';
|
|
2
2
|
import { Location } from '../../ref-utils';
|
|
3
3
|
import { UserContext } from '../../walk';
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
Oas2Definition,
|
|
6
|
+
Oas2Operation,
|
|
7
|
+
Oas2PathItem,
|
|
8
|
+
Oas2SecurityScheme,
|
|
9
|
+
} from '../../typings/swagger';
|
|
10
|
+
import {
|
|
11
|
+
Oas3Definition,
|
|
12
|
+
Oas3Operation,
|
|
13
|
+
Oas3PathItem,
|
|
14
|
+
Oas3SecurityScheme,
|
|
15
|
+
} from '../../typings/openapi';
|
|
6
16
|
|
|
7
|
-
export const SecurityDefined: Oas3Rule | Oas2Rule = (
|
|
17
|
+
export const SecurityDefined: Oas3Rule | Oas2Rule = (opts: {
|
|
18
|
+
exceptions?: { path: string; methods?: string[] }[];
|
|
19
|
+
}) => {
|
|
8
20
|
const referencedSchemes = new Map<
|
|
9
21
|
string,
|
|
10
22
|
{
|
|
@@ -15,6 +27,7 @@ export const SecurityDefined: Oas3Rule | Oas2Rule = () => {
|
|
|
15
27
|
|
|
16
28
|
const operationsWithoutSecurity: Location[] = [];
|
|
17
29
|
let eachOperationHasSecurity: boolean = true;
|
|
30
|
+
let path: string | undefined;
|
|
18
31
|
|
|
19
32
|
return {
|
|
20
33
|
Root: {
|
|
@@ -55,11 +68,21 @@ export const SecurityDefined: Oas3Rule | Oas2Rule = () => {
|
|
|
55
68
|
}
|
|
56
69
|
}
|
|
57
70
|
},
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
71
|
+
PathItem: {
|
|
72
|
+
enter(pathItem: Oas2PathItem | Oas3PathItem, { key }: UserContext) {
|
|
73
|
+
path = key as string;
|
|
74
|
+
},
|
|
75
|
+
Operation(operation: Oas2Operation | Oas3Operation, { location, key }: UserContext) {
|
|
76
|
+
const isException = opts.exceptions?.some(
|
|
77
|
+
(item) =>
|
|
78
|
+
item.path === path &&
|
|
79
|
+
(!item.methods || item.methods?.some((method) => method.toLowerCase() === key))
|
|
80
|
+
);
|
|
81
|
+
if (!operation?.security && !isException) {
|
|
82
|
+
eachOperationHasSecurity = false;
|
|
83
|
+
operationsWithoutSecurity.push(location);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
63
86
|
},
|
|
64
87
|
};
|
|
65
88
|
};
|
|
@@ -137,6 +137,58 @@ describe('no-invalid-media-type-examples', () => {
|
|
|
137
137
|
`);
|
|
138
138
|
});
|
|
139
139
|
|
|
140
|
+
it('should report on invalid example with a falsy value', async () => {
|
|
141
|
+
const document = parseYamlToDocument(
|
|
142
|
+
outdent`
|
|
143
|
+
openapi: 3.1.0
|
|
144
|
+
paths:
|
|
145
|
+
/test:
|
|
146
|
+
get:
|
|
147
|
+
responses:
|
|
148
|
+
'200':
|
|
149
|
+
content:
|
|
150
|
+
application/json:
|
|
151
|
+
schema:
|
|
152
|
+
type: string
|
|
153
|
+
example: false
|
|
154
|
+
`,
|
|
155
|
+
'foobar.yaml'
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const results = await lintDocument({
|
|
159
|
+
externalRefResolver: new BaseResolver(),
|
|
160
|
+
document,
|
|
161
|
+
config: await makeConfig({
|
|
162
|
+
'no-invalid-media-type-examples': {
|
|
163
|
+
severity: 'error',
|
|
164
|
+
allowAdditionalProperties: false,
|
|
165
|
+
},
|
|
166
|
+
}),
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
170
|
+
[
|
|
171
|
+
{
|
|
172
|
+
"from": {
|
|
173
|
+
"pointer": "#/paths/~1test/get/responses/200/content/application~1json",
|
|
174
|
+
"source": "foobar.yaml",
|
|
175
|
+
},
|
|
176
|
+
"location": [
|
|
177
|
+
{
|
|
178
|
+
"pointer": "#/paths/~1test/get/responses/200/content/application~1json/example",
|
|
179
|
+
"reportOnKey": false,
|
|
180
|
+
"source": "foobar.yaml",
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
"message": "Example value must conform to the schema: type must be string.",
|
|
184
|
+
"ruleId": "no-invalid-media-type-examples",
|
|
185
|
+
"severity": "error",
|
|
186
|
+
"suggest": [],
|
|
187
|
+
},
|
|
188
|
+
]
|
|
189
|
+
`);
|
|
190
|
+
});
|
|
191
|
+
|
|
140
192
|
it('should not report on valid example with allowAdditionalProperties', async () => {
|
|
141
193
|
const document = parseYamlToDocument(
|
|
142
194
|
outdent`
|
|
@@ -12,7 +12,7 @@ export const ValidContentExamples: Oas3Rule = (opts) => {
|
|
|
12
12
|
leave(mediaType, ctx: UserContext) {
|
|
13
13
|
const { location, resolve } = ctx;
|
|
14
14
|
if (!mediaType.schema) return;
|
|
15
|
-
if (mediaType.example) {
|
|
15
|
+
if (mediaType.example !== undefined) {
|
|
16
16
|
resolveAndValidateExample(mediaType.example, location.child('example'));
|
|
17
17
|
} else if (mediaType.examples) {
|
|
18
18
|
for (const exampleName of Object.keys(mediaType.examples)) {
|