@redocly/openapi-core 1.0.0-beta.111 → 1.0.0-beta.112

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 (50) hide show
  1. package/lib/config/all.js +0 -1
  2. package/lib/config/config-resolvers.js +19 -12
  3. package/lib/config/load.d.ts +1 -1
  4. package/lib/config/load.js +5 -5
  5. package/lib/config/minimal.js +0 -1
  6. package/lib/config/recommended.js +0 -1
  7. package/lib/rules/common/assertions/asserts.d.ts +22 -5
  8. package/lib/rules/common/assertions/asserts.js +25 -0
  9. package/lib/rules/common/assertions/index.d.ts +27 -2
  10. package/lib/rules/common/assertions/index.js +6 -29
  11. package/lib/rules/common/assertions/utils.d.ts +7 -14
  12. package/lib/rules/common/assertions/utils.js +129 -97
  13. package/lib/rules/oas2/index.d.ts +0 -1
  14. package/lib/rules/oas2/index.js +0 -2
  15. package/lib/rules/oas3/index.js +0 -2
  16. package/lib/types/redocly-yaml.js +44 -27
  17. package/lib/utils.d.ts +1 -0
  18. package/lib/utils.js +7 -1
  19. package/lib/visitors.d.ts +2 -1
  20. package/lib/visitors.js +1 -0
  21. package/lib/walk.js +7 -1
  22. package/package.json +1 -1
  23. package/src/__tests__/lint.test.ts +24 -5
  24. package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +1 -3
  25. package/src/config/__tests__/config-resolvers.test.ts +5 -5
  26. package/src/config/__tests__/fixtures/load-redocly.yaml +4 -0
  27. package/src/config/__tests__/fixtures/resolve-config/local-config-with-custom-function.yaml +6 -4
  28. package/src/config/__tests__/load.test.ts +4 -1
  29. package/src/config/all.ts +0 -1
  30. package/src/config/config-resolvers.ts +42 -19
  31. package/src/config/load.ts +8 -5
  32. package/src/config/minimal.ts +0 -1
  33. package/src/config/recommended.ts +0 -1
  34. package/src/rules/common/assertions/__tests__/asserts.test.ts +7 -3
  35. package/src/rules/common/assertions/__tests__/index.test.ts +41 -20
  36. package/src/rules/common/assertions/__tests__/utils.test.ts +43 -17
  37. package/src/rules/common/assertions/asserts.ts +60 -8
  38. package/src/rules/common/assertions/index.ts +36 -46
  39. package/src/rules/common/assertions/utils.ts +204 -127
  40. package/src/rules/oas2/index.ts +0 -2
  41. package/src/rules/oas3/index.ts +0 -2
  42. package/src/types/redocly-yaml.ts +44 -29
  43. package/src/utils.ts +5 -0
  44. package/src/visitors.ts +7 -1
  45. package/src/walk.ts +8 -1
  46. package/tsconfig.tsbuildinfo +1 -1
  47. package/lib/rules/common/info-description.d.ts +0 -2
  48. package/lib/rules/common/info-description.js +0 -12
  49. package/src/rules/common/__tests__/info-description.test.ts +0 -102
  50. package/src/rules/common/info-description.ts +0 -10
@@ -1,3 +1,4 @@
1
+ import { Assertion, AssertionDefinition } from '..';
1
2
  import { isOrdered, buildVisitorObject, getIntersectionLength } from '../utils';
2
3
 
