@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.
Files changed (40) hide show
  1. package/README.md +2 -2
  2. package/lib/config/config-resolvers.js +21 -3
  3. package/lib/config/config.d.ts +1 -0
  4. package/lib/config/config.js +1 -0
  5. package/lib/config/load.d.ts +8 -2
  6. package/lib/config/load.js +4 -2
  7. package/lib/config/types.d.ts +10 -0
  8. package/lib/config/utils.js +2 -2
  9. package/lib/rules/ajv.d.ts +1 -1
  10. package/lib/rules/ajv.js +5 -5
  11. package/lib/rules/common/assertions/asserts.d.ts +3 -5
  12. package/lib/rules/common/assertions/asserts.js +137 -97
  13. package/lib/rules/common/assertions/index.js +2 -6
  14. package/lib/rules/common/assertions/utils.d.ts +12 -6
  15. package/lib/rules/common/assertions/utils.js +33 -20
  16. package/lib/rules/utils.js +1 -1
  17. package/lib/types/redocly-yaml.js +16 -1
  18. package/package.json +3 -5
  19. package/src/__tests__/lint.test.ts +88 -0
  20. package/src/config/__tests__/config-resolvers.test.ts +37 -1
  21. package/src/config/__tests__/config.test.ts +5 -0
  22. package/src/config/__tests__/fixtures/resolve-config/local-config-with-custom-function.yaml +16 -0
  23. package/src/config/__tests__/fixtures/resolve-config/local-config-with-wrong-custom-function.yaml +16 -0
  24. package/src/config/__tests__/fixtures/resolve-config/plugin.js +11 -0
  25. package/src/config/__tests__/load.test.ts +1 -1
  26. package/src/config/__tests__/resolve-plugins.test.ts +3 -3
  27. package/src/config/config-resolvers.ts +28 -5
  28. package/src/config/config.ts +2 -0
  29. package/src/config/load.ts +10 -4
  30. package/src/config/types.ts +13 -0
  31. package/src/config/utils.ts +1 -0
  32. package/src/rules/ajv.ts +4 -4
  33. package/src/rules/common/assertions/__tests__/asserts.test.ts +491 -428
  34. package/src/rules/common/assertions/asserts.ts +155 -97
  35. package/src/rules/common/assertions/index.ts +2 -11
  36. package/src/rules/common/assertions/utils.ts +66 -36
  37. package/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts +51 -2
  38. package/src/rules/utils.ts +2 -1
  39. package/src/types/redocly-yaml.ts +16 -0
  40. 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 {
@@ -59,5 +59,6 @@ export declare class Config {
59
59
  'features.openapi': Record<string, any>;
60
60
  'features.mockServer'?: Record<string, any>;
61
61
  organization?: string;
62
+ files: string[];
62
63
  constructor(rawConfig: ResolvedConfig, configFile?: string | undefined);
63
64
  }
@@ -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;
@@ -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(configPath?: string | undefined, customExtends?: string[], processRawConfig?: (rawConfig: RawConfig) => void | Promise<void>): Promise<Config>;
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>;
@@ -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(configPath = findConfig(), customExtends, processRawConfig) {
62
+ function loadConfig(options = {}) {
63
63
  return __awaiter(this, void 0, void 0, function* () {
64
- const rawConfig = yield getConfig(configPath);
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
  }
@@ -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'> & {
@@ -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;
@@ -1,4 +1,4 @@
1
- import { ErrorObject } from '@redocly/ajv';
1
+ import { ErrorObject } from '@redocly/ajv/dist/2020';
2
2
  import { Location } from '../ref-utils';
3
3
  import { ResolveFn } from '../walk';
4
4
  export declare function releaseAjvInstance(): void;
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 ajv_1 = require("@redocly/ajv");
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 ajv_1.default({
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
- defaultAdditionalProperties: allowAdditionalProperties,
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 AssertResult = {
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 { isValid: true }; // property doesn't exist, no need to lint it with this assert
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
- for (const _val of values) {
39
- if (!(regx === null || regx === void 0 ? void 0 : regx.test(_val))) {
40
- return { isValid: false, location: utils_1.isString(value) ? baseLocation : baseLocation.key() };
41
- }
42
- }
43
- return { isValid: true };
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 { isValid: true }; // property doesn't exist, no need to lint it with this assert
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
- for (const _val of values) {
50
- if (!condition.includes(_val)) {
51
- return {
52
- isValid: false,
53
- location: utils_1.isString(value) ? baseLocation : baseLocation.child(_val).key(),
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
- return { isValid: condition ? isDefined : !isDefined, location: baseLocation };
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
- for (const requiredKey of keys) {
65
- if (!value.includes(requiredKey)) {
66
- return { isValid: false, location: baseLocation.key() };
67
- }
68
- }
69
- return { isValid: true };
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 { isValid: true }; // property doesn't exist, no need to lint it with this assert
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
- for (const _val of values) {
76
- if (condition.includes(_val)) {
77
- return {
78
- isValid: false,
79
- location: utils_1.isString(value) ? baseLocation : baseLocation.child(_val).key(),
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
- return { isValid: condition ? isUndefined : !isUndefined, location: baseLocation };
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
- return { isValid: condition ? !isEmpty : isEmpty, location: baseLocation };
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 { isValid: true }; // property doesn't exist, no need to lint it with this assert
96
- return { isValid: value.length >= condition, location: baseLocation };
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 { isValid: true }; // property doesn't exist, no need to lint it with this assert
101
- return { isValid: value.length <= condition, location: baseLocation };
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 { isValid: true }; // property doesn't exist, no need to lint it with this assert
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
- for (const _val of values) {
108
- let matchCase = false;
109
- switch (condition) {
110
- case 'camelCase':
111
- matchCase = !!_val.match(/^[a-z][a-zA-Z0-9]+$/g);
112
- break;
113
- case 'kebab-case':
114
- matchCase = !!_val.match(/^([a-z][a-z0-9]*)(-[a-z0-9]+)*$/g);
115
- break;
116
- case 'snake_case':
117
- matchCase = !!_val.match(/^([a-z][a-z0-9]*)(_[a-z0-9]+)*$/g);
118
- break;
119
- case 'PascalCase':
120
- matchCase = !!_val.match(/^[A-Z][a-zA-Z0-9]+$/g);
121
- break;
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 { isValid: true };
144
- return { isValid: utils_2.isOrdered(value, condition), location: baseLocation };
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
- return { isValid: utils_2.getIntersectionLength(value, condition) < 2, location: baseLocation.key() };
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
- return {
151
- isValid: utils_2.getIntersectionLength(value, condition) > 0
152
- ? utils_2.getIntersectionLength(value, condition) === condition.length
153
- : true,
154
- location: baseLocation.key(),
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 { isValid: utils_2.getIntersectionLength(value, condition) >= 1, location: baseLocation.key() };
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 { isValid: true }; // property doesn't exist, no need to lint it with this assert
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
- return {
166
- isValid: condition ? hasRef : !hasRef,
167
- location: hasRef ? baseLocation : baseLocation.key(),
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
- return {
172
- isValid: hasRef && (regex === null || regex === void 0 ? void 0 : regex.test(rawValue['$ref'])),
173
- location: hasRef ? baseLocation : baseLocation.key(),
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/master/packages/core/src/config/config.ts#L311
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.property, assertsToApply, assertion.context);
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 { ProblemSeverity, UserContext } from '../../../walk';
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(properties: string | string[], asserts: AssertToApply[], context?: Record<string, any>[]): (node: any, { report, location, rawLocation, key, type, resolve, rawNode }: UserContext) => void;
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 {};