@redocly/openapi-core 1.0.0-beta.90 → 1.0.0-beta.93

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 (41) hide show
  1. package/__tests__/__snapshots__/bundle.test.ts.snap +15 -13
  2. package/__tests__/ref-utils.test.ts +23 -1
  3. package/lib/config/all.js +1 -0
  4. package/lib/config/config.js +7 -12
  5. package/lib/config/minimal.js +1 -0
  6. package/lib/config/recommended.js +1 -0
  7. package/lib/config/rules.d.ts +1 -1
  8. package/lib/config/rules.js +10 -2
  9. package/lib/ref-utils.js +1 -1
  10. package/lib/rules/common/assertions/asserts.d.ts +5 -0
  11. package/lib/rules/common/assertions/asserts.js +143 -0
  12. package/lib/rules/common/assertions/index.d.ts +2 -0
  13. package/lib/rules/common/assertions/index.js +52 -0
  14. package/lib/rules/common/assertions/utils.d.ts +20 -0
  15. package/lib/rules/common/assertions/utils.js +123 -0
  16. package/lib/rules/oas2/index.d.ts +1 -0
  17. package/lib/rules/oas2/index.js +2 -0
  18. package/lib/rules/oas3/index.js +2 -0
  19. package/lib/visitors.d.ts +2 -2
  20. package/lib/walk.d.ts +1 -0
  21. package/lib/walk.js +1 -1
  22. package/package.json +1 -1
  23. package/src/bundle.ts +1 -1
  24. package/src/config/all.ts +1 -0
  25. package/src/config/config.ts +30 -24
  26. package/src/config/minimal.ts +1 -0
  27. package/src/config/recommended.ts +1 -0
  28. package/src/config/rules.ts +11 -2
  29. package/src/lint.ts +3 -3
  30. package/src/ref-utils.ts +1 -1
  31. package/src/rules/common/assertions/__tests__/asserts.test.ts +231 -0
  32. package/src/rules/common/assertions/__tests__/index.test.ts +65 -0
  33. package/src/rules/common/assertions/__tests__/utils.test.ts +89 -0
  34. package/src/rules/common/assertions/asserts.ts +137 -0
  35. package/src/rules/common/assertions/index.ts +75 -0
  36. package/src/rules/common/assertions/utils.ts +164 -0
  37. package/src/rules/oas2/index.ts +2 -0
  38. package/src/rules/oas3/index.ts +2 -0
  39. package/src/visitors.ts +2 -2
  40. package/src/walk.ts +2 -1
  41. 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',