3
4
  describe('Oas3 assertions', () => {
@@ -25,29 +26,43 @@ describe('Oas3 assertions', () => {
25
26
 
26
27
  describe('buildVisitorObject', () => {
27
28
  it('should return a consistent visitor structure', () => {
28
- const context = [
29
+ const where: AssertionDefinition[] = [
29
30
  {
30
- type: 'Foo',
31
- matchParentKeys: ['test'],
31
+ subject: {
32
+ type: 'Foo',
33
+ filterInParentKeys: ['test'],
34
+ },
35
+ assertions: {},
32
36
  },
33
37
  {
34
- type: 'Bar',
35
- matchParentKeys: ['test'],
38
+ subject: {
39
+ type: 'Bar',
40
+ filterInParentKeys: ['test'],
41
+ },
42
+ assertions: {},
36
43
  },
37
44
  {
38
- type: 'Roof',
39
- matchParentKeys: ['test'],
45
+ subject: {
46
+ type: 'Roof',
47
+ filterInParentKeys: ['test'],
48
+ },
49
+ assertions: {},
40
50
  },
41
- ];
51
+ ] as AssertionDefinition[];
42
52
 
43
- const visitors = buildVisitorObject('Bar', context, () => {}) as any;
53
+ const visitors = buildVisitorObject(
54
+ { subject: { type: 'Bar' }, where, assertions: {} } as Assertion,
55
+ () => {}
56
+ );
44
57
 
45
58
  expect(visitors).toMatchInlineSnapshot(`
46
59
  Object {
47
60
  "Foo": Object {
48
61
  "Bar": Object {
49
62
  "Roof": Object {
50
- "Bar": [Function],
63
+ "Bar": Object {
64
+ "enter": [Function],
65
+ },
51
66
  "skip": [Function],
52
67
  },
53
68
  "skip": [Function],
@@ -59,24 +74,35 @@ describe('Oas3 assertions', () => {
59
74
  });
60
75
 
61
76
  it('should return the right visitor structure', () => {
62
- const context = [
77
+ const where = [
63
78
  {
64
- type: 'Operation',
65
- matchParentKeys: ['put'],
79
+ subject: {
80
+ type: 'Operation',
81
+ filterInParentKeys: ['put'],
82
+ },
83
+ assertions: {},
66
84
  },
67
85
  {
68
- type: 'Responses',
69
- matchParentKeys: [201, 200],
86
+ subject: {
87
+ type: 'Responses',
88
+ filterInParentKeys: [201, 200],
89
+ },
90
+ assertions: {},
70
91
  },
71
92
  ];
72
93
 
73
- const visitors = buildVisitorObject('MediaTypesMap', context, () => {}) as any;
94
+ const visitors = buildVisitorObject(
95
+ { subject: { type: 'MediaTypesMap' }, where, assertions: {} } as Assertion,
96
+ () => {}
97
+ );
74
98
 
75
99
  expect(visitors).toMatchInlineSnapshot(`
76
100
  Object {
77
101
  "Operation": Object {
78
102
  "Responses": Object {
79
- "MediaTypesMap": [Function],
103
+ "MediaTypesMap": Object {
104
+ "enter": [Function],
105
+ },
80
106
  "skip": [Function],
81
107
  },
82
108
  "skip": [Function],
@@ -9,12 +9,33 @@ import {
9
9
  regexFromString,
10
10
  } from './utils';
11
11
 
12
- type Asserts = Record<
13
- string,
14
- (value: any, condition: any, baseLocation: Location, rawValue?: any) => AssertResult[]
15
- >;
12
+ export type AssertionFn = (
13
+ value: any,
14
+ condition: any,
15
+ baseLocation: Location,
16
+ rawValue?: any
17
+ ) => AssertResult[];
16
18
 
17
- export const runOnKeysSet = new Set([
19
+ export type Asserts = {
20
+ pattern: AssertionFn;
21
+ enum: AssertionFn;
22
+ defined: AssertionFn;
23
+ required: AssertionFn;
24
+ disallowed: AssertionFn;
25
+ undefined: AssertionFn;
26
+ nonEmpty: AssertionFn;
27
+ minLength: AssertionFn;
28
+ maxLength: AssertionFn;
29
+ casing: AssertionFn;
30
+ sortOrder: AssertionFn;
31
+ mutuallyExclusive: AssertionFn;
32
+ mutuallyRequired: AssertionFn;
33
+ requireAny: AssertionFn;
34
+ ref: AssertionFn;
35
+ const: AssertionFn;
36
+ };
37
+
38
+ export const runOnKeysSet = new Set<keyof Asserts>([
18
39
  'mutuallyExclusive',
19
40
  'mutuallyRequired',
20
41
  'enum',
@@ -27,8 +48,10 @@ export const runOnKeysSet = new Set([
27
48
  'required',
28
49
  'requireAny',
29
50
  'ref',
51
+ 'const',
52
+ 'defined', // In case if `property` for assertions is not added
30
53
  ]);
31
- export const runOnValuesSet = new Set([
54
+ export const runOnValuesSet = new Set<keyof Asserts>([
32
55
  'pattern',
33
56
  'enum',
34
57
  'defined',
@@ -39,6 +62,7 @@ export const runOnValuesSet = new Set([
39
62
  'casing',
40
63
  'sortOrder',
41
64
  'ref',
65
+ 'const',
42
66
  ]);
43
67
 
44
68
  export const asserts: Asserts = {
@@ -106,6 +130,34 @@ export const asserts: Asserts = {
106
130
  )
107
131
  .filter(isTruthy);
108
132
  },
133
+ const: (
134
+ value: string | number | boolean | string[] | number[],
135
+ condition: string | number | boolean,
136
+ baseLocation: Location
137
+ ) => {
138
+ if (typeof value === 'undefined') return [];
139
+
140
+ if (Array.isArray(value)) {
141
+ return value
142
+ .map(
143
+ (_val) =>
144
+ condition !== _val && {
145
+ message: `"${_val}" should be equal ${condition} `,
146
+ location: runOnValue(value) ? baseLocation : baseLocation.child(_val).key(),
147
+ }
148
+ )
149
+ .filter(isTruthy);
150
+ } else {
151
+ return value !== condition
152
+ ? [
153
+ {
154
+ message: `${value} should be equal ${condition}`,
155
+ location: baseLocation,
156
+ },
157
+ ]
158
+ : [];
159
+ }
160
+ },
109
161
  undefined: (value: any, condition: boolean = true, baseLocation: Location) => {
110
162
  const isUndefined = typeof value === 'undefined';
111
163
  const isValid = condition ? isUndefined : !isUndefined;
@@ -210,7 +262,7 @@ export const asserts: Asserts = {
210
262
  },
211
263
  ];
212
264
  },
213
- ref: (_value: any, condition: string | boolean, baseLocation, rawValue: any) => {
265
+ ref: (_value: any, condition: string | boolean, baseLocation: Location, rawValue: any) => {
214
266
  if (typeof rawValue === 'undefined') return []; // property doesn't exist, no need to lint it with this assert
215
267
  const hasRef = rawValue.hasOwnProperty('$ref');
216
268
  if (typeof condition === 'boolean') {
@@ -237,7 +289,7 @@ export const asserts: Asserts = {
237
289
  },
238
290
  };
239
291
 
240
- export function buildAssertCustomFunction(fn: CustomFunction) {
292
+ export function buildAssertCustomFunction(fn: CustomFunction): AssertionFn {
241
293
  return (value: string[], options: any, baseLocation: Location) =>
242
294
  fn.call(null, value, options, baseLocation);
243
295
  }
@@ -1,65 +1,55 @@
1
- import { asserts, runOnKeysSet, runOnValuesSet } from './asserts';
2
- import { AssertToApply, buildSubjectVisitor, buildVisitorObject } from './utils';
3
- import { Oas2Rule, Oas3Rule } from '../../../visitors';
1
+ import { asserts, AssertionFn } from './asserts';
2
+ import { buildSubjectVisitor, buildVisitorObject } from './utils';
3
+ import { Oas2Visitor, Oas3Visitor } from '../../../visitors';
4
+ import { RuleSeverity } from '../../../config';
5
+ import { isString } from '../../../utils';
6
+
7
+ export type AssertionLocators = {
8
+ filterInParentKeys?: (string | number)[];
9
+ filterOutParentKeys?: (string | number)[];
10
+ matchParentKeys?: string;
11
+ };
12
+
13
+ export type AssertionDefinition = {
14
+ subject: {
15
+ type: string;
16
+ property?: string | string[];
17
+ } & AssertionLocators;
18
+ assertions: { [name in keyof typeof asserts]?: AssertionFn };
19
+ };
4
20
 
5
- export const Assertions: Oas3Rule | Oas2Rule = (opts: object) => {
6
- const visitors: any[] = [];
21
+ export type RawAssertion = AssertionDefinition & {
22
+ where?: AssertionDefinition[];
23
+ message?: string;
24
+ suggest?: string[];
25
+ severity?: RuleSeverity;
26
+ };
27
+
28
+ export type Assertion = RawAssertion & { assertionId: string };
29
+
30
+ export const Assertions = (opts: Record<string, Assertion>) => {
31
+ const visitors: (Oas2Visitor | Oas3Visitor)[] = [];
7
32
 
8
33
  // As 'Assertions' has an array of asserts,
9
34
  // that array spreads into an 'opts' object on init rules phase here
10
35
  // https://github.com/Redocly/redocly-cli/blob/main/packages/core/src/config/config.ts#L311
11
36
  // that is why we need to iterate through 'opts' values;
12
37
  // before - filter only object 'opts' values
13
- const assertions: any[] = Object.values(opts).filter(
38
+ const assertions: Assertion[] = Object.values(opts).filter(
14
39
  (opt: unknown) => typeof opt === 'object' && opt !== null
15
40
  );
16
41
 
17
42
  for (const [index, assertion] of assertions.entries()) {
18
43
  const assertId =
19
44
  (assertion.assertionId && `${assertion.assertionId} assertion`) || `assertion #${index + 1}`;
20
- if (!assertion.subject) {
21
- throw new Error(`${assertId}: 'subject' is required`);
22
- }
23
45
 
24
- const subjects: string[] = Array.isArray(assertion.subject)
25
- ? assertion.subject
26
- : [assertion.subject];
27
-
28
- const assertsToApply: AssertToApply[] = Object.keys(asserts)
29
- .filter((assertName: string) => assertion[assertName] !== undefined)
30
- .map((assertName: string) => {
31
- return {
32
- name: assertName,
33
- conditions: assertion[assertName],
34
- runsOnKeys: runOnKeysSet.has(assertName),
35
- runsOnValues: runOnValuesSet.has(assertName),
36
- };
37
- });
38
-
39
- const shouldRunOnKeys: AssertToApply | undefined = assertsToApply.find(
40
- (assert: AssertToApply) => assert.runsOnKeys && !assert.runsOnValues
41
- );
42
- const shouldRunOnValues: AssertToApply | undefined = assertsToApply.find(
43
- (assert: AssertToApply) => assert.runsOnValues && !assert.runsOnKeys
44
- );
45
-
46
- if (shouldRunOnValues && !assertion.property) {
47
- throw new Error(
48
- `${shouldRunOnValues.name} can't be used on all keys. Please provide a single property.`
49
- );
46
+ if (!isString(assertion.subject.type)) {
47
+ throw new Error(`${assertId}: 'type' (String) is required`);
50
48
  }
51
49
 
52
- if (shouldRunOnKeys && assertion.property) {
53
- throw new Error(
54
- `${shouldRunOnKeys.name} can't be used on a single property. Please use 'property'.`
55
- );
56
- }
57
-
58
- for (const subject of subjects) {
59
- const subjectVisitor = buildSubjectVisitor(assertId, assertion, assertsToApply);
60
- const visitorObject = buildVisitorObject(subject, assertion.context, subjectVisitor);
61
- visitors.push(visitorObject);
62
- }
50
+ const subjectVisitor = buildSubjectVisitor(assertId, assertion);
51
+ const visitorObject = buildVisitorObject(assertion, subjectVisitor);
52
+ visitors.push(visitorObject);
63
53
  }
64
54
 
65
55
  return visitors;