@redocly/openapi-core 1.0.0-beta.110 → 1.0.0-beta.111
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/config/config-resolvers.js +23 -22
- package/lib/rules/common/no-ambiguous-paths.js +1 -1
- package/lib/rules/common/no-identical-paths.js +4 -4
- package/lib/rules/common/operation-2xx-response.js +2 -2
- package/lib/rules/common/operation-4xx-response.js +2 -2
- package/lib/rules/common/path-not-include-query.js +1 -1
- package/lib/rules/common/path-params-defined.js +7 -2
- package/lib/rules/common/response-contains-header.js +2 -2
- package/lib/rules/common/security-defined.js +10 -5
- package/lib/rules/common/spec.js +14 -12
- package/lib/rules/oas3/request-mime-type.js +1 -1
- package/lib/rules/oas3/response-mime-type.js +1 -1
- package/lib/rules/other/stats.d.ts +1 -1
- package/lib/rules/other/stats.js +1 -1
- package/lib/rules/utils.d.ts +1 -0
- package/lib/rules/utils.js +17 -1
- package/lib/types/oas2.js +6 -6
- package/lib/types/oas3.js +11 -11
- package/lib/types/oas3_1.js +3 -3
- package/lib/types/redocly-yaml.js +14 -4
- package/lib/utils.d.ts +1 -0
- package/lib/utils.js +13 -1
- package/lib/visitors.d.ts +7 -6
- package/lib/visitors.js +11 -3
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/bundle.test.ts.snap +1 -1
- package/src/__tests__/utils.test.ts +11 -0
- package/src/__tests__/walk.test.ts +2 -2
- package/src/config/__tests__/config-resolvers.test.ts +25 -0
- package/src/config/config-resolvers.ts +2 -1
- package/src/rules/common/__tests__/operation-2xx-response.test.ts +37 -0
- package/src/rules/common/__tests__/operation-4xx-response.test.ts +37 -0
- package/src/rules/common/__tests__/path-params-defined.test.ts +69 -0
- package/src/rules/common/__tests__/security-defined.test.ts +6 -6
- package/src/rules/common/__tests__/spec.test.ts +125 -0
- package/src/rules/common/assertions/__tests__/utils.test.ts +2 -2
- package/src/rules/common/no-ambiguous-paths.ts +1 -1
- package/src/rules/common/no-identical-paths.ts +4 -4
- package/src/rules/common/operation-2xx-response.ts +2 -2
- package/src/rules/common/operation-4xx-response.ts +2 -2
- package/src/rules/common/path-not-include-query.ts +1 -1
- package/src/rules/common/path-params-defined.ts +9 -2
- package/src/rules/common/response-contains-header.ts +6 -1
- package/src/rules/common/security-defined.ts +10 -5
- package/src/rules/common/spec.ts +15 -11
- package/src/rules/oas3/__tests__/response-contains-header.test.ts +116 -0
- package/src/rules/oas3/request-mime-type.ts +1 -1
- package/src/rules/oas3/response-mime-type.ts +1 -1
- package/src/rules/other/stats.ts +1 -1
- package/src/rules/utils.ts +22 -0
- package/src/types/oas2.ts +6 -6
- package/src/types/oas3.ts +11 -11
- package/src/types/oas3_1.ts +3 -3
- package/src/types/redocly-yaml.ts +14 -4
- package/src/utils.ts +13 -0
- package/src/visitors.ts +25 -10
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -68,7 +68,7 @@ const nodeTypesList = [
|
|
|
68
68
|
'Info',
|
|
69
69
|
'Contact',
|
|
70
70
|
'License',
|
|
71
|
-
'
|
|
71
|
+
'Paths',
|
|
72
72
|
'PathItem',
|
|
73
73
|
'Parameter',
|
|
74
74
|
'Operation',
|
|
@@ -80,10 +80,10 @@ const nodeTypesList = [
|
|
|
80
80
|
'Example',
|
|
81
81
|
'ExamplesMap',
|
|
82
82
|
'Encoding',
|
|
83
|
-
'
|
|
83
|
+
'EncodingMap',
|
|
84
84
|
'Header',
|
|
85
85
|
'HeadersMap',
|
|
86
|
-
'
|
|
86
|
+
'Responses',
|
|
87
87
|
'Response',
|
|
88
88
|
'Link',
|
|
89
89
|
'LinksMap',
|
|
@@ -106,7 +106,7 @@ const nodeTypesList = [
|
|
|
106
106
|
'PasswordFlow',
|
|
107
107
|
'ClientCredentials',
|
|
108
108
|
'AuthorizationCode',
|
|
109
|
-
'
|
|
109
|
+
'OAuth2Flows',
|
|
110
110
|
'SecurityScheme',
|
|
111
111
|
'XCodeSample',
|
|
112
112
|
'WebhooksMap',
|
|
@@ -758,6 +758,16 @@ const ConfigReferenceDocs = {
|
|
|
758
758
|
unstable_externalDescription: { type: 'boolean' },
|
|
759
759
|
unstable_ignoreMimeParameters: { type: 'boolean' },
|
|
760
760
|
untrustedDefinition: { type: 'boolean' },
|
|
761
|
+
mockServer: {
|
|
762
|
+
properties: {
|
|
763
|
+
url: { type: 'string' },
|
|
764
|
+
position: { enum: ['first', 'last', 'replace', 'off'] },
|
|
765
|
+
description: { type: 'string' },
|
|
766
|
+
},
|
|
767
|
+
},
|
|
768
|
+
showAccessMode: { type: 'boolean' },
|
|
769
|
+
preserveOriginalExtensionsName: { type: 'boolean' },
|
|
770
|
+
markdownHeadingsAnchorLevel: { type: 'number' },
|
|
761
771
|
},
|
|
762
772
|
additionalProperties: { type: 'string' },
|
|
763
773
|
};
|
package/lib/utils.d.ts
CHANGED
|
@@ -46,3 +46,4 @@ export declare function showErrorForDeprecatedField(deprecatedField: string, upd
|
|
|
46
46
|
export declare type Falsy = undefined | null | false | '' | 0;
|
|
47
47
|
export declare function isTruthy<Truthy>(value: Truthy | Falsy): value is Truthy;
|
|
48
48
|
export declare function identity<T>(value: T): T;
|
|
49
|
+
export declare function pickDefined<T extends Record<string, unknown>>(obj?: T): Record<string, unknown> | undefined;
|
package/lib/utils.js
CHANGED
|
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.identity = exports.isTruthy = exports.showErrorForDeprecatedField = exports.showWarningForDeprecatedField = exports.doesYamlFileExist = exports.isCustomRuleId = exports.getMatchingStatusCodeRange = exports.assignExisting = exports.isNotString = exports.isString = exports.isNotEmptyObject = exports.slash = exports.isPathParameter = exports.readFileAsStringSync = exports.isSingular = exports.validateMimeTypeOAS3 = exports.validateMimeType = exports.splitCamelCaseIntoWords = exports.omitObjectProps = exports.pickObjectProps = exports.readFileFromUrl = exports.isEmptyArray = exports.isEmptyObject = exports.isPlainObject = exports.isDefined = exports.loadYaml = exports.popStack = exports.pushStack = exports.stringifyYaml = exports.parseYaml = void 0;
|
|
12
|
+
exports.pickDefined = exports.identity = exports.isTruthy = exports.showErrorForDeprecatedField = exports.showWarningForDeprecatedField = exports.doesYamlFileExist = exports.isCustomRuleId = exports.getMatchingStatusCodeRange = exports.assignExisting = exports.isNotString = exports.isString = exports.isNotEmptyObject = exports.slash = exports.isPathParameter = exports.readFileAsStringSync = exports.isSingular = exports.validateMimeTypeOAS3 = exports.validateMimeType = exports.splitCamelCaseIntoWords = exports.omitObjectProps = exports.pickObjectProps = exports.readFileFromUrl = exports.isEmptyArray = exports.isEmptyObject = exports.isPlainObject = exports.isDefined = exports.loadYaml = exports.popStack = exports.pushStack = exports.stringifyYaml = exports.parseYaml = void 0;
|
|
13
13
|
const fs = require("fs");
|
|
14
14
|
const path_1 = require("path");
|
|
15
15
|
const minimatch = require("minimatch");
|
|
@@ -205,3 +205,15 @@ function identity(value) {
|
|
|
205
205
|
return value;
|
|
206
206
|
}
|
|
207
207
|
exports.identity = identity;
|
|
208
|
+
function pickDefined(obj) {
|
|
209
|
+
if (!obj)
|
|
210
|
+
return undefined;
|
|
211
|
+
const res = {};
|
|
212
|
+
for (const key in obj) {
|
|
213
|
+
if (obj[key] !== undefined) {
|
|
214
|
+
res[key] = obj[key];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return res;
|
|
218
|
+
}
|
|
219
|
+
exports.pickDefined = pickDefined;
|
package/lib/visitors.d.ts
CHANGED
|
@@ -70,9 +70,10 @@ declare type Oas3FlatVisitor = {
|
|
|
70
70
|
Info?: VisitFunctionOrObject<Oas3Info>;
|
|
71
71
|
Contact?: VisitFunctionOrObject<Oas3Contact>;
|
|
72
72
|
License?: VisitFunctionOrObject<Oas3License>;
|
|
73
|
-
|
|
73
|
+
Paths?: VisitFunctionOrObject<Record<string, Oas3PathItem>>;
|
|
74
74
|
PathItem?: VisitFunctionOrObject<Oas3PathItem>;
|
|
75
|
-
Callback?: VisitFunctionOrObject<
|
|
75
|
+
Callback?: VisitFunctionOrObject<Oas3Callback>;
|
|
76
|
+
CallbacksMap?: VisitFunctionOrObject<Record<string, Oas3Callback>>;
|
|
76
77
|
Parameter?: VisitFunctionOrObject<Oas3Parameter>;
|
|
77
78
|
Operation?: VisitFunctionOrObject<Oas3Operation>;
|
|
78
79
|
RequestBody?: VisitFunctionOrObject<Oas3RequestBody>;
|
|
@@ -81,7 +82,7 @@ declare type Oas3FlatVisitor = {
|
|
|
81
82
|
Example?: VisitFunctionOrObject<Oas3Example>;
|
|
82
83
|
Encoding?: VisitFunctionOrObject<Oas3Encoding>;
|
|
83
84
|
Header?: VisitFunctionOrObject<Oas3Header>;
|
|
84
|
-
|
|
85
|
+
Responses?: VisitFunctionOrObject<Record<string, Oas3Response>>;
|
|
85
86
|
Response?: VisitFunctionOrObject<Oas3Response>;
|
|
86
87
|
Link?: VisitFunctionOrObject<Oas3Link>;
|
|
87
88
|
Schema?: VisitFunctionOrObject<Oas3Schema>;
|
|
@@ -103,7 +104,7 @@ declare type Oas3FlatVisitor = {
|
|
|
103
104
|
PasswordFlow?: VisitFunctionOrObject<Oas3SecurityScheme['flows']['password']>;
|
|
104
105
|
ClientCredentials?: VisitFunctionOrObject<Oas3SecurityScheme['flows']['clientCredentials']>;
|
|
105
106
|
AuthorizationCode?: VisitFunctionOrObject<Oas3SecurityScheme['flows']['authorizationCode']>;
|
|
106
|
-
|
|
107
|
+
OAuth2Flows?: VisitFunctionOrObject<Oas3SecurityScheme['flows']>;
|
|
107
108
|
SecurityScheme?: VisitFunctionOrObject<Oas3SecurityScheme>;
|
|
108
109
|
};
|
|
109
110
|
declare type Oas2FlatVisitor = {
|
|
@@ -114,13 +115,13 @@ declare type Oas2FlatVisitor = {
|
|
|
114
115
|
Info?: VisitFunctionOrObject<Oas2Info>;
|
|
115
116
|
Contact?: VisitFunctionOrObject<Oas2Contact>;
|
|
116
117
|
License?: VisitFunctionOrObject<Oas2License>;
|
|
117
|
-
|
|
118
|
+
Paths?: VisitFunctionOrObject<Record<string, Oas2PathItem>>;
|
|
118
119
|
PathItem?: VisitFunctionOrObject<Oas2PathItem>;
|
|
119
120
|
Parameter?: VisitFunctionOrObject<any>;
|
|
120
121
|
Operation?: VisitFunctionOrObject<Oas2Operation>;
|
|
121
122
|
Examples?: VisitFunctionOrObject<Record<string, any>>;
|
|
122
123
|
Header?: VisitFunctionOrObject<Oas2Header>;
|
|
123
|
-
|
|
124
|
+
Responses?: VisitFunctionOrObject<Record<string, Oas2Response>>;
|
|
124
125
|
Response?: VisitFunctionOrObject<Oas2Response>;
|
|
125
126
|
Schema?: VisitFunctionOrObject<Oas2Schema>;
|
|
126
127
|
Xml?: VisitFunctionOrObject<Oas2Xml>;
|
package/lib/visitors.js
CHANGED
|
@@ -4,13 +4,14 @@ exports.normalizeVisitors = void 0;
|
|
|
4
4
|
const legacyTypesMap = {
|
|
5
5
|
Root: 'DefinitionRoot',
|
|
6
6
|
ServerVariablesMap: 'ServerVariableMap',
|
|
7
|
-
|
|
7
|
+
Paths: ['PathMap', 'PathsMap'],
|
|
8
8
|
CallbacksMap: 'CallbackMap',
|
|
9
9
|
MediaTypesMap: 'MediaTypeMap',
|
|
10
10
|
ExamplesMap: 'ExampleMap',
|
|
11
|
-
|
|
11
|
+
EncodingMap: 'EncodingsMap',
|
|
12
12
|
HeadersMap: 'HeaderMap',
|
|
13
13
|
LinksMap: 'LinkMap',
|
|
14
|
+
OAuth2Flows: 'SecuritySchemeFlows',
|
|
14
15
|
};
|
|
15
16
|
function normalizeVisitors(visitorsConfig, types) {
|
|
16
17
|
const normalizedVisitors = {};
|
|
@@ -85,6 +86,13 @@ function normalizeVisitors(visitorsConfig, types) {
|
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
88
|
}
|
|
89
|
+
function findLegacyVisitorNode(visitor, typeName) {
|
|
90
|
+
if (Array.isArray(typeName)) {
|
|
91
|
+
const name = typeName.find((name) => visitor[name]) || undefined;
|
|
92
|
+
return name && visitor[name];
|
|
93
|
+
}
|
|
94
|
+
return visitor[typeName];
|
|
95
|
+
}
|
|
88
96
|
function normalizeVisitorLevel(ruleConf, visitor, parentContext, depth = 0) {
|
|
89
97
|
const visitorKeys = Object.keys(types);
|
|
90
98
|
if (depth === 0) {
|
|
@@ -101,7 +109,7 @@ function normalizeVisitors(visitorsConfig, types) {
|
|
|
101
109
|
}
|
|
102
110
|
for (const typeName of visitorKeys) {
|
|
103
111
|
const typeVisitor = (visitor[typeName] ||
|
|
104
|
-
visitor
|
|
112
|
+
findLegacyVisitorNode(visitor, legacyTypesMap[typeName]));
|
|
105
113
|
const normalizedTypeVisitor = normalizedVisitors[typeName];
|
|
106
114
|
if (!typeVisitor)
|
|
107
115
|
continue;
|
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
slash,
|
|
5
5
|
getMatchingStatusCodeRange,
|
|
6
6
|
doesYamlFileExist,
|
|
7
|
+
pickDefined,
|
|
7
8
|
} from '../utils';
|
|
8
9
|
import { isBrowser } from '../env';
|
|
9
10
|
import * as fs from 'fs';
|
|
@@ -81,6 +82,16 @@ describe('utils', () => {
|
|
|
81
82
|
});
|
|
82
83
|
});
|
|
83
84
|
|
|
85
|
+
describe('pickDefined', () => {
|
|
86
|
+
it('returns undefined for undefined', () => {
|
|
87
|
+
expect(pickDefined(undefined)).toBeUndefined();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('picks only defined values', () => {
|
|
91
|
+
expect(pickDefined({ a: 1, b: undefined, c: 3 })).toStrictEqual({ a: 1, c: 3 });
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
84
95
|
describe('getMatchingStatusCodeRange', () => {
|
|
85
96
|
it('should get the generalized form of status codes', () => {
|
|
86
97
|
expect(getMatchingStatusCodeRange('202')).toEqual('2XX');
|
|
@@ -1115,7 +1115,7 @@ describe('walk order', () => {
|
|
|
1115
1115
|
expect(calls).toMatchInlineSnapshot(`
|
|
1116
1116
|
Array [
|
|
1117
1117
|
"enter Root",
|
|
1118
|
-
"enter
|
|
1118
|
+
"enter Paths",
|
|
1119
1119
|
"enter PathItem",
|
|
1120
1120
|
"enter ParameterList",
|
|
1121
1121
|
"enter Parameter",
|
|
@@ -1134,7 +1134,7 @@ describe('walk order', () => {
|
|
|
1134
1134
|
"leave ParameterList",
|
|
1135
1135
|
"leave Operation",
|
|
1136
1136
|
"leave PathItem",
|
|
1137
|
-
"leave
|
|
1137
|
+
"leave Paths",
|
|
1138
1138
|
"enter Components",
|
|
1139
1139
|
"enter NamedParameters",
|
|
1140
1140
|
"leave NamedParameters",
|
|
@@ -464,4 +464,29 @@ describe('resolveConfig', () => {
|
|
|
464
464
|
delete apis['petstore'].styleguide.pluginPaths;
|
|
465
465
|
expect(apis['petstore'].styleguide).toMatchSnapshot();
|
|
466
466
|
});
|
|
467
|
+
|
|
468
|
+
it('should default to the extends from the main config if no extends defined', async () => {
|
|
469
|
+
const rawConfig: RawConfig = {
|
|
470
|
+
apis: {
|
|
471
|
+
petstore: {
|
|
472
|
+
root: 'some/path',
|
|
473
|
+
styleguide: {
|
|
474
|
+
rules: {
|
|
475
|
+
'operation-4xx-response': 'error',
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
styleguide: {
|
|
481
|
+
extends: ['minimal'],
|
|
482
|
+
rules: {
|
|
483
|
+
'operation-2xx-response': 'warn',
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
const { apis } = await resolveConfig(rawConfig, configPath);
|
|
489
|
+
expect(apis['petstore'].styleguide.rules).toBeDefined();
|
|
490
|
+
expect(apis['petstore'].styleguide.rules?.['operation-2xx-response']).toEqual('warn'); // from minimal ruleset
|
|
491
|
+
});
|
|
467
492
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
2
|
import { isAbsoluteUrl } from '../ref-utils';
|
|
3
|
+
import { pickDefined } from '../utils';
|
|
3
4
|
import { BaseResolver } from '../resolve';
|
|
4
5
|
import { defaultPlugin } from './builtIn';
|
|
5
6
|
import {
|
|
@@ -355,7 +356,7 @@ function getMergedRawStyleguideConfig(
|
|
|
355
356
|
) {
|
|
356
357
|
const resultLint = {
|
|
357
358
|
...rootStyleguideConfig,
|
|
358
|
-
...apiStyleguideConfig,
|
|
359
|
+
...pickDefined(apiStyleguideConfig),
|
|
359
360
|
rules: { ...rootStyleguideConfig?.rules, ...apiStyleguideConfig?.rules },
|
|
360
361
|
oas2Rules: { ...rootStyleguideConfig?.oas2Rules, ...apiStyleguideConfig?.oas2Rules },
|
|
361
362
|
oas3_0Rules: { ...rootStyleguideConfig?.oas3_0Rules, ...apiStyleguideConfig?.oas3_0Rules },
|
|
@@ -88,4 +88,41 @@ describe('Oas3 operation-2xx-response', () => {
|
|
|
88
88
|
|
|
89
89
|
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
|
|
90
90
|
});
|
|
91
|
+
|
|
92
|
+
it('should report even if the responses are null', async () => {
|
|
93
|
+
const document = parseYamlToDocument(
|
|
94
|
+
outdent`
|
|
95
|
+
openapi: 3.0.0
|
|
96
|
+
paths:
|
|
97
|
+
'/test/':
|
|
98
|
+
put:
|
|
99
|
+
responses: null
|
|
100
|
+
`,
|
|
101
|
+
'foobar.yaml'
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const results = await lintDocument({
|
|
105
|
+
externalRefResolver: new BaseResolver(),
|
|
106
|
+
document,
|
|
107
|
+
config: await makeConfig({ 'operation-2xx-response': 'error' }),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
111
|
+
Array [
|
|
112
|
+
Object {
|
|
113
|
+
"location": Array [
|
|
114
|
+
Object {
|
|
115
|
+
"pointer": "#/paths/~1test~1/put/responses",
|
|
116
|
+
"reportOnKey": true,
|
|
117
|
+
"source": "foobar.yaml",
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
"message": "Operation must have at least one \`2XX\` response.",
|
|
121
|
+
"ruleId": "operation-2xx-response",
|
|
122
|
+
"severity": "error",
|
|
123
|
+
"suggest": Array [],
|
|
124
|
+
},
|
|
125
|
+
]
|
|
126
|
+
`);
|
|
127
|
+
});
|
|
91
128
|
});
|
|
@@ -127,4 +127,41 @@ describe('Oas3 operation-4xx-response', () => {
|
|
|
127
127
|
]
|
|
128
128
|
`);
|
|
129
129
|
});
|
|
130
|
+
|
|
131
|
+
it('should report even if the responses are null', async () => {
|
|
132
|
+
const document = parseYamlToDocument(
|
|
133
|
+
outdent`
|
|
134
|
+
openapi: 3.0.0
|
|
135
|
+
paths:
|
|
136
|
+
'/test/':
|
|
137
|
+
put:
|
|
138
|
+
responses: null
|
|
139
|
+
`,
|
|
140
|
+
'foobar.yaml'
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const results = await lintDocument({
|
|
144
|
+
externalRefResolver: new BaseResolver(),
|
|
145
|
+
document,
|
|
146
|
+
config: await makeConfig({ 'operation-2xx-response': 'error' }),
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
150
|
+
Array [
|
|
151
|
+
Object {
|
|
152
|
+
"location": Array [
|
|
153
|
+
Object {
|
|
154
|
+
"pointer": "#/paths/~1test~1/put/responses",
|
|
155
|
+
"reportOnKey": true,
|
|
156
|
+
"source": "foobar.yaml",
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
"message": "Operation must have at least one \`2XX\` response.",
|
|
160
|
+
"ruleId": "operation-2xx-response",
|
|
161
|
+
"severity": "error",
|
|
162
|
+
"suggest": Array [],
|
|
163
|
+
},
|
|
164
|
+
]
|
|
165
|
+
`);
|
|
166
|
+
});
|
|
130
167
|
});
|
|
@@ -130,4 +130,73 @@ describe('Oas3 path-params-defined', () => {
|
|
|
130
130
|
]
|
|
131
131
|
`);
|
|
132
132
|
});
|
|
133
|
+
|
|
134
|
+
it('should fail cause POST has no parameters', async () => {
|
|
135
|
+
const document = parseYamlToDocument(
|
|
136
|
+
outdent`
|
|
137
|
+
openapi: 3.0.0
|
|
138
|
+
paths:
|
|
139
|
+
/pets/{a}:
|
|
140
|
+
get:
|
|
141
|
+
parameters:
|
|
142
|
+
- name: a
|
|
143
|
+
in: path
|
|
144
|
+
post:
|
|
145
|
+
description: without parameters
|
|
146
|
+
`,
|
|
147
|
+
'foobar.yaml'
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const results = await lintDocument({
|
|
151
|
+
externalRefResolver: new BaseResolver(),
|
|
152
|
+
document,
|
|
153
|
+
config: await makeConfig({ 'path-params-defined': 'error' }),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
157
|
+
Array [
|
|
158
|
+
Object {
|
|
159
|
+
"location": Array [
|
|
160
|
+
Object {
|
|
161
|
+
"pointer": "#/paths/~1pets~1{a}/post/parameters",
|
|
162
|
+
"reportOnKey": true,
|
|
163
|
+
"source": "foobar.yaml",
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
"message": "The operation does not define the path parameter \`{a}\` expected by path \`/pets/{a}\`.",
|
|
167
|
+
"ruleId": "path-params-defined",
|
|
168
|
+
"severity": "error",
|
|
169
|
+
"suggest": Array [],
|
|
170
|
+
},
|
|
171
|
+
]
|
|
172
|
+
`);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should apply parameters for POST operation from path parameters', async () => {
|
|
176
|
+
const document = parseYamlToDocument(
|
|
177
|
+
outdent`
|
|
178
|
+
openapi: 3.0.0
|
|
179
|
+
paths:
|
|
180
|
+
/pets/{a}:
|
|
181
|
+
parameters:
|
|
182
|
+
- name: a
|
|
183
|
+
in: path
|
|
184
|
+
get:
|
|
185
|
+
parameters:
|
|
186
|
+
- name: a
|
|
187
|
+
in: path
|
|
188
|
+
post:
|
|
189
|
+
description: without parameters
|
|
190
|
+
`,
|
|
191
|
+
'foobar.yaml'
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const results = await lintDocument({
|
|
195
|
+
externalRefResolver: new BaseResolver(),
|
|
196
|
+
document,
|
|
197
|
+
config: await makeConfig({ 'path-params-defined': 'error' }),
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
|
|
201
|
+
});
|
|
133
202
|
});
|
|
@@ -81,12 +81,12 @@ describe('Oas3 security-defined', () => {
|
|
|
81
81
|
Object {
|
|
82
82
|
"location": Array [
|
|
83
83
|
Object {
|
|
84
|
-
"pointer": "#/",
|
|
85
|
-
"reportOnKey":
|
|
84
|
+
"pointer": "#/paths/~1pets/get",
|
|
85
|
+
"reportOnKey": true,
|
|
86
86
|
"source": "foobar.yaml",
|
|
87
87
|
},
|
|
88
88
|
],
|
|
89
|
-
"message": "Every
|
|
89
|
+
"message": "Every operation should have security defined on it or on the root level.",
|
|
90
90
|
"ruleId": "security-defined",
|
|
91
91
|
"severity": "error",
|
|
92
92
|
"suggest": Array [],
|
|
@@ -133,12 +133,12 @@ describe('Oas3 security-defined', () => {
|
|
|
133
133
|
Object {
|
|
134
134
|
"location": Array [
|
|
135
135
|
Object {
|
|
136
|
-
"pointer": "#/",
|
|
137
|
-
"reportOnKey":
|
|
136
|
+
"pointer": "#/paths/~1cats/get",
|
|
137
|
+
"reportOnKey": true,
|
|
138
138
|
"source": "foobar.yaml",
|
|
139
139
|
},
|
|
140
140
|
],
|
|
141
|
-
"message": "Every
|
|
141
|
+
"message": "Every operation should have security defined on it or on the root level.",
|
|
142
142
|
"ruleId": "security-defined",
|
|
143
143
|
"severity": "error",
|
|
144
144
|
"suggest": Array [],
|
|
@@ -139,3 +139,128 @@ describe('Oas3 spec', () => {
|
|
|
139
139
|
`);
|
|
140
140
|
});
|
|
141
141
|
});
|
|
142
|
+
|
|
143
|
+
describe('Oas3.1 spec', () => {
|
|
144
|
+
it('should report with "type can be one of the following only"', async () => {
|
|
145
|
+
const document = parseYamlToDocument(
|
|
146
|
+
outdent`
|
|
147
|
+
openapi: 3.1.0
|
|
148
|
+
info:
|
|
149
|
+
version: 1.0.0
|
|
150
|
+
title: Example.com
|
|
151
|
+
description: info,
|
|
152
|
+
license:
|
|
153
|
+
name: Apache 2.0
|
|
154
|
+
url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
|
|
155
|
+
components:
|
|
156
|
+
schemas:
|
|
157
|
+
TestSchema:
|
|
158
|
+
title: TestSchema
|
|
159
|
+
description: Property name's description
|
|
160
|
+
type: test
|
|
161
|
+
`
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const results = await lintDocument({
|
|
165
|
+
externalRefResolver: new BaseResolver(),
|
|
166
|
+
document,
|
|
167
|
+
config: await makeConfig({ spec: 'error' }),
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
171
|
+
Array [
|
|
172
|
+
Object {
|
|
173
|
+
"from": undefined,
|
|
174
|
+
"location": Array [
|
|
175
|
+
Object {
|
|
176
|
+
"pointer": "#/components/schemas/TestSchema/type",
|
|
177
|
+
"reportOnKey": false,
|
|
178
|
+
"source": "",
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
"message": "\`type\` can be one of the following only: \\"object\\", \\"array\\", \\"string\\", \\"number\\", \\"integer\\", \\"boolean\\", \\"null\\".",
|
|
182
|
+
"ruleId": "spec",
|
|
183
|
+
"severity": "error",
|
|
184
|
+
"suggest": Array [],
|
|
185
|
+
},
|
|
186
|
+
]
|
|
187
|
+
`);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should report with unknown type in type`s list', async () => {
|
|
191
|
+
const document = parseYamlToDocument(
|
|
192
|
+
outdent`
|
|
193
|
+
openapi: 3.1.0
|
|
194
|
+
info:
|
|
195
|
+
version: 1.0.0
|
|
196
|
+
title: Example.com
|
|
197
|
+
description: info,
|
|
198
|
+
license:
|
|
199
|
+
name: Apache 2.0
|
|
200
|
+
url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
|
|
201
|
+
components:
|
|
202
|
+
schemas:
|
|
203
|
+
TestSchema:
|
|
204
|
+
title: TestSchema
|
|
205
|
+
description: Property name's description
|
|
206
|
+
type:
|
|
207
|
+
- string
|
|
208
|
+
- foo
|
|
209
|
+
`
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
const results = await lintDocument({
|
|
213
|
+
externalRefResolver: new BaseResolver(),
|
|
214
|
+
document,
|
|
215
|
+
config: await makeConfig({ spec: 'error' }),
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
219
|
+
Array [
|
|
220
|
+
Object {
|
|
221
|
+
"from": undefined,
|
|
222
|
+
"location": Array [
|
|
223
|
+
Object {
|
|
224
|
+
"pointer": "#/components/schemas/TestSchema/type/1",
|
|
225
|
+
"reportOnKey": false,
|
|
226
|
+
"source": "",
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
"message": "\`type\` can be one of the following only: \\"object\\", \\"array\\", \\"string\\", \\"number\\", \\"integer\\", \\"boolean\\", \\"null\\".",
|
|
230
|
+
"ruleId": "spec",
|
|
231
|
+
"severity": "error",
|
|
232
|
+
"suggest": Array [],
|
|
233
|
+
},
|
|
234
|
+
]
|
|
235
|
+
`);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should not report about unknown type', async () => {
|
|
239
|
+
const document = parseYamlToDocument(
|
|
240
|
+
outdent`
|
|
241
|
+
openapi: 3.1.0
|
|
242
|
+
info:
|
|
243
|
+
version: 1.0.0
|
|
244
|
+
title: Example.com
|
|
245
|
+
description: info,
|
|
246
|
+
license:
|
|
247
|
+
name: Apache 2.0
|
|
248
|
+
url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
|
|
249
|
+
components:
|
|
250
|
+
schemas:
|
|
251
|
+
TestSchema:
|
|
252
|
+
title: TestSchema
|
|
253
|
+
description: Property name's description
|
|
254
|
+
type: null
|
|
255
|
+
`
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const results = await lintDocument({
|
|
259
|
+
externalRefResolver: new BaseResolver(),
|
|
260
|
+
document,
|
|
261
|
+
config: await makeConfig({ spec: 'error' }),
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
@@ -65,7 +65,7 @@ describe('Oas3 assertions', () => {
|
|
|
65
65
|
matchParentKeys: ['put'],
|
|
66
66
|
},
|
|
67
67
|
{
|
|
68
|
-
type: '
|
|
68
|
+
type: 'Responses',
|
|
69
69
|
matchParentKeys: [201, 200],
|
|
70
70
|
},
|
|
71
71
|
];
|
|
@@ -75,7 +75,7 @@ describe('Oas3 assertions', () => {
|
|
|
75
75
|
expect(visitors).toMatchInlineSnapshot(`
|
|
76
76
|
Object {
|
|
77
77
|
"Operation": Object {
|
|
78
|
-
"
|
|
78
|
+
"Responses": Object {
|
|
79
79
|
"MediaTypesMap": [Function],
|
|
80
80
|
"skip": [Function],
|
|
81
81
|
},
|
|
@@ -5,7 +5,7 @@ import { Oas2Paths } from '../../typings/swagger';
|
|
|
5
5
|
|
|
6
6
|
export const NoAmbiguousPaths: Oas3Rule | Oas2Rule = () => {
|
|
7
7
|
return {
|
|
8
|
-
|
|
8
|
+
Paths(pathMap: Oas3Paths | Oas2Paths, { report, location }: UserContext) {
|
|
9
9
|
const seenPaths: string[] = [];
|
|
10
10
|
|
|
11
11
|
for (const currentPath of Object.keys(pathMap)) {
|
|
@@ -5,18 +5,18 @@ import { Oas2Paths } from '../../typings/swagger';
|
|
|
5
5
|
|
|
6
6
|
export const NoIdenticalPaths: Oas3Rule | Oas2Rule = () => {
|
|
7
7
|
return {
|
|
8
|
-
|
|
9
|
-
const
|
|
8
|
+
Paths(pathMap: Oas3Paths | Oas2Paths, { report, location }: UserContext) {
|
|
9
|
+
const Paths = new Map<string, string>();
|
|
10
10
|
for (const pathName of Object.keys(pathMap)) {
|
|
11
11
|
const id = pathName.replace(/{.+?}/g, '{VARIABLE}');
|
|
12
|
-
const existingSamePath =
|
|
12
|
+
const existingSamePath = Paths.get(id);
|
|
13
13
|
if (existingSamePath) {
|
|
14
14
|
report({
|
|
15
15
|
message: `The path already exists which differs only by path parameter name(s): \`${existingSamePath}\` and \`${pathName}\`.`,
|
|
16
16
|
location: location.child([pathName]).key(),
|
|
17
17
|
});
|
|
18
18
|
} else {
|
|
19
|
-
|
|
19
|
+
Paths.set(id, pathName);
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
},
|
|
@@ -3,8 +3,8 @@ import { UserContext } from '../../walk';
|
|
|
3
3
|
|
|
4
4
|
export const Operation2xxResponse: Oas3Rule | Oas2Rule = () => {
|
|
5
5
|
return {
|
|
6
|
-
|
|
7
|
-
const codes = Object.keys(responses);
|
|
6
|
+
Responses(responses: Record<string, object>, { report }: UserContext) {
|
|
7
|
+
const codes = Object.keys(responses || {});
|
|
8
8
|
if (!codes.some((code) => code === 'default' || /2[Xx0-9]{2}/.test(code))) {
|
|
9
9
|
report({
|
|
10
10
|
message: 'Operation must have at least one `2XX` response.',
|