@@ -417,7 +417,7 @@ function assignExisting(target, obj) {
417
417
  function getMergedConfig(config, entrypointAlias) {
418
418
  var _a, _b;
419
419
  return entrypointAlias
420
- ? new Config(Object.assign(Object.assign({}, config.rawConfig), { lint: getMergedLintConfig(config, entrypointAlias), 'features.openapi': Object.assign(Object.assign({}, config['features.openapi']), (_a = config.apis[entrypointAlias]) === null || _a === void 0 ? void 0 : _a['features.openapi']), 'features.mockServer': Object.assign(Object.assign({}, config['features.mockServer']), (_b = config.apis[entrypointAlias]) === null || _b === void 0 ? void 0 : _b['features.mockServer']) }))
420
+ ? new Config(Object.assign(Object.assign({}, config.rawConfig), { lint: getMergedLintConfig(config, entrypointAlias), 'features.openapi': Object.assign(Object.assign({}, config['features.openapi']), (_a = config.apis[entrypointAlias]) === null || _a === void 0 ? void 0 : _a['features.openapi']), 'features.mockServer': Object.assign(Object.assign({}, config['features.mockServer']), (_b = config.apis[entrypointAlias]) === null || _b === void 0 ? void 0 : _b['features.mockServer']) }), config.configFile)
421
421
  : config;
422
422
  }
423
423
  exports.getMergedConfig = getMergedConfig;
@@ -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
- // TODO: put links to the changelog and uncomment this after successful release of ReferenceDocs/Redoc, Portal and Workflows
448
- // if (apiDefinitions) {
449
- // process.stderr.write(
450
- // `The ${yellow('apiDefinitions')} field is deprecated. Use ${green('apis')} instead, see changelog: <link>\n`
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;
@@ -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',
@@ -17,6 +17,7 @@ exports.default = {
17
17
  'path-parameters-defined': 'error',
18
18
  'operation-description': 'off',
19
19
  'operation-2xx-response': 'warn',
20
+ 'assertions': 'warn',
20
21
  'operation-4xx-response': 'warn',
21
22
  'operation-operationId': 'warn',
22
23
  'operation-summary': 'error',
@@ -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
  }[];
@@ -14,13 +14,21 @@ function initRules(rules, config, type, oasVersion) {
14
14
  if (ruleSettings.severity === 'off') {
15
15
  return undefined;
16
16
  }
17
- const visitor = rule(ruleSettings);
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/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].split('.')[0];
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,5 @@
1
+ declare type Asserts = Record<string, (value: any, condition: any) => boolean>;
2
+ export declare const runOnKeysSet: Set<string>;
3
+ export declare const runOnValuesSet: Set<string>;
4
+ export declare const asserts: Asserts;
5
+ export {};
@@ -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,2 @@
1
+ import { Oas2Rule, Oas3Rule } from '../../../visitors';
2
+ export declare const Assertions: Oas3Rule | Oas2Rule;
@@ -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;
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isOrdered = exports.getIntersectionLength = exports.buildSubjectVisitor = exports.buildVisitorObject = void 0;
4
+ const asserts_1 = require("./asserts");
5
+ function buildVisitorObject(subject, context, subjectVisitor) {
6
+ if (!context) {
7
+ return { [subject]: subjectVisitor };
8
+ }
9
+ let currentVisitorLevel = {};
10
+ const visitor = currentVisitorLevel;
11
+ for (let index = 0; index < context.length; index++) {
12
+ const node = context[index];
13
+ if (context.length === index + 1 && node.type === subject) {
14
+ // Visitors don't work properly for the same type nested nodes, so
15
+ // as a workaround for that we don't create separate visitor for the last element
16
+ // which is the same as subject;
17
+ // we will check includes/excludes it in the last visitor.
18
+ continue;
19
+ }
20
+ const matchParentKeys = node.matchParentKeys;
21
+ const excludeParentKeys = node.excludeParentKeys;
22
+ if (matchParentKeys && excludeParentKeys) {
23
+ throw new Error(`Both 'matchParentKeys' and 'excludeParentKeys' can't be under one context item`);
24
+ }
25
+ if (matchParentKeys || excludeParentKeys) {
26
+ currentVisitorLevel[node.type] = {
27
+ skip: (_value, key) => {
28
+ if (matchParentKeys) {
29
+ return !matchParentKeys.includes(key);
30
+ }
31
+ if (excludeParentKeys) {
32
+ return excludeParentKeys.includes(key);
33
+ }
34
+ },
35
+ };
36
+ }
37
+ else {
38
+ currentVisitorLevel[node.type] = {};
39
+ }
40
+ currentVisitorLevel = currentVisitorLevel[node.type];
41
+ }
42
+ currentVisitorLevel[subject] = subjectVisitor;
43
+ return visitor;
44
+ }
45
+ exports.buildVisitorObject = buildVisitorObject;
46
+ function buildSubjectVisitor(properties, asserts, context) {
47
+ return function (node, { report, location, key, type }) {
48
+ // We need to check context's last node if it has the same type as subject node;
49
+ // if yes - that means we didn't create context's last node visitor,
50
+ // so we need to handle 'matchParentKeys' and 'excludeParentKeys' conditions here;
51
+ if (context) {
52
+ const lastContextNode = context[context.length - 1];
53
+ if (lastContextNode.type === type.name) {
54
+ const matchParentKeys = lastContextNode.matchParentKeys;
55
+ const excludeParentKeys = lastContextNode.excludeParentKeys;
56
+ if (matchParentKeys && !matchParentKeys.includes(key)) {
57
+ return;
58
+ }
59
+ if (excludeParentKeys && excludeParentKeys.includes(key)) {
60
+ return;
61
+ }
62
+ }
63
+ }
64
+ if (properties) {
65
+ properties = Array.isArray(properties) ? properties : [properties];
66
+ }
67
+ for (const assert of asserts) {
68
+ if (properties) {
69
+ for (const property of properties) {
70
+ runAssertion(node[property], assert, location.child(property), report);
71
+ }
72
+ }
73
+ else {
74
+ runAssertion(Object.keys(node), assert, location.key(), report);
75
+ }
76
+ }
77
+ };
78
+ }
79
+ exports.buildSubjectVisitor = buildSubjectVisitor;
80
+ function getIntersectionLength(keys, properties) {
81
+ const props = new Set(properties);
82
+ let count = 0;
83
+ for (const key of keys) {
84
+ if (props.has(key)) {
85
+ count++;
86
+ }
87
+ }
88
+ return count;
89
+ }
90
+ exports.getIntersectionLength = getIntersectionLength;
91
+ function isOrdered(value, options) {
92
+ const direction = options.direction || options;
93
+ const property = options.property;
94
+ for (let i = 1; i < value.length; i++) {
95
+ let currValue = value[i];
96
+ let prevVal = value[i - 1];
97
+ if (property) {
98
+ if (!value[i][property] || !value[i - 1][property]) {
99
+ return false; // property doesn't exist, so collection is not ordered
100
+ }
101
+ currValue = value[i][property];
102
+ prevVal = value[i - 1][property];
103
+ }
104
+ const result = direction === 'asc' ? currValue >= prevVal : currValue <= prevVal;
105
+ if (!result) {
106
+ return false;
107
+ }
108
+ }
109
+ return true;
110
+ }
111
+ exports.isOrdered = isOrdered;
112
+ function runAssertion(values, assert, location, report) {
113
+ const lintResult = asserts_1.asserts[assert.name](values, assert.conditions);
114
+ if (!lintResult) {
115
+ report({
116
+ message: assert.message || `The ${assert.assertId} doesn't meet required conditions`,
117
+ location,
118
+ forceSeverity: assert.severity,
119
+ suggest: assert.suggest,
120
+ ruleId: assert.assertId,
121
+ });
122
+ }
123
+ }
@@ -15,6 +15,7 @@ export declare const rules: {
15
15
  'no-path-trailing-slash': Oas2Rule;
16
16
  'operation-2xx-response': Oas2Rule;
17
17
  'operation-4xx-response': Oas2Rule;
18
+ assertions: Oas2Rule;
18
19
  'operation-operationId-unique': Oas2Rule;
19
20
  'operation-parameters-unique': Oas2Rule;
20
21
  'path-parameters-defined': Oas2Rule;
@@ -16,6 +16,7 @@ const no_enum_type_mismatch_1 = require("../common/no-enum-type-mismatch");
16
16
  const no_path_trailing_slash_1 = require("../common/no-path-trailing-slash");
17
17
  const operation_2xx_response_1 = require("../common/operation-2xx-response");
18
18
  const operation_4xx_response_1 = require("../common/operation-4xx-response");
19
+ const assertions_1 = require("../common/assertions");
19
20
  const operation_operationId_unique_1 = require("../common/operation-operationId-unique");
20
21
  const operation_parameters_unique_1 = require("../common/operation-parameters-unique");
21
22
  const path_params_defined_1 = require("../common/path-params-defined");
@@ -54,6 +55,7 @@ exports.rules = {
54
55
  'no-path-trailing-slash': no_path_trailing_slash_1.NoPathTrailingSlash,
55
56
  'operation-2xx-response': operation_2xx_response_1.Operation2xxResponse,
56
57
  'operation-4xx-response': operation_4xx_response_1.Operation4xxResponse,
58
+ 'assertions': assertions_1.Assertions,
57
59
  'operation-operationId-unique': operation_operationId_unique_1.OperationIdUnique,
58
60
  'operation-parameters-unique': operation_parameters_unique_1.OperationParametersUnique,
59
61
  'path-parameters-defined': path_params_defined_1.PathParamsDefined,
@@ -4,6 +4,7 @@ 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
6
  const operation_4xx_response_1 = require("../common/operation-4xx-response");
7
+ const assertions_1 = require("../common/assertions");
7
8
  const operation_operationId_unique_1 = require("../common/operation-operationId-unique");
8
9
  const operation_parameters_unique_1 = require("../common/operation-parameters-unique");
9
10
  const path_params_defined_1 = require("../common/path-params-defined");
@@ -54,6 +55,7 @@ exports.rules = {
54
55
  'info-license-url': license_url_1.InfoLicenseUrl,
55
56
  'operation-2xx-response': operation_2xx_response_1.Operation2xxResponse,
56
57
  'operation-4xx-response': operation_4xx_response_1.Operation4xxResponse,
58
+ 'assertions': assertions_1.Assertions,
57
59
  'operation-operationId-unique': operation_operationId_unique_1.OperationIdUnique,
58
60
  'operation-parameters-unique': operation_parameters_unique_1.OperationParametersUnique,
59
61
  'path-parameters-defined': path_params_defined_1.PathParamsDefined,
package/lib/visitors.d.ts CHANGED
@@ -156,8 +156,8 @@ export declare type NormalizedOasVisitors<T extends BaseVisitor> = {
156
156
  leave: Array<VisitorNode<any>>;
157
157
  };
158
158
  };
159
- export declare type Oas3Rule = (options: Record<string, any>) => Oas3Visitor;
160
- export declare type Oas2Rule = (options: Record<string, any>) => Oas2Visitor;
159
+ export declare type Oas3Rule = (options: Record<string, any>) => Oas3Visitor | Oas3Visitor[];
160
+ export declare type Oas2Rule = (options: Record<string, any>) => Oas2Visitor | Oas2Visitor[];
161
161
  export declare type Oas3Preprocessor = (options: Record<string, any>) => Oas3TransformVisitor;
162
162
  export declare type Oas2Preprocessor = (options: Record<string, any>) => Oas2TransformVisitor;
163
163
  export declare type Oas3Decorator = (options: Record<string, any>) => Oas3TransformVisitor;
package/lib/walk.d.ts CHANGED
@@ -60,6 +60,7 @@ export declare type Problem = {
60
60
  location?: Partial<LocationObject> | Array<Partial<LocationObject>>;
61
61
  from?: LocationObject;
62
62
  forceSeverity?: ProblemSeverity;
63
+ ruleId?: string;
63
64
  };
64
65
  export declare type NormalizedProblem = {
65
66
  message: string;
package/lib/walk.js CHANGED
@@ -240,7 +240,7 @@ function walkDocument(opts) {
240
240
  ? opts.location
241
241
  : [opts.location]
242
242
  : [Object.assign(Object.assign({}, currentLocation), { reportOnKey: false })];
243
- ctx.problems.push(Object.assign(Object.assign({ ruleId, severity: opts.forceSeverity || severity }, opts), { suggest: opts.suggest || [], location: loc.map((loc) => {
243
+ ctx.problems.push(Object.assign(Object.assign({ ruleId: opts.ruleId || ruleId, severity: opts.forceSeverity || severity }, opts), { suggest: opts.suggest || [], location: loc.map((loc) => {
244
244
  return Object.assign(Object.assign(Object.assign({}, currentLocation), { reportOnKey: false }), loc);
245
245
  }) }));
246
246
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/openapi-core",
3
- "version": "1.0.0-beta.90",
3
+ "version": "1.0.0-beta.93",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "engines": {
package/src/bundle.ts CHANGED
@@ -130,7 +130,7 @@ export async function bundleDocument(opts: {
130
130
  visitor: makeBundleVisitor(oasMajorVersion, dereference, skipRedoclyRegistryRefs, document, resolvedRefMap),
131
131
  },
132
132
  ...decorators,
133
- ],
133
+ ] as any,
134
134
  types,
135
135
  );
136
136
 
package/src/config/all.ts CHANGED
@@ -19,6 +19,7 @@ export 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',