@redocly/openapi-core 1.0.0-beta.89 → 1.0.0-beta.92
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 +15 -13
- package/__tests__/ref-utils.test.ts +23 -1
- package/lib/config/all.js +1 -0
- package/lib/config/config.js +6 -11
- package/lib/config/minimal.js +1 -0
- package/lib/config/recommended.js +1 -0
- package/lib/config/rules.d.ts +1 -1
- package/lib/config/rules.js +10 -2
- package/lib/redocly/index.d.ts +4 -1
- package/lib/redocly/index.js +28 -19
- package/lib/redocly/registry-api.d.ts +4 -1
- package/lib/redocly/registry-api.js +3 -3
- package/lib/ref-utils.js +1 -1
- package/lib/rules/common/assertions/asserts.d.ts +5 -0
- package/lib/rules/common/assertions/asserts.js +143 -0
- package/lib/rules/common/assertions/index.d.ts +2 -0
- package/lib/rules/common/assertions/index.js +52 -0
- package/lib/rules/common/assertions/utils.d.ts +20 -0
- package/lib/rules/common/assertions/utils.js +123 -0
- package/lib/rules/oas2/index.d.ts +1 -0
- package/lib/rules/oas2/index.js +2 -0
- package/lib/rules/oas3/index.js +2 -0
- package/lib/types/redocly-yaml.js +24 -11
- package/lib/visitors.d.ts +2 -2
- package/lib/walk.d.ts +1 -0
- package/lib/walk.js +1 -1
- package/package.json +1 -1
- package/src/__tests__/lint.test.ts +40 -6
- package/src/bundle.ts +1 -1
- package/src/config/all.ts +1 -0
- package/src/config/config.ts +15 -12
- package/src/config/minimal.ts +1 -0
- package/src/config/recommended.ts +1 -0
- package/src/config/rules.ts +11 -2
- package/src/lint.ts +3 -3
- package/src/redocly/index.ts +49 -30
- package/src/redocly/registry-api.ts +38 -21
- package/src/ref-utils.ts +1 -1
- package/src/rules/common/assertions/__tests__/asserts.test.ts +231 -0
- package/src/rules/common/assertions/__tests__/index.test.ts +65 -0
- package/src/rules/common/assertions/__tests__/utils.test.ts +89 -0
- package/src/rules/common/assertions/asserts.ts +137 -0
- package/src/rules/common/assertions/index.ts +75 -0
- package/src/rules/common/assertions/utils.ts +164 -0
- package/src/rules/oas2/index.ts +2 -0
- package/src/rules/oas3/index.ts +2 -0
- package/src/types/redocly-yaml.ts +30 -11
- package/src/visitors.ts +2 -2
- package/src/walk.ts +2 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -144,10 +144,24 @@ paths:
|
|
|
144
144
|
content:
|
|
145
145
|
application/json:
|
|
146
146
|
schema:
|
|
147
|
-
$ref: '#/components/schemas/vendor'
|
|
147
|
+
$ref: '#/components/schemas/vendor.schema'
|
|
148
148
|
components:
|
|
149
149
|
schemas:
|
|
150
150
|
vendor:
|
|
151
|
+
$ref: '#/components/schemas/vendor.schema'
|
|
152
|
+
myvendor:
|
|
153
|
+
$ref: '#/components/schemas/vendor.schema'
|
|
154
|
+
simple:
|
|
155
|
+
type: string
|
|
156
|
+
A:
|
|
157
|
+
type: string
|
|
158
|
+
test:
|
|
159
|
+
$ref: '#/components/schemas/rename-2'
|
|
160
|
+
rename:
|
|
161
|
+
type: string
|
|
162
|
+
rename-2:
|
|
163
|
+
type: number
|
|
164
|
+
vendor.schema:
|
|
151
165
|
title: vendor
|
|
152
166
|
type: object
|
|
153
167
|
description: Vendors
|
|
@@ -171,18 +185,6 @@ components:
|
|
|
171
185
|
type: boolean
|
|
172
186
|
description: One-time use
|
|
173
187
|
default: false
|
|
174
|
-
myvendor:
|
|
175
|
-
$ref: '#/components/schemas/vendor'
|
|
176
|
-
simple:
|
|
177
|
-
type: string
|
|
178
|
-
A:
|
|
179
|
-
type: string
|
|
180
|
-
test:
|
|
181
|
-
$ref: '#/components/schemas/rename-2'
|
|
182
|
-
rename:
|
|
183
|
-
type: string
|
|
184
|
-
rename-2:
|
|
185
|
-
type: number
|
|
186
188
|
|
|
187
189
|
`;
|
|
188
190
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import outdent from 'outdent';
|
|
2
2
|
import { parseYamlToDocument } from './utils';
|
|
3
|
-
import { parseRef } from '../src/ref-utils';
|
|
3
|
+
import { parseRef, refBaseName } from '../src/ref-utils';
|
|
4
4
|
import { lintDocument } from '../src/lint';
|
|
5
5
|
import { LintConfig } from '../src/config/config';
|
|
6
6
|
import { BaseResolver } from '../src/resolve';
|
|
@@ -95,4 +95,26 @@ describe('ref-utils', () => {
|
|
|
95
95
|
|
|
96
96
|
expect(result).toMatchInlineSnapshot(`Array []`);
|
|
97
97
|
});
|
|
98
|
+
|
|
99
|
+
describe('refBaseName', () => {
|
|
100
|
+
it("returns base name for file reference", () => {
|
|
101
|
+
expect(refBaseName("../testcase/Pet.yaml")).toStrictEqual("Pet");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("returns base name for local file reference", () => {
|
|
105
|
+
expect(refBaseName("Cat.json")).toStrictEqual("Cat");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("returns base name for url reference", () => {
|
|
109
|
+
expect(refBaseName("http://example.com/tests/crocodile.json")).toStrictEqual("crocodile");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("returns base name for file with multiple dots in name", () => {
|
|
113
|
+
expect(refBaseName("feline.tiger.v1.yaml")).toStrictEqual("feline.tiger.v1");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("returns base name for file without any dots in name", () => {
|
|
117
|
+
expect(refBaseName("abcdefg")).toStrictEqual("abcdefg");
|
|
118
|
+
});
|
|
119
|
+
});
|
|
98
120
|
});
|
package/lib/config/all.js
CHANGED
|
@@ -19,6 +19,7 @@ exports.default = {
|
|
|
19
19
|
'operation-description': 'error',
|
|
20
20
|
'operation-2xx-response': 'error',
|
|
21
21
|
'operation-4xx-response': 'error',
|
|
22
|
+
'assertions': 'error',
|
|
22
23
|
'operation-operationId': 'error',
|
|
23
24
|
'operation-summary': 'error',
|
|
24
25
|
'operation-operationId-unique': 'error',
|
package/lib/config/config.js
CHANGED
|
@@ -444,17 +444,12 @@ function transformConfig(rawConfig) {
|
|
|
444
444
|
throw new Error("Do not use 'referenceDocs' field. Use 'features.openapi' instead.\n");
|
|
445
445
|
}
|
|
446
446
|
const _a = rawConfig, { apiDefinitions, referenceDocs } = _a, rest = __rest(_a, ["apiDefinitions", "referenceDocs"]);
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
// if (referenceDocs) {
|
|
454
|
-
// process.stderr.write(
|
|
455
|
-
// `The ${yellow('referenceDocs')} field is deprecated. Use ${green('features.openapi')} instead, see changelog: <link>\n`
|
|
456
|
-
// );
|
|
457
|
-
// }
|
|
447
|
+
if (apiDefinitions) {
|
|
448
|
+
process.stderr.write(`The ${colorette_1.yellow('apiDefinitions')} field is deprecated. Use ${colorette_1.green('apis')} instead. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties\n`);
|
|
449
|
+
}
|
|
450
|
+
if (referenceDocs) {
|
|
451
|
+
process.stderr.write(`The ${colorette_1.yellow('referenceDocs')} field is deprecated. Use ${colorette_1.green('features.openapi')} instead. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties\n`);
|
|
452
|
+
}
|
|
458
453
|
return Object.assign({ 'features.openapi': referenceDocs, apis: transformApiDefinitionsToApis(apiDefinitions) }, rest);
|
|
459
454
|
}
|
|
460
455
|
exports.transformConfig = transformConfig;
|
package/lib/config/minimal.js
CHANGED
|
@@ -18,6 +18,7 @@ exports.default = {
|
|
|
18
18
|
'operation-description': 'off',
|
|
19
19
|
'operation-2xx-response': 'warn',
|
|
20
20
|
'operation-4xx-response': 'off',
|
|
21
|
+
'assertions': 'warn',
|
|
21
22
|
'operation-operationId': 'warn',
|
|
22
23
|
'operation-summary': 'warn',
|
|
23
24
|
'operation-operationId-unique': 'warn',
|
package/lib/config/rules.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RuleSet, OasVersion } from '../oas-types';
|
|
2
2
|
import { LintConfig } from './config';
|
|
3
3
|
export declare function initRules<T extends Function, P extends RuleSet<T>>(rules: P[], config: LintConfig, type: 'rules' | 'preprocessors' | 'decorators', oasVersion: OasVersion): {
|
|
4
|
-
severity: import("..").ProblemSeverity;
|
|
4
|
+
severity: import("..").ProblemSeverity | "off";
|
|
5
5
|
ruleId: string;
|
|
6
6
|
visitor: any;
|
|
7
7
|
}[];
|
package/lib/config/rules.js
CHANGED
|
@@ -14,13 +14,21 @@ function initRules(rules, config, type, oasVersion) {
|
|
|
14
14
|
if (ruleSettings.severity === 'off') {
|
|
15
15
|
return undefined;
|
|
16
16
|
}
|
|
17
|
-
const
|
|
17
|
+
const visitors = rule(ruleSettings);
|
|
18
|
+
if (Array.isArray(visitors)) {
|
|
19
|
+
return visitors.map((visitor) => ({
|
|
20
|
+
severity: ruleSettings.severity,
|
|
21
|
+
ruleId,
|
|
22
|
+
visitor: visitor,
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
18
25
|
return {
|
|
19
26
|
severity: ruleSettings.severity,
|
|
20
27
|
ruleId,
|
|
21
|
-
visitor,
|
|
28
|
+
visitor: visitors, // note: actually it is only one visitor object
|
|
22
29
|
};
|
|
23
30
|
}))
|
|
31
|
+
.flatMap(visitor => visitor)
|
|
24
32
|
.filter(utils_1.notUndefined);
|
|
25
33
|
}
|
|
26
34
|
exports.initRules = initRules;
|
package/lib/redocly/index.d.ts
CHANGED
|
@@ -20,7 +20,10 @@ export declare class RedoclyClient {
|
|
|
20
20
|
isAuthorizedWithRedoclyByRegion(): Promise<boolean>;
|
|
21
21
|
isAuthorizedWithRedocly(): Promise<boolean>;
|
|
22
22
|
readCredentialsFile(credentialsPath: string): any;
|
|
23
|
-
verifyToken(accessToken: string, region: Region, verbose?: boolean): Promise<
|
|
23
|
+
verifyToken(accessToken: string, region: Region, verbose?: boolean): Promise<{
|
|
24
|
+
viewerId: string;
|
|
25
|
+
organizations: string[];
|
|
26
|
+
}>;
|
|
24
27
|
login(accessToken: string, verbose?: boolean): Promise<void>;
|
|
25
28
|
logout(): void;
|
|
26
29
|
}
|
package/lib/redocly/index.js
CHANGED
|
@@ -24,9 +24,7 @@ class RedoclyClient {
|
|
|
24
24
|
this.accessTokens = {};
|
|
25
25
|
this.region = this.loadRegion(region);
|
|
26
26
|
this.loadTokens();
|
|
27
|
-
this.domain = region
|
|
28
|
-
? config_1.DOMAINS[region]
|
|
29
|
-
: process.env.REDOCLY_DOMAIN || config_1.DOMAINS[config_1.DEFAULT_REGION];
|
|
27
|
+
this.domain = region ? config_1.DOMAINS[region] : process.env.REDOCLY_DOMAIN || config_1.DOMAINS[config_1.DEFAULT_REGION];
|
|
30
28
|
/*
|
|
31
29
|
* We can't use process.env here because it is replaced by a const in some client-side bundles,
|
|
32
30
|
* which breaks assignment.
|
|
@@ -73,8 +71,9 @@ class RedoclyClient {
|
|
|
73
71
|
const credentialsPath = path_1.resolve(os_1.homedir(), TOKEN_FILENAME);
|
|
74
72
|
const credentials = this.readCredentialsFile(credentialsPath);
|
|
75
73
|
if (utils_1.isNotEmptyObject(credentials)) {
|
|
76
|
-
this.setAccessTokens(Object.assign(Object.assign({}, credentials), (credentials.token &&
|
|
77
|
-
[this.region]
|
|
74
|
+
this.setAccessTokens(Object.assign(Object.assign({}, credentials), (credentials.token &&
|
|
75
|
+
!credentials[this.region] && {
|
|
76
|
+
[this.region]: credentials.token,
|
|
78
77
|
})));
|
|
79
78
|
}
|
|
80
79
|
if (process.env.REDOCLY_AUTHORIZATION) {
|
|
@@ -82,17 +81,17 @@ class RedoclyClient {
|
|
|
82
81
|
}
|
|
83
82
|
}
|
|
84
83
|
getAllTokens() {
|
|
85
|
-
return Object.entries(this.accessTokens)
|
|
84
|
+
return Object.entries(this.accessTokens)
|
|
85
|
+
.filter(([region]) => config_1.AVAILABLE_REGIONS.includes(region))
|
|
86
|
+
.map(([region, token]) => ({ region, token }));
|
|
86
87
|
}
|
|
87
88
|
getValidTokens() {
|
|
88
89
|
return __awaiter(this, void 0, void 0, function* () {
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return validTokens;
|
|
90
|
+
const allTokens = this.getAllTokens();
|
|
91
|
+
const verifiedTokens = yield Promise.allSettled(allTokens.map(({ token, region }) => this.verifyToken(token, region)));
|
|
92
|
+
return allTokens
|
|
93
|
+
.filter((_, index) => verifiedTokens[index].status === 'fulfilled')
|
|
94
|
+
.map(({ token, region }) => ({ token, region, valid: true }));
|
|
96
95
|
});
|
|
97
96
|
}
|
|
98
97
|
getTokens() {
|
|
@@ -102,10 +101,20 @@ class RedoclyClient {
|
|
|
102
101
|
}
|
|
103
102
|
isAuthorizedWithRedoclyByRegion() {
|
|
104
103
|
return __awaiter(this, void 0, void 0, function* () {
|
|
105
|
-
if (!this.hasTokens())
|
|
104
|
+
if (!this.hasTokens()) {
|
|
106
105
|
return false;
|
|
106
|
+
}
|
|
107
107
|
const accessToken = this.accessTokens[this.region];
|
|
108
|
-
|
|
108
|
+
if (!accessToken) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
yield this.verifyToken(accessToken, this.region);
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
109
118
|
});
|
|
110
119
|
}
|
|
111
120
|
isAuthorizedWithRedocly() {
|
|
@@ -118,8 +127,6 @@ class RedoclyClient {
|
|
|
118
127
|
}
|
|
119
128
|
verifyToken(accessToken, region, verbose = false) {
|
|
120
129
|
return __awaiter(this, void 0, void 0, function* () {
|
|
121
|
-
if (!accessToken)
|
|
122
|
-
return false;
|
|
123
130
|
return this.registryApi.authStatus(accessToken, region, verbose);
|
|
124
131
|
});
|
|
125
132
|
}
|
|
@@ -127,8 +134,10 @@ class RedoclyClient {
|
|
|
127
134
|
return __awaiter(this, void 0, void 0, function* () {
|
|
128
135
|
const credentialsPath = path_1.resolve(os_1.homedir(), TOKEN_FILENAME);
|
|
129
136
|
process.stdout.write(colorette_1.gray('\n Logging in...\n'));
|
|
130
|
-
|
|
131
|
-
|
|
137
|
+
try {
|
|
138
|
+
yield this.verifyToken(accessToken, this.region, verbose);
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
132
141
|
process.stdout.write(colorette_1.red('Authorization failed. Please check if you entered a valid API key.\n'));
|
|
133
142
|
process.exit(1);
|
|
134
143
|
}
|
|
@@ -8,7 +8,10 @@ export declare class RegistryApi {
|
|
|
8
8
|
getBaseUrl(region?: Region): string;
|
|
9
9
|
setAccessTokens(accessTokens: AccessTokens): this;
|
|
10
10
|
private request;
|
|
11
|
-
authStatus(accessToken: string, region: Region, verbose?: boolean): Promise<
|
|
11
|
+
authStatus(accessToken: string, region: Region, verbose?: boolean): Promise<{
|
|
12
|
+
viewerId: string;
|
|
13
|
+
organizations: string[];
|
|
14
|
+
}>;
|
|
12
15
|
prepareFileUpload({ organizationId, name, version, filesHash, filename, isUpsert, }: RegistryApiTypes.PrepareFileuploadParams): Promise<RegistryApiTypes.PrepareFileuploadOKResponse>;
|
|
13
16
|
pushApi({ organizationId, name, version, rootFilePath, filePaths, branch, isUpsert, }: RegistryApiTypes.PushApiParams): Promise<void>;
|
|
14
17
|
}
|
|
@@ -50,13 +50,13 @@ class RegistryApi {
|
|
|
50
50
|
return __awaiter(this, void 0, void 0, function* () {
|
|
51
51
|
try {
|
|
52
52
|
const response = yield this.request('', { headers: { authorization: accessToken } }, region);
|
|
53
|
-
return response.
|
|
53
|
+
return yield response.json();
|
|
54
54
|
}
|
|
55
55
|
catch (error) {
|
|
56
56
|
if (verbose) {
|
|
57
57
|
console.log(error);
|
|
58
58
|
}
|
|
59
|
-
|
|
59
|
+
throw error;
|
|
60
60
|
}
|
|
61
61
|
});
|
|
62
62
|
}
|
|
@@ -86,7 +86,7 @@ class RegistryApi {
|
|
|
86
86
|
method: 'PUT',
|
|
87
87
|
headers: {
|
|
88
88
|
'content-type': 'application/json',
|
|
89
|
-
authorization: this.accessToken
|
|
89
|
+
authorization: this.accessToken,
|
|
90
90
|
},
|
|
91
91
|
body: JSON.stringify({
|
|
92
92
|
rootFilePath,
|
package/lib/ref-utils.js
CHANGED
|
@@ -56,7 +56,7 @@ function pointerBaseName(pointer) {
|
|
|
56
56
|
exports.pointerBaseName = pointerBaseName;
|
|
57
57
|
function refBaseName(ref) {
|
|
58
58
|
const parts = ref.split(/[\/\\]/); // split by '\' and '/'
|
|
59
|
-
return parts[parts.length - 1].
|
|
59
|
+
return parts[parts.length - 1].replace(/\.[^.]+$/, ''); // replace extension with empty string
|
|
60
60
|
}
|
|
61
61
|
exports.refBaseName = refBaseName;
|
|
62
62
|
function isAbsoluteUrl(ref) {
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.asserts = exports.runOnValuesSet = exports.runOnKeysSet = void 0;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
exports.runOnKeysSet = new Set([
|
|
6
|
+
'mutuallyExclusive',
|
|
7
|
+
'mutuallyRequired',
|
|
8
|
+
'enum',
|
|
9
|
+
'pattern',
|
|
10
|
+
'minLength',
|
|
11
|
+
'maxLength',
|
|
12
|
+
'casing',
|
|
13
|
+
'sortOrder',
|
|
14
|
+
'disallowed',
|
|
15
|
+
'required',
|
|
16
|
+
]);
|
|
17
|
+
exports.runOnValuesSet = new Set([
|
|
18
|
+
'pattern',
|
|
19
|
+
'enum',
|
|
20
|
+
'defined',
|
|
21
|
+
'undefined',
|
|
22
|
+
'nonEmpty',
|
|
23
|
+
'minLength',
|
|
24
|
+
'maxLength',
|
|
25
|
+
'casing',
|
|
26
|
+
'sortOrder',
|
|
27
|
+
]);
|
|
28
|
+
exports.asserts = {
|
|
29
|
+
pattern: (value, condition) => {
|
|
30
|
+
if (typeof value === 'undefined')
|
|
31
|
+
return true; // property doesn't exist, no need to lint it with this assert
|
|
32
|
+
const values = typeof value === 'string' ? [value] : value;
|
|
33
|
+
const regexOptions = condition.match(/(\b\/\b)(.+)/g) || ['/'];
|
|
34
|
+
condition = condition.slice(1).replace(regexOptions[0], '');
|
|
35
|
+
const regx = new RegExp(condition, regexOptions[0].slice(1));
|
|
36
|
+
for (let _val of values) {
|
|
37
|
+
if (!_val.match(regx)) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
},
|
|
43
|
+
enum: (value, condition) => {
|
|
44
|
+
if (typeof value === 'undefined')
|
|
45
|
+
return true; // property doesn't exist, no need to lint it with this assert
|
|
46
|
+
const values = typeof value === 'string' ? [value] : value;
|
|
47
|
+
for (let _val of values) {
|
|
48
|
+
if (!condition.includes(_val)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
},
|
|
54
|
+
defined: (value, condition = true) => {
|
|
55
|
+
const isDefined = typeof value !== 'undefined';
|
|
56
|
+
return condition ? isDefined : !isDefined;
|
|
57
|
+
},
|
|
58
|
+
required: (value, keys) => {
|
|
59
|
+
for (const requiredKey of keys) {
|
|
60
|
+
if (!value.includes(requiredKey)) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
},
|
|
66
|
+
disallowed: (value, condition) => {
|
|
67
|
+
if (typeof value === 'undefined')
|
|
68
|
+
return true; // property doesn't exist, no need to lint it with this assert
|
|
69
|
+
const values = typeof value === 'string' ? [value] : value;
|
|
70
|
+
for (let _val of values) {
|
|
71
|
+
if (condition.includes(_val)) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
},
|
|
77
|
+
undefined: (value, condition = true) => {
|
|
78
|
+
const isUndefined = typeof value === 'undefined';
|
|
79
|
+
return condition ? isUndefined : !isUndefined;
|
|
80
|
+
},
|
|
81
|
+
nonEmpty: (value, condition = true) => {
|
|
82
|
+
const isEmpty = typeof value === 'undefined' || value === null || value === '';
|
|
83
|
+
return condition ? !isEmpty : isEmpty;
|
|
84
|
+
},
|
|
85
|
+
minLength: (value, condition) => {
|
|
86
|
+
if (typeof value === 'undefined')
|
|
87
|
+
return true; // property doesn't exist, no need to lint it with this assert
|
|
88
|
+
return value.length >= condition;
|
|
89
|
+
},
|
|
90
|
+
maxLength: (value, condition) => {
|
|
91
|
+
if (typeof value === 'undefined')
|
|
92
|
+
return true; // property doesn't exist, no need to lint it with this assert
|
|
93
|
+
return value.length <= condition;
|
|
94
|
+
},
|
|
95
|
+
casing: (value, condition) => {
|
|
96
|
+
if (typeof value === 'undefined')
|
|
97
|
+
return true; // property doesn't exist, no need to lint it with this assert
|
|
98
|
+
const values = typeof value === 'string' ? [value] : value;
|
|
99
|
+
for (let _val of values) {
|
|
100
|
+
let matchCase = false;
|
|
101
|
+
switch (condition) {
|
|
102
|
+
case 'camelCase':
|
|
103
|
+
matchCase = !!_val.match(/^[a-z][a-zA-Z0-9]+$/g);
|
|
104
|
+
break;
|
|
105
|
+
case 'kebab-case':
|
|
106
|
+
matchCase = !!_val.match(/^([a-z][a-z0-9]*)(-[a-z0-9]+)*$/g);
|
|
107
|
+
break;
|
|
108
|
+
case 'snake_case':
|
|
109
|
+
matchCase = !!_val.match(/^([a-z][a-z0-9]*)(_[a-z0-9]+)*$/g);
|
|
110
|
+
break;
|
|
111
|
+
case 'PascalCase':
|
|
112
|
+
matchCase = !!_val.match(/^[A-Z][a-zA-Z0-9]+$/g);
|
|
113
|
+
break;
|
|
114
|
+
case 'MACRO_CASE':
|
|
115
|
+
matchCase = !!_val.match(/^([A-Z][A-Z0-9]*)(_[A-Z0-9]+)*$/g);
|
|
116
|
+
break;
|
|
117
|
+
case 'COBOL-CASE':
|
|
118
|
+
matchCase = !!_val.match(/^([A-Z][A-Z0-9]*)(-[A-Z0-9]+)*$/g);
|
|
119
|
+
break;
|
|
120
|
+
case 'flatcase':
|
|
121
|
+
matchCase = !!_val.match(/^[a-z][a-z0-9]+$/g);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
if (!matchCase) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return true;
|
|
129
|
+
},
|
|
130
|
+
sortOrder: (value, condition) => {
|
|
131
|
+
if (typeof value === 'undefined')
|
|
132
|
+
return true;
|
|
133
|
+
return utils_1.isOrdered(value, condition);
|
|
134
|
+
},
|
|
135
|
+
mutuallyExclusive: (value, condition) => {
|
|
136
|
+
return utils_1.getIntersectionLength(value, condition) < 2;
|
|
137
|
+
},
|
|
138
|
+
mutuallyRequired: (value, condition) => {
|
|
139
|
+
return utils_1.getIntersectionLength(value, condition) > 0
|
|
140
|
+
? utils_1.getIntersectionLength(value, condition) === condition.length
|
|
141
|
+
: true;
|
|
142
|
+
},
|
|
143
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Assertions = void 0;
|
|
4
|
+
const asserts_1 = require("./asserts");
|
|
5
|
+
const utils_1 = require("./utils");
|
|
6
|
+
const Assertions = (opts) => {
|
|
7
|
+
let visitors = [];
|
|
8
|
+
// As 'Assertions' has an array of asserts,
|
|
9
|
+
// that array spreads into an 'opts' object on init rules phase here
|
|
10
|
+
// https://github.com/Redocly/openapi-cli/blob/master/packages/core/src/config/config.ts#L311
|
|
11
|
+
// that is why we need to iterate through 'opts' values;
|
|
12
|
+
// before - filter only object 'opts' values
|
|
13
|
+
const assertions = Object.values(opts).filter((opt) => typeof opt === 'object' && opt !== null);
|
|
14
|
+
for (const [index, assertion] of assertions.entries()) {
|
|
15
|
+
const assertId = (assertion.assertionId && `${assertion.assertionId} assertion`) || `assertion #${index + 1}`;
|
|
16
|
+
if (!assertion.subject) {
|
|
17
|
+
throw new Error(`${assertId}: 'subject' is required`);
|
|
18
|
+
}
|
|
19
|
+
const subjects = Array.isArray(assertion.subject)
|
|
20
|
+
? assertion.subject
|
|
21
|
+
: [assertion.subject];
|
|
22
|
+
const assertsToApply = Object.keys(asserts_1.asserts)
|
|
23
|
+
.filter((assertName) => assertion[assertName] !== undefined)
|
|
24
|
+
.map((assertName) => {
|
|
25
|
+
return {
|
|
26
|
+
assertId,
|
|
27
|
+
name: assertName,
|
|
28
|
+
conditions: assertion[assertName],
|
|
29
|
+
message: assertion.message,
|
|
30
|
+
severity: assertion.severity || 'error',
|
|
31
|
+
suggest: assertion.suggest || [],
|
|
32
|
+
runsOnKeys: asserts_1.runOnKeysSet.has(assertName),
|
|
33
|
+
runsOnValues: asserts_1.runOnValuesSet.has(assertName),
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
const shouldRunOnKeys = assertsToApply.find((assert) => assert.runsOnKeys && !assert.runsOnValues);
|
|
37
|
+
const shouldRunOnValues = assertsToApply.find((assert) => assert.runsOnValues && !assert.runsOnKeys);
|
|
38
|
+
if (shouldRunOnValues && !assertion.property) {
|
|
39
|
+
throw new Error(`${shouldRunOnValues.name} can't be used on all keys. Please provide a single property.`);
|
|
40
|
+
}
|
|
41
|
+
if (shouldRunOnKeys && assertion.property) {
|
|
42
|
+
throw new Error(`${shouldRunOnKeys.name} can't be used on a single property. Please use 'property'.`);
|
|
43
|
+
}
|
|
44
|
+
for (const subject of subjects) {
|
|
45
|
+
const subjectVisitor = utils_1.buildSubjectVisitor(assertion.property, assertsToApply, assertion.context);
|
|
46
|
+
const visitorObject = utils_1.buildVisitorObject(subject, assertion.context, subjectVisitor);
|
|
47
|
+
visitors.push(visitorObject);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return visitors;
|
|
51
|
+
};
|
|
52
|
+
exports.Assertions = Assertions;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ProblemSeverity, UserContext } from '../../../walk';
|
|
2
|
+
export declare type OrderDirection = 'asc' | 'desc';
|
|
3
|
+
export declare type OrderOptions = {
|
|
4
|
+
direction: OrderDirection;
|
|
5
|
+
property: string;
|
|
6
|
+
};
|
|
7
|
+
export declare type AssertToApply = {
|
|
8
|
+
name: string;
|
|
9
|
+
assertId?: string;
|
|
10
|
+
conditions: any;
|
|
11
|
+
message?: string;
|
|
12
|
+
severity?: ProblemSeverity;
|
|
13
|
+
suggest?: string[];
|
|
14
|
+
runsOnKeys: boolean;
|
|
15
|
+
runsOnValues: boolean;
|
|
16
|
+
};
|
|
17
|
+
export declare function buildVisitorObject(subject: string, context: Record<string, any>[], subjectVisitor: any): Record<string, any>;
|
|
18
|
+
export declare function buildSubjectVisitor(properties: string | string[], asserts: AssertToApply[], context?: Record<string, any>[]): (node: any, { report, location, key, type }: UserContext) => void;
|
|
19
|
+
export declare function getIntersectionLength(keys: string[], properties: string[]): number;
|
|
20
|
+
export declare function isOrdered(value: any[], options: OrderOptions | OrderDirection): boolean;
|