@redocly/openapi-core 1.0.0-beta.109 → 1.0.0-beta.110
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/README.md +2 -2
- package/lib/config/config-resolvers.js +21 -3
- package/lib/config/config.d.ts +1 -0
- package/lib/config/config.js +1 -0
- package/lib/config/load.d.ts +8 -2
- package/lib/config/load.js +4 -2
- package/lib/config/types.d.ts +10 -0
- package/lib/config/utils.js +2 -2
- package/lib/rules/ajv.d.ts +1 -1
- package/lib/rules/ajv.js +5 -5
- package/lib/rules/common/assertions/asserts.d.ts +3 -5
- package/lib/rules/common/assertions/asserts.js +137 -97
- package/lib/rules/common/assertions/index.js +2 -6
- package/lib/rules/common/assertions/utils.d.ts +12 -6
- package/lib/rules/common/assertions/utils.js +33 -20
- package/lib/rules/utils.js +1 -1
- package/lib/types/redocly-yaml.js +16 -1
- package/package.json +3 -5
- package/src/__tests__/lint.test.ts +88 -0
- package/src/config/__tests__/config-resolvers.test.ts +37 -1
- package/src/config/__tests__/config.test.ts +5 -0
- package/src/config/__tests__/fixtures/resolve-config/local-config-with-custom-function.yaml +16 -0
- package/src/config/__tests__/fixtures/resolve-config/local-config-with-wrong-custom-function.yaml +16 -0
- package/src/config/__tests__/fixtures/resolve-config/plugin.js +11 -0
- package/src/config/__tests__/load.test.ts +1 -1
- package/src/config/__tests__/resolve-plugins.test.ts +3 -3
- package/src/config/config-resolvers.ts +28 -5
- package/src/config/config.ts +2 -0
- package/src/config/load.ts +10 -4
- package/src/config/types.ts +13 -0
- package/src/config/utils.ts +1 -0
- package/src/rules/ajv.ts +4 -4
- package/src/rules/common/assertions/__tests__/asserts.test.ts +491 -428
- package/src/rules/common/assertions/asserts.ts +155 -97
- package/src/rules/common/assertions/index.ts +2 -11
- package/src/rules/common/assertions/utils.ts +66 -36
- package/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts +51 -2
- package/src/rules/utils.ts +2 -1
- package/src/types/redocly-yaml.ts +16 -0
- package/tsconfig.tsbuildinfo +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ See https://github.com/Redocly/redocly-cli
|
|
|
10
10
|
import { formatProblems, lint, loadConfig } from '@redocly/openapi-core';
|
|
11
11
|
|
|
12
12
|
const pathToApi = 'openapi.yaml';
|
|
13
|
-
const config = loadConfig('optional/path/to/.redocly.yaml');
|
|
13
|
+
const config = loadConfig({ configPath: 'optional/path/to/.redocly.yaml' });
|
|
14
14
|
const lintResults = await lint({ ref: pathToApi, config });
|
|
15
15
|
```
|
|
16
16
|
|
|
@@ -20,6 +20,6 @@ const lintResults = await lint({ ref: pathToApi, config });
|
|
|
20
20
|
import { formatProblems, bundle, loadConfig } from '@redocly/openapi-core';
|
|
21
21
|
|
|
22
22
|
const pathToApi = 'openapi.yaml';
|
|
23
|
-
const config = loadConfig('optional/path/to/.redocly.yaml');
|
|
23
|
+
const config = loadConfig({ configPath: 'optional/path/to/.redocly.yaml' });
|
|
24
24
|
const { bundle, problems } = await bundle({ ref: pathToApi, config });
|
|
25
25
|
```
|
|
@@ -30,6 +30,7 @@ const env_1 = require("../env");
|
|
|
30
30
|
const utils_2 = require("../utils");
|
|
31
31
|
const config_1 = require("./config");
|
|
32
32
|
const logger_1 = require("../logger");
|
|
33
|
+
const asserts_1 = require("../rules/common/assertions/asserts");
|
|
33
34
|
function resolveConfig(rawConfig, configPath) {
|
|
34
35
|
var _a, _b, _c, _d, _e;
|
|
35
36
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -133,6 +134,9 @@ function resolvePlugins(plugins, configPath = '') {
|
|
|
133
134
|
plugin.decorators.oas2 = utils_1.prefixRules(pluginModule.decorators.oas2, id);
|
|
134
135
|
}
|
|
135
136
|
}
|
|
137
|
+
if (pluginModule.assertions) {
|
|
138
|
+
plugin.assertions = pluginModule.assertions;
|
|
139
|
+
}
|
|
136
140
|
return plugin;
|
|
137
141
|
})
|
|
138
142
|
.filter(utils_2.isDefined);
|
|
@@ -196,8 +200,7 @@ function resolveAndMergeNestedStyleguideConfig({ styleguideConfig, configPath =
|
|
|
196
200
|
function resolveStyleguideConfig(opts, parentConfigPaths = [], extendPaths = []) {
|
|
197
201
|
return __awaiter(this, void 0, void 0, function* () {
|
|
198
202
|
const resolvedStyleguideConfig = yield resolveAndMergeNestedStyleguideConfig(opts, parentConfigPaths, extendPaths);
|
|
199
|
-
return Object.assign(Object.assign({}, resolvedStyleguideConfig), { rules: resolvedStyleguideConfig.rules &&
|
|
200
|
-
groupStyleguideAssertionRules(resolvedStyleguideConfig.rules) });
|
|
203
|
+
return Object.assign(Object.assign({}, resolvedStyleguideConfig), { rules: resolvedStyleguideConfig.rules && groupStyleguideAssertionRules(resolvedStyleguideConfig) });
|
|
201
204
|
});
|
|
202
205
|
}
|
|
203
206
|
exports.resolveStyleguideConfig = resolveStyleguideConfig;
|
|
@@ -238,7 +241,7 @@ function getMergedRawStyleguideConfig(rootStyleguideConfig, apiStyleguideConfig)
|
|
|
238
241
|
: rootStyleguideConfig.recommendedFallback });
|
|
239
242
|
return resultLint;
|
|
240
243
|
}
|
|
241
|
-
function groupStyleguideAssertionRules(rules) {
|
|
244
|
+
function groupStyleguideAssertionRules({ rules, plugins, }) {
|
|
242
245
|
if (!rules) {
|
|
243
246
|
return rules;
|
|
244
247
|
}
|
|
@@ -249,6 +252,21 @@ function groupStyleguideAssertionRules(rules) {
|
|
|
249
252
|
for (const [ruleKey, rule] of Object.entries(rules)) {
|
|
250
253
|
if (ruleKey.startsWith('assert/') && typeof rule === 'object' && rule !== null) {
|
|
251
254
|
const assertion = rule;
|
|
255
|
+
if (plugins) {
|
|
256
|
+
for (const field of Object.keys(assertion)) {
|
|
257
|
+
const [pluginId, fn] = field.split('/');
|
|
258
|
+
if (!pluginId || !fn)
|
|
259
|
+
continue;
|
|
260
|
+
const plugin = plugins.find((plugin) => plugin.id === pluginId);
|
|
261
|
+
if (!plugin) {
|
|
262
|
+
throw Error(logger_1.colorize.red(`Plugin ${logger_1.colorize.blue(pluginId)} isn't found.`));
|
|
263
|
+
}
|
|
264
|
+
if (!plugin.assertions || !plugin.assertions[fn]) {
|
|
265
|
+
throw Error(`Plugin ${logger_1.colorize.red(pluginId)} doesn't export assertions function with name ${logger_1.colorize.red(fn)}.`);
|
|
266
|
+
}
|
|
267
|
+
asserts_1.asserts[field] = asserts_1.buildAssertCustomFunction(plugin.assertions[fn]);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
252
270
|
assertions.push(Object.assign(Object.assign({}, assertion), { assertionId: ruleKey.replace('assert/', '') }));
|
|
253
271
|
}
|
|
254
272
|
else {
|
package/lib/config/config.d.ts
CHANGED
package/lib/config/config.js
CHANGED
|
@@ -249,6 +249,7 @@ class Config {
|
|
|
249
249
|
this.resolve = utils_2.getResolveConfig(rawConfig === null || rawConfig === void 0 ? void 0 : rawConfig.resolve);
|
|
250
250
|
this.region = rawConfig.region;
|
|
251
251
|
this.organization = rawConfig.organization;
|
|
252
|
+
this.files = rawConfig.files || [];
|
|
252
253
|
}
|
|
253
254
|
}
|
|
254
255
|
exports.Config = Config;
|
package/lib/config/load.d.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { Config } from './config';
|
|
2
|
-
import type { RawConfig } from './types';
|
|
2
|
+
import type { RawConfig, Region } from './types';
|
|
3
3
|
import { RegionalTokenWithValidity } from '../redocly/redocly-client-types';
|
|
4
|
-
export declare function loadConfig(
|
|
4
|
+
export declare function loadConfig(options?: {
|
|
5
|
+
configPath?: string;
|
|
6
|
+
customExtends?: string[];
|
|
7
|
+
processRawConfig?: (rawConfig: RawConfig) => void | Promise<void>;
|
|
8
|
+
files?: string[];
|
|
9
|
+
region?: Region;
|
|
10
|
+
}): Promise<Config>;
|
|
5
11
|
export declare const CONFIG_FILE_NAMES: string[];
|
|
6
12
|
export declare function findConfig(dir?: string): string | undefined;
|
|
7
13
|
export declare function getConfig(configPath?: string | undefined): Promise<RawConfig>;
|
package/lib/config/load.js
CHANGED
|
@@ -59,9 +59,11 @@ function addConfigMetadata({ rawConfig, customExtends, configPath, tokens, }) {
|
|
|
59
59
|
return config_resolvers_1.resolveConfig(rawConfig, configPath);
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
|
-
function loadConfig(
|
|
62
|
+
function loadConfig(options = {}) {
|
|
63
63
|
return __awaiter(this, void 0, void 0, function* () {
|
|
64
|
-
const
|
|
64
|
+
const { configPath = findConfig(), customExtends, processRawConfig, files, region } = options;
|
|
65
|
+
const config = yield getConfig(configPath);
|
|
66
|
+
const rawConfig = Object.assign(Object.assign({}, config), { files: files !== null && files !== void 0 ? files : config.files, region: region !== null && region !== void 0 ? region : config.region });
|
|
65
67
|
if (typeof processRawConfig === 'function') {
|
|
66
68
|
yield processRawConfig(rawConfig);
|
|
67
69
|
}
|
package/lib/config/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ProblemSeverity } from '../walk';
|
|
2
2
|
import type { Oas3PreprocessorsSet, OasMajorVersion, Oas3DecoratorsSet, Oas2RuleSet, Oas2PreprocessorsSet, Oas2DecoratorsSet, Oas3RuleSet, OasVersion } from '../oas-types';
|
|
3
3
|
import type { NodeType } from '../types';
|
|
4
|
+
import { Location } from '../ref-utils';
|
|
4
5
|
export declare type RuleSeverity = ProblemSeverity | 'off';
|
|
5
6
|
export declare type PreprocessorSeverity = RuleSeverity | 'on';
|
|
6
7
|
export declare type RuleConfig = RuleSeverity | ({
|
|
@@ -50,6 +51,12 @@ export declare type CustomRulesConfig = {
|
|
|
50
51
|
oas3?: Oas3RuleSet;
|
|
51
52
|
oas2?: Oas2RuleSet;
|
|
52
53
|
};
|
|
54
|
+
export declare type AssertResult = {
|
|
55
|
+
message?: string;
|
|
56
|
+
location?: Location;
|
|
57
|
+
};
|
|
58
|
+
export declare type CustomFunction = (value: any, options: unknown, baseLocation: Location) => AssertResult[];
|
|
59
|
+
export declare type AssertionsConfig = Record<string, CustomFunction>;
|
|
53
60
|
export declare type Plugin = {
|
|
54
61
|
id: string;
|
|
55
62
|
configs?: Record<string, PluginStyleguideConfig>;
|
|
@@ -57,6 +64,7 @@ export declare type Plugin = {
|
|
|
57
64
|
preprocessors?: PreprocessorsConfig;
|
|
58
65
|
decorators?: DecoratorsConfig;
|
|
59
66
|
typeExtension?: TypeExtensionsConfig;
|
|
67
|
+
assertions?: AssertionsConfig;
|
|
60
68
|
};
|
|
61
69
|
export declare type PluginStyleguideConfig = Omit<StyleguideRawConfig, 'plugins' | 'extends'>;
|
|
62
70
|
export declare type ResolveHeader = {
|
|
@@ -101,6 +109,7 @@ export declare type DeprecatedInApi = {
|
|
|
101
109
|
};
|
|
102
110
|
export declare type ResolvedApi = Omit<Api, 'styleguide'> & {
|
|
103
111
|
styleguide: ResolvedStyleguideConfig;
|
|
112
|
+
files?: string[];
|
|
104
113
|
};
|
|
105
114
|
export declare type RawConfig = {
|
|
106
115
|
apis?: Record<string, Api>;
|
|
@@ -108,6 +117,7 @@ export declare type RawConfig = {
|
|
|
108
117
|
resolve?: RawResolveConfig;
|
|
109
118
|
region?: Region;
|
|
110
119
|
organization?: string;
|
|
120
|
+
files?: string[];
|
|
111
121
|
} & FeaturesConfig;
|
|
112
122
|
export declare type FlatApi = Omit<Api, 'styleguide'> & Omit<ApiStyleguideRawConfig, 'doNotResolveExamples'>;
|
|
113
123
|
export declare type FlatRawConfig = Omit<RawConfig, 'styleguide' | 'resolve' | 'apis'> & Omit<StyleguideRawConfig, 'doNotResolveExamples'> & {
|
package/lib/config/utils.js
CHANGED
|
@@ -139,7 +139,7 @@ function mergeExtends(rulesConfList) {
|
|
|
139
139
|
}
|
|
140
140
|
exports.mergeExtends = mergeExtends;
|
|
141
141
|
function getMergedConfig(config, apiName) {
|
|
142
|
-
var _a, _b, _c, _d, _e, _f;
|
|
142
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
143
143
|
const extendPaths = [
|
|
144
144
|
...Object.values(config.apis).map((api) => { var _a; return (_a = api === null || api === void 0 ? void 0 : api.styleguide) === null || _a === void 0 ? void 0 : _a.extendPaths; }),
|
|
145
145
|
(_b = (_a = config.rawConfig) === null || _a === void 0 ? void 0 : _a.styleguide) === null || _b === void 0 ? void 0 : _b.extendPaths,
|
|
@@ -156,7 +156,7 @@ function getMergedConfig(config, apiName) {
|
|
|
156
156
|
? new config_1.Config(Object.assign(Object.assign({}, config.rawConfig), { styleguide: Object.assign(Object.assign({}, (config.apis[apiName]
|
|
157
157
|
? config.apis[apiName].styleguide
|
|
158
158
|
: config.rawConfig.styleguide)), { extendPaths,
|
|
159
|
-
pluginPaths }), 'features.openapi': Object.assign(Object.assign({}, config['features.openapi']), (_e = config.apis[apiName]) === null || _e === void 0 ? void 0 : _e['features.openapi']), 'features.mockServer': Object.assign(Object.assign({}, config['features.mockServer']), (_f = config.apis[apiName]) === null || _f === void 0 ? void 0 : _f['features.mockServer']) }), config.configFile)
|
|
159
|
+
pluginPaths }), 'features.openapi': Object.assign(Object.assign({}, config['features.openapi']), (_e = config.apis[apiName]) === null || _e === void 0 ? void 0 : _e['features.openapi']), 'features.mockServer': Object.assign(Object.assign({}, config['features.mockServer']), (_f = config.apis[apiName]) === null || _f === void 0 ? void 0 : _f['features.mockServer']), files: [...config.files, ...((_j = (_h = (_g = config.apis) === null || _g === void 0 ? void 0 : _g[apiName]) === null || _h === void 0 ? void 0 : _h.files) !== null && _j !== void 0 ? _j : [])] }), config.configFile)
|
|
160
160
|
: config;
|
|
161
161
|
}
|
|
162
162
|
exports.getMergedConfig = getMergedConfig;
|
package/lib/rules/ajv.d.ts
CHANGED
package/lib/rules/ajv.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.validateJsonSchema = exports.releaseAjvInstance = void 0;
|
|
4
|
-
const
|
|
4
|
+
const _2020_1 = require("@redocly/ajv/dist/2020");
|
|
5
5
|
const ref_utils_1 = require("../ref-utils");
|
|
6
6
|
let ajvInstance = null;
|
|
7
7
|
function releaseAjvInstance() {
|
|
@@ -10,7 +10,7 @@ function releaseAjvInstance() {
|
|
|
10
10
|
exports.releaseAjvInstance = releaseAjvInstance;
|
|
11
11
|
function getAjv(resolve, allowAdditionalProperties) {
|
|
12
12
|
if (!ajvInstance) {
|
|
13
|
-
ajvInstance = new
|
|
13
|
+
ajvInstance = new _2020_1.default({
|
|
14
14
|
schemaId: '$id',
|
|
15
15
|
meta: true,
|
|
16
16
|
allErrors: true,
|
|
@@ -20,7 +20,7 @@ function getAjv(resolve, allowAdditionalProperties) {
|
|
|
20
20
|
discriminator: true,
|
|
21
21
|
allowUnionTypes: true,
|
|
22
22
|
validateFormats: false,
|
|
23
|
-
|
|
23
|
+
defaultUnevaluatedProperties: allowAdditionalProperties,
|
|
24
24
|
loadSchemaSync(base, $ref) {
|
|
25
25
|
const resolvedRef = resolve({ $ref }, base.split('#')[0]);
|
|
26
26
|
if (!resolvedRef || !resolvedRef.location)
|
|
@@ -68,8 +68,8 @@ function validateJsonSchema(data, schema, schemaLoc, instancePath, resolve, allo
|
|
|
68
68
|
if (propName) {
|
|
69
69
|
message = `\`${propName}\` property ${message}`;
|
|
70
70
|
}
|
|
71
|
-
if (error.keyword === 'additionalProperties') {
|
|
72
|
-
const property = error.params.additionalProperty;
|
|
71
|
+
if (error.keyword === 'additionalProperties' || error.keyword === 'unevaluatedProperties') {
|
|
72
|
+
const property = error.params.additionalProperty || error.params.unevaluatedProperty;
|
|
73
73
|
message = `${message} \`${property}\``;
|
|
74
74
|
error.instancePath += '/' + ref_utils_1.escapePointer(property);
|
|
75
75
|
}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
+
import { AssertResult, CustomFunction } from 'core/src/config/types';
|
|
1
2
|
import { Location } from '../../../ref-utils';
|
|
2
|
-
declare type
|
|
3
|
-
isValid: boolean;
|
|
4
|
-
location?: Location;
|
|
5
|
-
};
|
|
6
|
-
declare type Asserts = Record<string, (value: any, condition: any, baseLocation: Location, rawValue?: any) => AssertResult>;
|
|
3
|
+
declare type Asserts = Record<string, (value: any, condition: any, baseLocation: Location, rawValue?: any) => AssertResult[]>;
|
|
7
4
|
export declare const runOnKeysSet: Set<string>;
|
|
8
5
|
export declare const runOnValuesSet: Set<string>;
|
|
9
6
|
export declare const asserts: Asserts;
|
|
7
|
+
export declare function buildAssertCustomFunction(fn: CustomFunction): (value: string[], options: any, baseLocation: Location) => any;
|
|
10
8
|
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.asserts = exports.runOnValuesSet = exports.runOnKeysSet = void 0;
|
|
3
|
+
exports.buildAssertCustomFunction = exports.asserts = exports.runOnValuesSet = exports.runOnKeysSet = void 0;
|
|
4
4
|
const utils_1 = require("../../../utils");
|
|
5
5
|
const utils_2 = require("./utils");
|
|
6
6
|
exports.runOnKeysSet = new Set([
|
|
@@ -32,145 +32,185 @@ exports.runOnValuesSet = new Set([
|
|
|
32
32
|
exports.asserts = {
|
|
33
33
|
pattern: (value, condition, baseLocation) => {
|
|
34
34
|
if (typeof value === 'undefined')
|
|
35
|
-
return
|
|
35
|
+
return []; // property doesn't exist, no need to lint it with this assert
|
|
36
36
|
const values = utils_1.isString(value) ? [value] : value;
|
|
37
37
|
const regx = utils_2.regexFromString(condition);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
38
|
+
return values
|
|
39
|
+
.map((_val) => !(regx === null || regx === void 0 ? void 0 : regx.test(_val)) && {
|
|
40
|
+
message: `"${_val}" should match a regex ${condition}`,
|
|
41
|
+
location: utils_1.isString(value) ? baseLocation : baseLocation.key(),
|
|
42
|
+
})
|
|
43
|
+
.filter(utils_1.isTruthy);
|
|
44
44
|
},
|
|
45
45
|
enum: (value, condition, baseLocation) => {
|
|
46
46
|
if (typeof value === 'undefined')
|
|
47
|
-
return
|
|
47
|
+
return []; // property doesn't exist, no need to lint it with this assert
|
|
48
48
|
const values = utils_1.isString(value) ? [value] : value;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return { isValid: true };
|
|
49
|
+
return values
|
|
50
|
+
.map((_val) => !condition.includes(_val) && {
|
|
51
|
+
message: `"${_val}" should be one of the predefined values`,
|
|
52
|
+
location: utils_1.isString(value) ? baseLocation : baseLocation.child(_val).key(),
|
|
53
|
+
})
|
|
54
|
+
.filter(utils_1.isTruthy);
|
|
58
55
|
},
|
|
59
56
|
defined: (value, condition = true, baseLocation) => {
|
|
60
57
|
const isDefined = typeof value !== 'undefined';
|
|
61
|
-
|
|
58
|
+
const isValid = condition ? isDefined : !isDefined;
|
|
59
|
+
return isValid
|
|
60
|
+
? []
|
|
61
|
+
: [
|
|
62
|
+
{
|
|
63
|
+
message: condition ? `Should be defined` : 'Should be not defined',
|
|
64
|
+
location: baseLocation,
|
|
65
|
+
},
|
|
66
|
+
];
|
|
62
67
|
},
|
|
63
68
|
required: (value, keys, baseLocation) => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
69
|
+
return keys
|
|
70
|
+
.map((requiredKey) => !value.includes(requiredKey) && {
|
|
71
|
+
message: `${requiredKey} is required`,
|
|
72
|
+
location: baseLocation.key(),
|
|
73
|
+
})
|
|
74
|
+
.filter(utils_1.isTruthy);
|
|
70
75
|
},
|
|
71
76
|
disallowed: (value, condition, baseLocation) => {
|
|
72
77
|
if (typeof value === 'undefined')
|
|
73
|
-
return
|
|
78
|
+
return []; // property doesn't exist, no need to lint it with this assert
|
|
74
79
|
const values = utils_1.isString(value) ? [value] : value;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return { isValid: true };
|
|
80
|
+
return values
|
|
81
|
+
.map((_val) => condition.includes(_val) && {
|
|
82
|
+
message: `"${_val}" is disallowed`,
|
|
83
|
+
location: utils_1.isString(value) ? baseLocation : baseLocation.child(_val).key(),
|
|
84
|
+
})
|
|
85
|
+
.filter(utils_1.isTruthy);
|
|
84
86
|
},
|
|
85
87
|
undefined: (value, condition = true, baseLocation) => {
|
|
86
88
|
const isUndefined = typeof value === 'undefined';
|
|
87
|
-
|
|
89
|
+
const isValid = condition ? isUndefined : !isUndefined;
|
|
90
|
+
return isValid
|
|
91
|
+
? []
|
|
92
|
+
: [
|
|
93
|
+
{
|
|
94
|
+
message: condition ? `Should not be defined` : 'Should be defined',
|
|
95
|
+
location: baseLocation,
|
|
96
|
+
},
|
|
97
|
+
];
|
|
88
98
|
},
|
|
89
99
|
nonEmpty: (value, condition = true, baseLocation) => {
|
|
90
100
|
const isEmpty = typeof value === 'undefined' || value === null || value === '';
|
|
91
|
-
|
|
101
|
+
const isValid = condition ? !isEmpty : isEmpty;
|
|
102
|
+
return isValid
|
|
103
|
+
? []
|
|
104
|
+
: [
|
|
105
|
+
{
|
|
106
|
+
message: condition ? `Should not be empty` : 'Should be empty',
|
|
107
|
+
location: baseLocation,
|
|
108
|
+
},
|
|
109
|
+
];
|
|
92
110
|
},
|
|
93
111
|
minLength: (value, condition, baseLocation) => {
|
|
94
|
-
if (typeof value === 'undefined')
|
|
95
|
-
return
|
|
96
|
-
return {
|
|
112
|
+
if (typeof value === 'undefined' || value.length >= condition)
|
|
113
|
+
return []; // property doesn't exist, no need to lint it with this assert
|
|
114
|
+
return [{ message: `Should have at least ${condition} characters`, location: baseLocation }];
|
|
97
115
|
},
|
|
98
116
|
maxLength: (value, condition, baseLocation) => {
|
|
99
|
-
if (typeof value === 'undefined')
|
|
100
|
-
return
|
|
101
|
-
return {
|
|
117
|
+
if (typeof value === 'undefined' || value.length <= condition)
|
|
118
|
+
return []; // property doesn't exist, no need to lint it with this assert
|
|
119
|
+
return [{ message: `Should have at most ${condition} characters`, location: baseLocation }];
|
|
102
120
|
},
|
|
103
121
|
casing: (value, condition, baseLocation) => {
|
|
104
122
|
if (typeof value === 'undefined')
|
|
105
|
-
return
|
|
123
|
+
return []; // property doesn't exist, no need to lint it with this assert
|
|
106
124
|
const values = utils_1.isString(value) ? [value] : value;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
case 'MACRO_CASE':
|
|
123
|
-
matchCase = !!_val.match(/^([A-Z][A-Z0-9]*)(_[A-Z0-9]+)*$/g);
|
|
124
|
-
break;
|
|
125
|
-
case 'COBOL-CASE':
|
|
126
|
-
matchCase = !!_val.match(/^([A-Z][A-Z0-9]*)(-[A-Z0-9]+)*$/g);
|
|
127
|
-
break;
|
|
128
|
-
case 'flatcase':
|
|
129
|
-
matchCase = !!_val.match(/^[a-z][a-z0-9]+$/g);
|
|
130
|
-
break;
|
|
131
|
-
}
|
|
132
|
-
if (!matchCase) {
|
|
133
|
-
return {
|
|
134
|
-
isValid: false,
|
|
135
|
-
location: utils_1.isString(value) ? baseLocation : baseLocation.child(_val).key(),
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return { isValid: true };
|
|
125
|
+
const casingRegexes = {
|
|
126
|
+
camelCase: /^[a-z][a-zA-Z0-9]+$/g,
|
|
127
|
+
'kebab-case': /^([a-z][a-z0-9]*)(-[a-z0-9]+)*$/g,
|
|
128
|
+
snake_case: /^([a-z][a-z0-9]*)(_[a-z0-9]+)*$/g,
|
|
129
|
+
PascalCase: /^[A-Z][a-zA-Z0-9]+$/g,
|
|
130
|
+
MACRO_CASE: /^([A-Z][A-Z0-9]*)(_[A-Z0-9]+)*$/g,
|
|
131
|
+
'COBOL-CASE': /^([A-Z][A-Z0-9]*)(-[A-Z0-9]+)*$/g,
|
|
132
|
+
flatcase: /^[a-z][a-z0-9]+$/g,
|
|
133
|
+
};
|
|
134
|
+
return values
|
|
135
|
+
.map((_val) => !_val.match(casingRegexes[condition]) && {
|
|
136
|
+
message: `"${_val}" should use ${condition}`,
|
|
137
|
+
location: utils_1.isString(value) ? baseLocation : baseLocation.child(_val).key(),
|
|
138
|
+
})
|
|
139
|
+
.filter(utils_1.isTruthy);
|
|
140
140
|
},
|
|
141
141
|
sortOrder: (value, condition, baseLocation) => {
|
|
142
|
-
if (typeof value === 'undefined')
|
|
143
|
-
return
|
|
144
|
-
|
|
142
|
+
if (typeof value === 'undefined' || utils_2.isOrdered(value, condition))
|
|
143
|
+
return [];
|
|
144
|
+
const direction = condition.direction || condition;
|
|
145
|
+
const property = condition.property;
|
|
146
|
+
return [
|
|
147
|
+
{
|
|
148
|
+
message: `Should be sorted in ${direction === 'asc' ? 'an ascending' : 'a descending'} order${property ? ` by property ${property}` : ''}`,
|
|
149
|
+
location: baseLocation,
|
|
150
|
+
},
|
|
151
|
+
];
|
|
145
152
|
},
|
|
146
153
|
mutuallyExclusive: (value, condition, baseLocation) => {
|
|
147
|
-
|
|
154
|
+
if (utils_2.getIntersectionLength(value, condition) < 2)
|
|
155
|
+
return [];
|
|
156
|
+
return [
|
|
157
|
+
{
|
|
158
|
+
message: `${condition.join(', ')} keys should be mutually exclusive`,
|
|
159
|
+
location: baseLocation.key(),
|
|
160
|
+
},
|
|
161
|
+
];
|
|
148
162
|
},
|
|
149
163
|
mutuallyRequired: (value, condition, baseLocation) => {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
164
|
+
const isValid = utils_2.getIntersectionLength(value, condition) > 0
|
|
165
|
+
? utils_2.getIntersectionLength(value, condition) === condition.length
|
|
166
|
+
: true;
|
|
167
|
+
return isValid
|
|
168
|
+
? []
|
|
169
|
+
: [
|
|
170
|
+
{
|
|
171
|
+
message: `Properties ${condition.join(', ')} are mutually required`,
|
|
172
|
+
location: baseLocation.key(),
|
|
173
|
+
},
|
|
174
|
+
];
|
|
156
175
|
},
|
|
157
176
|
requireAny: (value, condition, baseLocation) => {
|
|
158
|
-
return
|
|
177
|
+
return utils_2.getIntersectionLength(value, condition) >= 1
|
|
178
|
+
? []
|
|
179
|
+
: [
|
|
180
|
+
{
|
|
181
|
+
message: `Should have any of ${condition.join(', ')}`,
|
|
182
|
+
location: baseLocation.key(),
|
|
183
|
+
},
|
|
184
|
+
];
|
|
159
185
|
},
|
|
160
186
|
ref: (_value, condition, baseLocation, rawValue) => {
|
|
161
187
|
if (typeof rawValue === 'undefined')
|
|
162
|
-
return
|
|
188
|
+
return []; // property doesn't exist, no need to lint it with this assert
|
|
163
189
|
const hasRef = rawValue.hasOwnProperty('$ref');
|
|
164
190
|
if (typeof condition === 'boolean') {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
191
|
+
const isValid = condition ? hasRef : !hasRef;
|
|
192
|
+
return isValid
|
|
193
|
+
? []
|
|
194
|
+
: [
|
|
195
|
+
{
|
|
196
|
+
message: condition ? `should use $ref` : 'should not use $ref',
|
|
197
|
+
location: hasRef ? baseLocation : baseLocation.key(),
|
|
198
|
+
},
|
|
199
|
+
];
|
|
169
200
|
}
|
|
170
201
|
const regex = utils_2.regexFromString(condition);
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
202
|
+
const isValid = hasRef && (regex === null || regex === void 0 ? void 0 : regex.test(rawValue['$ref']));
|
|
203
|
+
return isValid
|
|
204
|
+
? []
|
|
205
|
+
: [
|
|
206
|
+
{
|
|
207
|
+
message: `$ref value should match ${condition}`,
|
|
208
|
+
location: hasRef ? baseLocation : baseLocation.key(),
|
|
209
|
+
},
|
|
210
|
+
];
|
|
175
211
|
},
|
|
176
212
|
};
|
|
213
|
+
function buildAssertCustomFunction(fn) {
|
|
214
|
+
return (value, options, baseLocation) => fn.call(null, value, options, baseLocation);
|
|
215
|
+
}
|
|
216
|
+
exports.buildAssertCustomFunction = buildAssertCustomFunction;
|
|
@@ -7,7 +7,7 @@ const Assertions = (opts) => {
|
|
|
7
7
|
const visitors = [];
|
|
8
8
|
// As 'Assertions' has an array of asserts,
|
|
9
9
|
// that array spreads into an 'opts' object on init rules phase here
|
|
10
|
-
// https://github.com/Redocly/redocly-cli/blob/
|
|
10
|
+
// https://github.com/Redocly/redocly-cli/blob/main/packages/core/src/config/config.ts#L311
|
|
11
11
|
// that is why we need to iterate through 'opts' values;
|
|
12
12
|
// before - filter only object 'opts' values
|
|
13
13
|
const assertions = Object.values(opts).filter((opt) => typeof opt === 'object' && opt !== null);
|
|
@@ -23,12 +23,8 @@ const Assertions = (opts) => {
|
|
|
23
23
|
.filter((assertName) => assertion[assertName] !== undefined)
|
|
24
24
|
.map((assertName) => {
|
|
25
25
|
return {
|
|
26
|
-
assertId,
|
|
27
26
|
name: assertName,
|
|
28
27
|
conditions: assertion[assertName],
|
|
29
|
-
message: assertion.message,
|
|
30
|
-
severity: assertion.severity || 'error',
|
|
31
|
-
suggest: assertion.suggest || [],
|
|
32
28
|
runsOnKeys: asserts_1.runOnKeysSet.has(assertName),
|
|
33
29
|
runsOnValues: asserts_1.runOnValuesSet.has(assertName),
|
|
34
30
|
};
|
|
@@ -42,7 +38,7 @@ const Assertions = (opts) => {
|
|
|
42
38
|
throw new Error(`${shouldRunOnKeys.name} can't be used on a single property. Please use 'property'.`);
|
|
43
39
|
}
|
|
44
40
|
for (const subject of subjects) {
|
|
45
|
-
const subjectVisitor = utils_1.buildSubjectVisitor(assertion
|
|
41
|
+
const subjectVisitor = utils_1.buildSubjectVisitor(assertId, assertion, assertsToApply);
|
|
46
42
|
const visitorObject = utils_1.buildVisitorObject(subject, assertion.context, subjectVisitor);
|
|
47
43
|
visitors.push(visitorObject);
|
|
48
44
|
}
|
|
@@ -1,21 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { RuleSeverity } from '../../../config';
|
|
2
|
+
import { UserContext } from '../../../walk';
|
|
2
3
|
export declare type OrderDirection = 'asc' | 'desc';
|
|
3
4
|
export declare type OrderOptions = {
|
|
4
5
|
direction: OrderDirection;
|
|
5
6
|
property: string;
|
|
6
7
|
};
|
|
8
|
+
declare type Assertion = {
|
|
9
|
+
property: string | string[];
|
|
10
|
+
context?: Record<string, any>[];
|
|
11
|
+
severity?: RuleSeverity;
|
|
12
|
+
suggest?: any[];
|
|
13
|
+
message?: string;
|
|
14
|
+
subject: string;
|
|
15
|
+
};
|
|
7
16
|
export declare type AssertToApply = {
|
|
8
17
|
name: string;
|
|
9
|
-
assertId?: string;
|
|
10
18
|
conditions: any;
|
|
11
|
-
message?: string;
|
|
12
|
-
severity?: ProblemSeverity;
|
|
13
|
-
suggest?: string[];
|
|
14
19
|
runsOnKeys: boolean;
|
|
15
20
|
runsOnValues: boolean;
|
|
16
21
|
};
|
|
17
22
|
export declare function buildVisitorObject(subject: string, context: Record<string, any>[], subjectVisitor: any): Record<string, any>;
|
|
18
|
-
export declare function buildSubjectVisitor(
|
|
23
|
+
export declare function buildSubjectVisitor(assertId: string, assertion: Assertion, asserts: AssertToApply[]): (node: any, { report, location, rawLocation, key, type, resolve, rawNode }: UserContext) => void;
|
|
19
24
|
export declare function getIntersectionLength(keys: string[], properties: string[]): number;
|
|
20
25
|
export declare function isOrdered(value: any[], options: OrderOptions | OrderDirection): boolean;
|
|
21
26
|
export declare function regexFromString(input: string): RegExp | null;
|
|
27
|
+
export {};
|