@redocly/openapi-core 1.0.0-beta.65 → 1.0.0-beta.69
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/__tests__/__snapshots__/bundle.test.ts.snap +126 -0
- package/__tests__/bundle.test.ts +53 -1
- package/__tests__/fixtures/refs/definitions.yaml +3 -0
- package/__tests__/fixtures/refs/external-request-body.yaml +13 -0
- package/__tests__/fixtures/refs/externalref.yaml +35 -0
- package/__tests__/fixtures/refs/hosted.yaml +35 -0
- package/__tests__/fixtures/refs/rename.yaml +1 -0
- package/__tests__/fixtures/refs/requestBody.yaml +9 -0
- package/__tests__/fixtures/refs/simple.yaml +1 -0
- package/__tests__/fixtures/refs/vendor.schema.yaml +20 -0
- package/lib/bundle.d.ts +4 -0
- package/lib/bundle.js +25 -7
- package/lib/config/all.js +9 -1
- package/lib/config/config.js +1 -1
- package/lib/config/minimal.js +1 -0
- package/lib/config/recommended.js +1 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +2 -1
- package/lib/lint.js +2 -0
- package/lib/redocly/index.d.ts +3 -14
- package/lib/redocly/index.js +19 -186
- package/lib/redocly/registry-api-types.d.ts +28 -0
- package/lib/redocly/registry-api-types.js +2 -0
- package/lib/redocly/registry-api.d.ts +11 -0
- package/lib/redocly/registry-api.js +94 -0
- package/lib/ref-utils.js +1 -2
- package/lib/rules/common/info-license-url.js +1 -0
- package/lib/rules/common/no-http-verbs-in-paths.d.ts +2 -0
- package/lib/rules/common/no-http-verbs-in-paths.js +33 -0
- package/lib/rules/common/operation-4xx-response.d.ts +2 -0
- package/lib/rules/common/operation-4xx-response.js +17 -0
- package/lib/rules/common/path-excludes-patterns.d.ts +2 -0
- package/lib/rules/common/path-excludes-patterns.js +22 -0
- package/lib/rules/common/path-segment-plural.d.ts +2 -0
- package/lib/rules/common/path-segment-plural.js +32 -0
- package/lib/rules/common/registry-dependencies.js +4 -7
- package/lib/rules/oas2/index.d.ts +6 -0
- package/lib/rules/oas2/index.js +12 -0
- package/lib/rules/oas2/request-mime-type.d.ts +2 -0
- package/lib/rules/oas2/request-mime-type.js +17 -0
- package/lib/rules/oas2/response-mime-type.d.ts +2 -0
- package/lib/rules/oas2/response-mime-type.js +17 -0
- package/lib/rules/oas3/index.js +12 -0
- package/lib/rules/oas3/no-server-trailing-slash.js +1 -1
- package/lib/rules/oas3/request-mime-type.d.ts +2 -0
- package/lib/rules/oas3/request-mime-type.js +31 -0
- package/lib/rules/oas3/response-mime-type.d.ts +2 -0
- package/lib/rules/oas3/response-mime-type.js +31 -0
- package/lib/types/oas3_1.js +6 -0
- package/lib/utils.d.ts +10 -0
- package/lib/utils.js +65 -1
- package/lib/walk.d.ts +2 -0
- package/lib/walk.js +7 -0
- package/package.json +5 -3
- package/src/__tests__/utils.test.ts +19 -1
- package/src/bundle.ts +51 -9
- package/src/config/all.ts +9 -1
- package/src/config/config.ts +2 -2
- package/src/config/minimal.ts +1 -0
- package/src/config/recommended.ts +1 -0
- package/src/index.ts +1 -1
- package/src/lint.ts +2 -0
- package/src/redocly/index.ts +17 -194
- package/src/redocly/registry-api-types.ts +31 -0
- package/src/redocly/registry-api.ts +106 -0
- package/src/ref-utils.ts +1 -3
- package/src/rules/common/__tests__/info-license.test.ts +1 -1
- package/src/rules/common/__tests__/operation-4xx-response.test.ts +108 -0
- package/src/rules/common/info-license-url.ts +1 -0
- package/src/rules/common/no-http-verbs-in-paths.ts +36 -0
- package/src/rules/common/operation-4xx-response.ts +17 -0
- package/src/rules/common/path-excludes-patterns.ts +23 -0
- package/src/rules/common/path-segment-plural.ts +31 -0
- package/src/rules/common/registry-dependencies.ts +6 -8
- package/src/rules/oas2/index.ts +12 -0
- package/src/rules/oas2/request-mime-type.ts +17 -0
- package/src/rules/oas2/response-mime-type.ts +17 -0
- package/src/rules/oas3/__tests__/no-server-trailing-slash.test.ts +19 -0
- package/src/rules/oas3/index.ts +12 -0
- package/src/rules/oas3/no-server-trailing-slash.ts +1 -1
- package/src/rules/oas3/request-mime-type.ts +31 -0
- package/src/rules/oas3/response-mime-type.ts +31 -0
- package/src/rules/utils.ts +1 -1
- package/src/types/oas3_1.ts +7 -0
- package/src/utils.ts +75 -0
- package/src/walk.ts +10 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/lib/redocly/query.d.ts +0 -4
- package/lib/redocly/query.js +0 -44
- package/src/redocly/query.ts +0 -38
package/lib/rules/oas3/index.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.decorators = exports.preprocessors = exports.rules = void 0;
|
|
4
4
|
const spec_1 = require("../common/spec");
|
|
5
5
|
const operation_2xx_response_1 = require("../common/operation-2xx-response");
|
|
6
|
+
const operation_4xx_response_1 = require("../common/operation-4xx-response");
|
|
6
7
|
const operation_operationId_unique_1 = require("../common/operation-operationId-unique");
|
|
7
8
|
const operation_parameters_unique_1 = require("../common/operation-parameters-unique");
|
|
8
9
|
const path_params_defined_1 = require("../common/path-params-defined");
|
|
@@ -39,9 +40,14 @@ const operation_operationId_1 = require("../common/operation-operationId");
|
|
|
39
40
|
const operation_summary_1 = require("../common/operation-summary");
|
|
40
41
|
const no_ambiguous_paths_1 = require("../common/no-ambiguous-paths");
|
|
41
42
|
const no_servers_empty_enum_1 = require("./no-servers-empty-enum");
|
|
43
|
+
const no_http_verbs_in_paths_1 = require("../common/no-http-verbs-in-paths");
|
|
44
|
+
const request_mime_type_1 = require("./request-mime-type");
|
|
45
|
+
const response_mime_type_1 = require("./response-mime-type");
|
|
46
|
+
const path_segment_plural_1 = require("../common/path-segment-plural");
|
|
42
47
|
const operation_description_override_1 = require("../common/operation-description-override");
|
|
43
48
|
const tag_description_override_1 = require("../common/tag-description-override");
|
|
44
49
|
const info_description_override_1 = require("../common/info-description-override");
|
|
50
|
+
const path_excludes_patterns_1 = require("../common/path-excludes-patterns");
|
|
45
51
|
exports.rules = {
|
|
46
52
|
spec: spec_1.OasSpec,
|
|
47
53
|
'info-description': info_description_1.InfoDescription,
|
|
@@ -49,6 +55,7 @@ exports.rules = {
|
|
|
49
55
|
'info-license': info_license_url_1.InfoLicense,
|
|
50
56
|
'info-license-url': license_url_1.InfoLicenseUrl,
|
|
51
57
|
'operation-2xx-response': operation_2xx_response_1.Operation2xxResponse,
|
|
58
|
+
'operation-4xx-response': operation_4xx_response_1.Operation4xxResponse,
|
|
52
59
|
'operation-operationId-unique': operation_operationId_unique_1.OperationIdUnique,
|
|
53
60
|
'operation-parameters-unique': operation_parameters_unique_1.OperationParametersUnique,
|
|
54
61
|
'path-parameters-defined': path_params_defined_1.PathParamsDefined,
|
|
@@ -81,6 +88,11 @@ exports.rules = {
|
|
|
81
88
|
'no-ambiguous-paths': no_ambiguous_paths_1.NoAmbiguousPaths,
|
|
82
89
|
'no-undefined-server-variable': no_undefined_server_variable_1.NoUndefinedServerVariable,
|
|
83
90
|
'no-servers-empty-enum': no_servers_empty_enum_1.NoEmptyEnumServers,
|
|
91
|
+
'no-http-verbs-in-paths': no_http_verbs_in_paths_1.NoHttpVerbsInPaths,
|
|
92
|
+
'path-excludes-patterns': path_excludes_patterns_1.PathExcludesPatterns,
|
|
93
|
+
'request-mime-type': request_mime_type_1.RequestMimeType,
|
|
94
|
+
'response-mime-type': response_mime_type_1.ResponseMimeType,
|
|
95
|
+
'path-segment-plural': path_segment_plural_1.PathSegmentPlural,
|
|
84
96
|
};
|
|
85
97
|
exports.preprocessors = {};
|
|
86
98
|
exports.decorators = {
|
|
@@ -6,7 +6,7 @@ const NoServerTrailingSlash = () => {
|
|
|
6
6
|
Server(server, { report, location }) {
|
|
7
7
|
if (!server.url)
|
|
8
8
|
return;
|
|
9
|
-
if (server.url.endsWith('/')) {
|
|
9
|
+
if (server.url.endsWith('/') && server.url !== '/') {
|
|
10
10
|
report({
|
|
11
11
|
message: 'Server `url` should not have a trailing slash.',
|
|
12
12
|
location: location.child(['url']),
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RequestMimeType = void 0;
|
|
4
|
+
const utils_1 = require("../../utils");
|
|
5
|
+
const RequestMimeType = ({ allowedValues }) => {
|
|
6
|
+
return {
|
|
7
|
+
PathMap: {
|
|
8
|
+
RequestBody: {
|
|
9
|
+
leave(requestBody, ctx) {
|
|
10
|
+
utils_1.validateMimeTypeOAS3({ type: 'consumes', value: requestBody }, ctx, allowedValues);
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
Callback: {
|
|
14
|
+
RequestBody() { },
|
|
15
|
+
Response: {
|
|
16
|
+
leave(response, ctx) {
|
|
17
|
+
utils_1.validateMimeTypeOAS3({ type: 'consumes', value: response }, ctx, allowedValues);
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
WebhooksMap: {
|
|
23
|
+
Response: {
|
|
24
|
+
leave(response, ctx) {
|
|
25
|
+
utils_1.validateMimeTypeOAS3({ type: 'consumes', value: response }, ctx, allowedValues);
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
exports.RequestMimeType = RequestMimeType;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ResponseMimeType = void 0;
|
|
4
|
+
const utils_1 = require("../../utils");
|
|
5
|
+
const ResponseMimeType = ({ allowedValues }) => {
|
|
6
|
+
return {
|
|
7
|
+
PathMap: {
|
|
8
|
+
Response: {
|
|
9
|
+
leave(response, ctx) {
|
|
10
|
+
utils_1.validateMimeTypeOAS3({ type: 'produces', value: response }, ctx, allowedValues);
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
Callback: {
|
|
14
|
+
Response() { },
|
|
15
|
+
RequestBody: {
|
|
16
|
+
leave(requestBody, ctx) {
|
|
17
|
+
utils_1.validateMimeTypeOAS3({ type: 'produces', value: requestBody }, ctx, allowedValues);
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
WebhooksMap: {
|
|
23
|
+
RequestBody: {
|
|
24
|
+
leave(requestBody, ctx) {
|
|
25
|
+
utils_1.validateMimeTypeOAS3({ type: 'produces', value: requestBody }, ctx, allowedValues);
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
exports.ResponseMimeType = ResponseMimeType;
|
package/lib/types/oas3_1.js
CHANGED
|
@@ -94,6 +94,12 @@ const Operation = {
|
|
|
94
94
|
};
|
|
95
95
|
const Schema = {
|
|
96
96
|
properties: {
|
|
97
|
+
$id: { type: 'string' },
|
|
98
|
+
id: { type: 'string' },
|
|
99
|
+
$schema: { type: 'string' },
|
|
100
|
+
definitions: 'NamedSchemas',
|
|
101
|
+
$defs: 'NamedSchemas',
|
|
102
|
+
$vocabulary: { type: 'string' },
|
|
97
103
|
externalDocs: 'ExternalDocs',
|
|
98
104
|
discriminator: 'Discriminator',
|
|
99
105
|
myArbitraryKeyword: { type: 'boolean' },
|
package/lib/utils.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HttpResolveConfig } from './config/config';
|
|
2
|
+
import { UserContext } from './walk';
|
|
2
3
|
export { parseYaml, stringifyYaml } from './js-yaml';
|
|
3
4
|
export declare type StackFrame<T> = {
|
|
4
5
|
prev: StackFrame<T> | null;
|
|
@@ -22,4 +23,13 @@ export declare function readFileFromUrl(url: string, config: HttpResolveConfig):
|
|
|
22
23
|
export declare function match(url: string, pattern: string): boolean;
|
|
23
24
|
export declare function pickObjectProps<T extends Record<string, unknown>>(object: T, keys: Array<string>): T;
|
|
24
25
|
export declare function omitObjectProps<T extends Record<string, unknown>>(object: T, keys: Array<string>): T;
|
|
26
|
+
export declare function splitCamelCaseIntoWords(str: string): Set<string>;
|
|
27
|
+
export declare function validateMimeType({ type, value }: any, { report, location }: UserContext, allowedValues: string[]): void;
|
|
28
|
+
export declare function validateMimeTypeOAS3({ type, value }: any, { report, location }: UserContext, allowedValues: string[]): void;
|
|
29
|
+
export declare function isSingular(path: string): boolean;
|
|
25
30
|
export declare function readFileAsStringSync(filePath: string): string;
|
|
31
|
+
export declare function isPathParameter(pathSegment: string): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Convert Windows backslash paths to slash paths: foo\\bar ➔ foo/bar
|
|
34
|
+
*/
|
|
35
|
+
export declare function slash(path: string): string;
|
package/lib/utils.js
CHANGED
|
@@ -9,10 +9,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.readFileAsStringSync = exports.omitObjectProps = exports.pickObjectProps = exports.match = exports.readFileFromUrl = exports.isPlainObject = exports.notUndefined = exports.loadYaml = exports.popStack = exports.pushStack = exports.stringifyYaml = exports.parseYaml = void 0;
|
|
12
|
+
exports.slash = exports.isPathParameter = exports.readFileAsStringSync = exports.isSingular = exports.validateMimeTypeOAS3 = exports.validateMimeType = exports.splitCamelCaseIntoWords = exports.omitObjectProps = exports.pickObjectProps = exports.match = exports.readFileFromUrl = exports.isPlainObject = exports.notUndefined = exports.loadYaml = exports.popStack = exports.pushStack = exports.stringifyYaml = exports.parseYaml = void 0;
|
|
13
13
|
const fs = require("fs");
|
|
14
14
|
const minimatch = require("minimatch");
|
|
15
15
|
const node_fetch_1 = require("node-fetch");
|
|
16
|
+
const pluralize = require("pluralize");
|
|
16
17
|
const js_yaml_1 = require("./js-yaml");
|
|
17
18
|
var js_yaml_2 = require("./js-yaml");
|
|
18
19
|
Object.defineProperty(exports, "parseYaml", { enumerable: true, get: function () { return js_yaml_2.parseYaml; } });
|
|
@@ -76,7 +77,70 @@ function omitObjectProps(object, keys) {
|
|
|
76
77
|
return Object.fromEntries(Object.entries(object).filter(([key]) => !keys.includes(key)));
|
|
77
78
|
}
|
|
78
79
|
exports.omitObjectProps = omitObjectProps;
|
|
80
|
+
function splitCamelCaseIntoWords(str) {
|
|
81
|
+
const camel = str
|
|
82
|
+
.split(/(?:[-._])|([A-Z][a-z]+)/)
|
|
83
|
+
.filter(Boolean)
|
|
84
|
+
.map((item) => item.toLocaleLowerCase());
|
|
85
|
+
const caps = str
|
|
86
|
+
.split(/([A-Z]{2,})/)
|
|
87
|
+
.filter((e) => e && e === e.toUpperCase())
|
|
88
|
+
.map((item) => item.toLocaleLowerCase());
|
|
89
|
+
return new Set([...camel, ...caps]);
|
|
90
|
+
}
|
|
91
|
+
exports.splitCamelCaseIntoWords = splitCamelCaseIntoWords;
|
|
92
|
+
function validateMimeType({ type, value }, { report, location }, allowedValues) {
|
|
93
|
+
const ruleType = type === 'consumes' ? 'request' : 'response';
|
|
94
|
+
if (!allowedValues)
|
|
95
|
+
throw new Error(`Parameter "allowedValues" is not provided for "${ruleType}-mime-type" rule`);
|
|
96
|
+
if (!value[type])
|
|
97
|
+
return;
|
|
98
|
+
for (const mime of value[type]) {
|
|
99
|
+
if (!allowedValues.includes(mime)) {
|
|
100
|
+
report({
|
|
101
|
+
message: `Mime type "${mime}" is not allowed`,
|
|
102
|
+
location: location.child(value[type].indexOf(mime)).key(),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
exports.validateMimeType = validateMimeType;
|
|
108
|
+
function validateMimeTypeOAS3({ type, value }, { report, location }, allowedValues) {
|
|
109
|
+
const ruleType = type === 'consumes' ? 'request' : 'response';
|
|
110
|
+
if (!allowedValues)
|
|
111
|
+
throw new Error(`Parameter "allowedValues" is not provided for "${ruleType}-mime-type" rule`);
|
|
112
|
+
if (!value.content)
|
|
113
|
+
return;
|
|
114
|
+
for (const mime of Object.keys(value.content)) {
|
|
115
|
+
if (!allowedValues.includes(mime)) {
|
|
116
|
+
report({
|
|
117
|
+
message: `Mime type "${mime}" is not allowed`,
|
|
118
|
+
location: location.child('content').child(mime).key(),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
exports.validateMimeTypeOAS3 = validateMimeTypeOAS3;
|
|
124
|
+
function isSingular(path) {
|
|
125
|
+
return pluralize.isSingular(path);
|
|
126
|
+
}
|
|
127
|
+
exports.isSingular = isSingular;
|
|
79
128
|
function readFileAsStringSync(filePath) {
|
|
80
129
|
return fs.readFileSync(filePath, 'utf-8');
|
|
81
130
|
}
|
|
82
131
|
exports.readFileAsStringSync = readFileAsStringSync;
|
|
132
|
+
function isPathParameter(pathSegment) {
|
|
133
|
+
return pathSegment.startsWith('{') && pathSegment.endsWith('{');
|
|
134
|
+
}
|
|
135
|
+
exports.isPathParameter = isPathParameter;
|
|
136
|
+
/**
|
|
137
|
+
* Convert Windows backslash paths to slash paths: foo\\bar ➔ foo/bar
|
|
138
|
+
*/
|
|
139
|
+
function slash(path) {
|
|
140
|
+
const isExtendedLengthPath = /^\\\\\?\\/.test(path);
|
|
141
|
+
if (isExtendedLengthPath) {
|
|
142
|
+
return path;
|
|
143
|
+
}
|
|
144
|
+
return path.replace(/\\/g, '/');
|
|
145
|
+
}
|
|
146
|
+
exports.slash = slash;
|
package/lib/walk.d.ts
CHANGED
|
@@ -36,6 +36,7 @@ export declare type UserContext = {
|
|
|
36
36
|
key: string | number;
|
|
37
37
|
parent: any;
|
|
38
38
|
oasVersion: OasVersion;
|
|
39
|
+
getVisitorData: () => Record<string, unknown>;
|
|
39
40
|
};
|
|
40
41
|
export declare type Loc = {
|
|
41
42
|
line: number;
|
|
@@ -72,6 +73,7 @@ export declare type NormalizedProblem = {
|
|
|
72
73
|
export declare type WalkContext = {
|
|
73
74
|
problems: NormalizedProblem[];
|
|
74
75
|
oasVersion: OasVersion;
|
|
76
|
+
visitorsData: Record<string, Record<string, unknown>>;
|
|
75
77
|
refTypes?: Map<string, NormalizedNodeType>;
|
|
76
78
|
};
|
|
77
79
|
export declare function walkDocument<T>(opts: {
|
package/lib/walk.js
CHANGED
|
@@ -50,6 +50,7 @@ function walkDocument(opts) {
|
|
|
50
50
|
key,
|
|
51
51
|
parentLocations: {},
|
|
52
52
|
oasVersion: ctx.oasVersion,
|
|
53
|
+
getVisitorData: getVisitorDataFn.bind(undefined, ruleId)
|
|
53
54
|
}, { node: resolvedNode, location: resolvedLocation, error });
|
|
54
55
|
if ((resolvedLocation === null || resolvedLocation === void 0 ? void 0 : resolvedLocation.source.absoluteRef) && ctx.refTypes) {
|
|
55
56
|
ctx.refTypes.set(resolvedLocation === null || resolvedLocation === void 0 ? void 0 : resolvedLocation.source.absoluteRef, type);
|
|
@@ -192,6 +193,7 @@ function walkDocument(opts) {
|
|
|
192
193
|
key,
|
|
193
194
|
parentLocations: {},
|
|
194
195
|
oasVersion: ctx.oasVersion,
|
|
196
|
+
getVisitorData: getVisitorDataFn.bind(undefined, ruleId)
|
|
195
197
|
}, { node: resolvedNode, location: resolvedLocation, error });
|
|
196
198
|
}
|
|
197
199
|
}
|
|
@@ -212,6 +214,7 @@ function walkDocument(opts) {
|
|
|
212
214
|
ignoreNextVisitorsOnNode: () => {
|
|
213
215
|
ignoreNextVisitorsOnNode = true;
|
|
214
216
|
},
|
|
217
|
+
getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
|
|
215
218
|
}, collectParents(context), context);
|
|
216
219
|
return ignoreNextVisitorsOnNode;
|
|
217
220
|
}
|
|
@@ -244,6 +247,10 @@ function walkDocument(opts) {
|
|
|
244
247
|
return Object.assign(Object.assign(Object.assign({}, currentLocation), { reportOnKey: false }), loc);
|
|
245
248
|
}) }));
|
|
246
249
|
}
|
|
250
|
+
function getVisitorDataFn(ruleId) {
|
|
251
|
+
ctx.visitorsData[ruleId] = ctx.visitorsData[ruleId] || {};
|
|
252
|
+
return ctx.visitorsData[ruleId];
|
|
253
|
+
}
|
|
247
254
|
}
|
|
248
255
|
}
|
|
249
256
|
exports.walkDocument = walkDocument;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/openapi-core",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.69",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"engines": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"Andriy Leliv <andriy@redoc.ly> (https://redoc.ly/)"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@redocly/ajv": "^8.6.
|
|
33
|
+
"@redocly/ajv": "^8.6.4",
|
|
34
34
|
"@types/node": "^14.11.8",
|
|
35
35
|
"colorette": "^1.2.0",
|
|
36
36
|
"js-levenshtein": "^1.1.6",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"lodash.isequal": "^4.5.0",
|
|
39
39
|
"minimatch": "^3.0.4",
|
|
40
40
|
"node-fetch": "^2.6.1",
|
|
41
|
+
"pluralize": "^8.0.0",
|
|
41
42
|
"yaml-ast-parser": "0.0.43"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
@@ -46,6 +47,7 @@
|
|
|
46
47
|
"@types/lodash.isequal": "^4.5.5",
|
|
47
48
|
"@types/minimatch": "^3.0.3",
|
|
48
49
|
"@types/node-fetch": "^2.5.7",
|
|
50
|
+
"@types/pluralize": "^0.0.29",
|
|
49
51
|
"typescript": "^4.0.5"
|
|
50
52
|
}
|
|
51
|
-
}
|
|
53
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { pickObjectProps, omitObjectProps } from '../utils';
|
|
1
|
+
import { pickObjectProps, omitObjectProps, slash } from '../utils';
|
|
2
2
|
|
|
3
3
|
describe('utils', () => {
|
|
4
4
|
const testObject = {
|
|
@@ -53,4 +53,22 @@ describe('utils', () => {
|
|
|
53
53
|
expect(omitObjectProps({}, ['d', 'e'])).toStrictEqual({});
|
|
54
54
|
});
|
|
55
55
|
});
|
|
56
|
+
|
|
57
|
+
describe('slash path', () => {
|
|
58
|
+
it('can correctly slash path', () => {
|
|
59
|
+
[
|
|
60
|
+
['foo\\bar', 'foo/bar'],
|
|
61
|
+
['foo/bar', 'foo/bar'],
|
|
62
|
+
['foo\\中文', 'foo/中文'],
|
|
63
|
+
['foo/中文', 'foo/中文'],
|
|
64
|
+
].forEach(([path, expectRes]) => {
|
|
65
|
+
expect(slash(path)).toBe(expectRes);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('does not modify extended length paths', () => {
|
|
70
|
+
const extended = '\\\\?\\some\\path';
|
|
71
|
+
expect(slash(extended)).toBe(extended);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
56
74
|
});
|
package/src/bundle.ts
CHANGED
|
@@ -7,12 +7,13 @@ import { Oas3_1Types } from './types/oas3_1';
|
|
|
7
7
|
import { NormalizedNodeType, normalizeTypes, NodeType } from './types';
|
|
8
8
|
import { WalkContext, walkDocument, UserContext, ResolveResult } from './walk';
|
|
9
9
|
import { detectOpenAPI, openAPIMajor, OasMajorVersion } from './oas-types';
|
|
10
|
-
import { Location, refBaseName } from './ref-utils';
|
|
10
|
+
import { isRef, Location, refBaseName } from './ref-utils';
|
|
11
11
|
import type { Config, LintConfig } from './config/config';
|
|
12
12
|
import { initRules } from './config/rules';
|
|
13
13
|
import { reportUnresolvedRef } from './rules/no-unresolved-refs';
|
|
14
14
|
import { isPlainObject } from './utils';
|
|
15
15
|
import { OasRef } from './typings/openapi';
|
|
16
|
+
import { isRedoclyRegistryURL } from './redocly';
|
|
16
17
|
|
|
17
18
|
export type Oas3RuleSet = Record<string, Oas3Rule>;
|
|
18
19
|
|
|
@@ -29,6 +30,7 @@ export async function bundle(opts: {
|
|
|
29
30
|
config: Config;
|
|
30
31
|
dereference?: boolean;
|
|
31
32
|
base?: string;
|
|
33
|
+
skipRedoclyRegistryRefs?: boolean;
|
|
32
34
|
}) {
|
|
33
35
|
const {
|
|
34
36
|
ref,
|
|
@@ -40,7 +42,8 @@ export async function bundle(opts: {
|
|
|
40
42
|
throw new Error('Document or reference is required.\n');
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
const document =
|
|
45
|
+
const document =
|
|
46
|
+
doc !== undefined ? doc : await externalRefResolver.resolveDocument(base, ref!, true);
|
|
44
47
|
|
|
45
48
|
if (document instanceof Error) {
|
|
46
49
|
throw document;
|
|
@@ -62,14 +65,26 @@ export async function bundleDocument(opts: {
|
|
|
62
65
|
customTypes?: Record<string, NodeType>;
|
|
63
66
|
externalRefResolver: BaseResolver;
|
|
64
67
|
dereference?: boolean;
|
|
68
|
+
skipRedoclyRegistryRefs?: boolean;
|
|
65
69
|
}) {
|
|
66
|
-
const {
|
|
70
|
+
const {
|
|
71
|
+
document,
|
|
72
|
+
config,
|
|
73
|
+
customTypes,
|
|
74
|
+
externalRefResolver,
|
|
75
|
+
dereference = false,
|
|
76
|
+
skipRedoclyRegistryRefs = false,
|
|
77
|
+
} = opts;
|
|
67
78
|
const oasVersion = detectOpenAPI(document.parsed);
|
|
68
79
|
const oasMajorVersion = openAPIMajor(oasVersion);
|
|
69
80
|
const rules = config.getRulesForOasVersion(oasMajorVersion);
|
|
70
81
|
const types = normalizeTypes(
|
|
71
82
|
config.extendTypes(
|
|
72
|
-
customTypes ?? oasMajorVersion === OasMajorVersion.Version3
|
|
83
|
+
customTypes ?? oasMajorVersion === OasMajorVersion.Version3
|
|
84
|
+
? oasVersion === OasVersion.Version3_1
|
|
85
|
+
? Oas3_1Types
|
|
86
|
+
: Oas3Types
|
|
87
|
+
: Oas2Types,
|
|
73
88
|
oasVersion,
|
|
74
89
|
),
|
|
75
90
|
config,
|
|
@@ -81,6 +96,7 @@ export async function bundleDocument(opts: {
|
|
|
81
96
|
problems: [],
|
|
82
97
|
oasVersion: oasVersion,
|
|
83
98
|
refTypes: new Map<string, NormalizedNodeType>(),
|
|
99
|
+
visitorsData: {},
|
|
84
100
|
};
|
|
85
101
|
|
|
86
102
|
const bundleVisitor = normalizeVisitors(
|
|
@@ -89,7 +105,7 @@ export async function bundleDocument(opts: {
|
|
|
89
105
|
{
|
|
90
106
|
severity: 'error',
|
|
91
107
|
ruleId: 'bundler',
|
|
92
|
-
visitor: makeBundleVisitor(oasMajorVersion, dereference, document),
|
|
108
|
+
visitor: makeBundleVisitor(oasMajorVersion, dereference, skipRedoclyRegistryRefs, document),
|
|
93
109
|
},
|
|
94
110
|
...decorators,
|
|
95
111
|
],
|
|
@@ -116,6 +132,7 @@ export async function bundleDocument(opts: {
|
|
|
116
132
|
fileDependencies: externalRefResolver.getFiles(),
|
|
117
133
|
rootType: types.DefinitionRoot,
|
|
118
134
|
refTypes: ctx.refTypes,
|
|
135
|
+
visitorsData: ctx.visitorsData,
|
|
119
136
|
};
|
|
120
137
|
}
|
|
121
138
|
|
|
@@ -160,7 +177,12 @@ function mapTypeToComponent(typeName: string, version: OasMajorVersion) {
|
|
|
160
177
|
|
|
161
178
|
// function oas3Move
|
|
162
179
|
|
|
163
|
-
function makeBundleVisitor(
|
|
180
|
+
function makeBundleVisitor(
|
|
181
|
+
version: OasMajorVersion,
|
|
182
|
+
dereference: boolean,
|
|
183
|
+
skipRedoclyRegistryRefs: boolean,
|
|
184
|
+
rootDocument: Document,
|
|
185
|
+
) {
|
|
164
186
|
let components: Record<string, Record<string, any>>;
|
|
165
187
|
|
|
166
188
|
const visitor: Oas3Visitor | Oas2Visitor = {
|
|
@@ -178,6 +200,11 @@ function makeBundleVisitor(version: OasMajorVersion, dereference: boolean, rootD
|
|
|
178
200
|
) {
|
|
179
201
|
return;
|
|
180
202
|
}
|
|
203
|
+
|
|
204
|
+
if (skipRedoclyRegistryRefs && isRedoclyRegistryURL(node.$ref)) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
181
208
|
const componentType = mapTypeToComponent(ctx.type.name, version);
|
|
182
209
|
if (!componentType) {
|
|
183
210
|
replaceRef(node, resolved, ctx);
|
|
@@ -249,6 +276,21 @@ function makeBundleVisitor(version: OasMajorVersion, dereference: boolean, rootD
|
|
|
249
276
|
}
|
|
250
277
|
}
|
|
251
278
|
|
|
279
|
+
function isEqualOrEqualRef(
|
|
280
|
+
node: any,
|
|
281
|
+
target: { node: any; location: Location },
|
|
282
|
+
ctx: UserContext,
|
|
283
|
+
) {
|
|
284
|
+
if (
|
|
285
|
+
isRef(node) &&
|
|
286
|
+
ctx.resolve(node).location?.absolutePointer === target.location.absolutePointer
|
|
287
|
+
) {
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return isEqual(node, target.node);
|
|
292
|
+
}
|
|
293
|
+
|
|
252
294
|
function getComponentName(
|
|
253
295
|
target: { node: any; location: Location },
|
|
254
296
|
componentType: string,
|
|
@@ -265,20 +307,20 @@ function makeBundleVisitor(version: OasMajorVersion, dereference: boolean, rootD
|
|
|
265
307
|
if (
|
|
266
308
|
!componentsGroup ||
|
|
267
309
|
!componentsGroup[name] ||
|
|
268
|
-
|
|
310
|
+
isEqualOrEqualRef(componentsGroup[name], target, ctx)
|
|
269
311
|
) {
|
|
270
312
|
return name;
|
|
271
313
|
}
|
|
272
314
|
}
|
|
273
315
|
|
|
274
316
|
name = refBaseName(fileRef) + (name ? `_${name}` : '');
|
|
275
|
-
if (!componentsGroup[name] ||
|
|
317
|
+
if (!componentsGroup[name] || isEqualOrEqualRef(componentsGroup[name], target, ctx)) {
|
|
276
318
|
return name;
|
|
277
319
|
}
|
|
278
320
|
|
|
279
321
|
const prevName = name;
|
|
280
322
|
let serialId = 2;
|
|
281
|
-
while (componentsGroup[name] && !
|
|
323
|
+
while (componentsGroup[name] && !isEqualOrEqualRef(componentsGroup[name], target, ctx)) {
|
|
282
324
|
name = `${prevName}-${serialId}`;
|
|
283
325
|
serialId++;
|
|
284
326
|
}
|
package/src/config/all.ts
CHANGED
|
@@ -12,11 +12,13 @@ export default {
|
|
|
12
12
|
'no-identical-paths': 'error',
|
|
13
13
|
'no-ambiguous-paths': 'error',
|
|
14
14
|
'no-path-trailing-slash': 'error',
|
|
15
|
+
'path-segment-plural': 'error',
|
|
15
16
|
'path-declaration-must-exist': 'error',
|
|
16
17
|
'path-not-include-query': 'error',
|
|
17
18
|
'path-parameters-defined': 'error',
|
|
18
19
|
'operation-description': 'error',
|
|
19
20
|
'operation-2xx-response': 'error',
|
|
21
|
+
'operation-4xx-response': 'error',
|
|
20
22
|
'operation-operationId': 'error',
|
|
21
23
|
'operation-summary': 'error',
|
|
22
24
|
'operation-operationId-unique': 'error',
|
|
@@ -29,6 +31,12 @@ export default {
|
|
|
29
31
|
'no-enum-type-mismatch': 'error',
|
|
30
32
|
'boolean-parameter-prefixes': 'error',
|
|
31
33
|
'paths-kebab-case': 'error',
|
|
34
|
+
'no-http-verbs-in-paths': 'error',
|
|
35
|
+
'path-excludes-patterns': {
|
|
36
|
+
severity: 'error',
|
|
37
|
+
patterns: [],
|
|
38
|
+
},
|
|
39
|
+
'request-mime-type': 'error',
|
|
32
40
|
spec: 'error',
|
|
33
41
|
},
|
|
34
42
|
oas3_0Rules: {
|
|
@@ -49,5 +57,5 @@ export default {
|
|
|
49
57
|
'no-unused-components': 'error',
|
|
50
58
|
'no-undefined-server-variable': 'error',
|
|
51
59
|
'no-servers-empty-enum': 'error',
|
|
52
|
-
}
|
|
60
|
+
},
|
|
53
61
|
} as LintRawConfig;
|
package/src/config/config.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { dirname } from 'path';
|
|
|
4
4
|
import { red, blue } from 'colorette';
|
|
5
5
|
|
|
6
6
|
import { parseYaml, stringifyYaml } from '../js-yaml';
|
|
7
|
-
import { notUndefined } from '../utils';
|
|
7
|
+
import { notUndefined, slash } from '../utils';
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
10
|
OasVersion,
|
|
@@ -211,7 +211,7 @@ export class LintConfig {
|
|
|
211
211
|
const ignoreFile = path.join(dir, IGNORE_FILE);
|
|
212
212
|
const mapped: Record<string, any> = {};
|
|
213
213
|
for (const absFileName of Object.keys(this.ignore)) {
|
|
214
|
-
const ignoredRules = (mapped[path.relative(dir, absFileName)] = this.ignore[absFileName]);
|
|
214
|
+
const ignoredRules = (mapped[slash(path.relative(dir, absFileName))] = this.ignore[absFileName]);
|
|
215
215
|
for (const ruleId of Object.keys(ignoredRules)) {
|
|
216
216
|
ignoredRules[ruleId] = Array.from(ignoredRules[ruleId]) as any;
|
|
217
217
|
}
|
package/src/config/minimal.ts
CHANGED
|
@@ -17,6 +17,7 @@ export default {
|
|
|
17
17
|
'path-parameters-defined': 'warn',
|
|
18
18
|
'operation-description': 'off',
|
|
19
19
|
'operation-2xx-response': 'warn',
|
|
20
|
+
'operation-4xx-response': 'off',
|
|
20
21
|
'operation-operationId': 'warn',
|
|
21
22
|
'operation-summary': 'warn',
|
|
22
23
|
'operation-operationId-unique': 'warn',
|
|
@@ -17,6 +17,7 @@ export default {
|
|
|
17
17
|
'path-parameters-defined': 'error',
|
|
18
18
|
'operation-description': 'off',
|
|
19
19
|
'operation-2xx-response': 'warn',
|
|
20
|
+
'operation-4xx-response': 'warn',
|
|
20
21
|
'operation-operationId': 'warn',
|
|
21
22
|
'operation-summary': 'error',
|
|
22
23
|
'operation-operationId-unique': 'error',
|
package/src/index.ts
CHANGED
package/src/lint.ts
CHANGED
|
@@ -72,6 +72,7 @@ export async function lintDocument(opts: {
|
|
|
72
72
|
const ctx: WalkContext = {
|
|
73
73
|
problems: [],
|
|
74
74
|
oasVersion: oasVersion,
|
|
75
|
+
visitorsData: {},
|
|
75
76
|
};
|
|
76
77
|
|
|
77
78
|
const preprocessors = initRules(rules as any, config, 'preprocessors', oasVersion);
|
|
@@ -101,6 +102,7 @@ export async function lintConfig(opts: {
|
|
|
101
102
|
const ctx: WalkContext = {
|
|
102
103
|
problems: [],
|
|
103
104
|
oasVersion: OasVersion.Version3_0,
|
|
105
|
+
visitorsData: {},
|
|
104
106
|
};
|
|
105
107
|
const config = new LintConfig({
|
|
106
108
|
plugins: [defaultPlugin],
|