@redocly/openapi-core 1.0.0-beta.101 → 1.0.0-beta.104

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 (56) hide show
  1. package/lib/config/config.d.ts +3 -3
  2. package/lib/config/load.d.ts +1 -1
  3. package/lib/config/load.js +15 -2
  4. package/lib/config/rules.d.ts +1 -1
  5. package/lib/config/types.d.ts +4 -2
  6. package/lib/decorators/common/filters/filter-helper.d.ts +3 -0
  7. package/lib/decorators/common/filters/filter-helper.js +67 -0
  8. package/lib/decorators/common/filters/filter-in.d.ts +2 -0
  9. package/lib/decorators/common/filters/filter-in.js +17 -0
  10. package/lib/decorators/common/filters/filter-out.d.ts +2 -0
  11. package/lib/decorators/common/filters/filter-out.js +17 -0
  12. package/lib/decorators/oas2/index.d.ts +2 -0
  13. package/lib/decorators/oas2/index.js +5 -1
  14. package/lib/decorators/oas3/index.d.ts +2 -0
  15. package/lib/decorators/oas3/index.js +5 -1
  16. package/lib/index.d.ts +1 -1
  17. package/lib/lint.d.ts +2 -0
  18. package/lib/lint.js +2 -2
  19. package/lib/redocly/registry-api-types.d.ts +2 -0
  20. package/lib/redocly/registry-api.d.ts +1 -1
  21. package/lib/redocly/registry-api.js +3 -1
  22. package/lib/rules/ajv.d.ts +1 -1
  23. package/lib/rules/ajv.js +1 -1
  24. package/lib/rules/common/assertions/asserts.d.ts +6 -1
  25. package/lib/rules/common/assertions/asserts.js +81 -51
  26. package/lib/rules/common/assertions/utils.d.ts +2 -1
  27. package/lib/rules/common/assertions/utils.js +27 -8
  28. package/lib/types/redocly-yaml.js +317 -27
  29. package/lib/utils.d.ts +2 -1
  30. package/lib/utils.js +5 -1
  31. package/lib/walk.d.ts +4 -14
  32. package/lib/walk.js +35 -26
  33. package/package.json +3 -2
  34. package/src/__tests__/lint.test.ts +17 -4
  35. package/src/config/__tests__/load.test.ts +6 -0
  36. package/src/config/load.ts +28 -5
  37. package/src/config/types.ts +6 -5
  38. package/src/decorators/__tests__/filter-in.test.ts +310 -0
  39. package/src/decorators/__tests__/filter-out.test.ts +331 -0
  40. package/src/decorators/common/filters/filter-helper.ts +72 -0
  41. package/src/decorators/common/filters/filter-in.ts +18 -0
  42. package/src/decorators/common/filters/filter-out.ts +18 -0
  43. package/src/decorators/oas2/index.ts +5 -1
  44. package/src/decorators/oas3/index.ts +5 -1
  45. package/src/index.ts +1 -0
  46. package/src/lint.ts +4 -3
  47. package/src/redocly/registry-api-types.ts +2 -0
  48. package/src/redocly/registry-api.ts +4 -0
  49. package/src/rules/ajv.ts +4 -4
  50. package/src/rules/common/assertions/__tests__/asserts.test.ts +149 -146
  51. package/src/rules/common/assertions/asserts.ts +97 -52
  52. package/src/rules/common/assertions/utils.ts +41 -16
  53. package/src/types/redocly-yaml.ts +322 -34
  54. package/src/utils.ts +10 -7
  55. package/src/walk.ts +59 -47
  56. package/tsconfig.tsbuildinfo +1 -1
package/lib/walk.js CHANGED
@@ -32,6 +32,26 @@ function walkDocument(opts) {
32
32
  walkNode(document.parsed, rootType, new ref_utils_1.Location(document.source, '#/'), undefined, '');
33
33
  function walkNode(node, type, location, parent, key) {
34
34
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
35
+ const resolve = (ref, from = currentLocation.source.absoluteRef) => {
36
+ if (!ref_utils_1.isRef(ref))
37
+ return { location, node: ref };
38
+ const refId = resolve_1.makeRefId(from, ref.$ref);
39
+ const resolvedRef = resolvedRefMap.get(refId);
40
+ if (!resolvedRef) {
41
+ return {
42
+ location: undefined,
43
+ node: undefined,
44
+ };
45
+ }
46
+ const { resolved, node, document, nodePointer, error } = resolvedRef;
47
+ const newLocation = resolved
48
+ ? new ref_utils_1.Location(document.source, nodePointer)
49
+ : error instanceof resolve_1.YamlParseError
50
+ ? new ref_utils_1.Location(error.source, '')
51
+ : undefined;
52
+ return { location: newLocation, node, error };
53
+ };
54
+ const rawLocation = location;
35
55
  let currentLocation = location;
36
56
  const { node: resolvedNode, location: resolvedLocation, error } = resolve(node);
37
57
  const enteredContexts = new Set();
@@ -44,13 +64,15 @@ function walkDocument(opts) {
44
64
  visitor(node, {
45
65
  report,
46
66
  resolve,
67
+ rawNode: node,
68
+ rawLocation,
47
69
  location,
48
70
  type,
49
71
  parent,
50
72
  key,
51
73
  parentLocations: {},
52
74
  oasVersion: ctx.oasVersion,
53
- getVisitorData: getVisitorDataFn.bind(undefined, ruleId)
75
+ getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
54
76
  }, { node: resolvedNode, location: resolvedLocation, error });
55
77
  if ((resolvedLocation === null || resolvedLocation === void 0 ? void 0 : resolvedLocation.source.absoluteRef) && ctx.refTypes) {
56
78
  ctx.refTypes.set(resolvedLocation === null || resolvedLocation === void 0 ? void 0 : resolvedLocation.source.absoluteRef, type);
@@ -101,7 +123,7 @@ function walkDocument(opts) {
101
123
  if (!activatedOn.skipped) {
102
124
  visitedBySome = true;
103
125
  enteredContexts.add(context);
104
- const { ignoreNextVisitorsOnNode } = visitWithContext(visit, resolvedNode, context, ruleId, severity);
126
+ const { ignoreNextVisitorsOnNode } = visitWithContext(visit, resolvedNode, node, context, ruleId, severity);
105
127
  if (ignoreNextVisitorsOnNode)
106
128
  break;
107
129
  }
@@ -173,7 +195,7 @@ function walkDocument(opts) {
173
195
  }
174
196
  for (const { context, visit, ruleId, severity } of currentLeaveVisitors) {
175
197
  if (!context.isSkippedLevel && enteredContexts.has(context)) {
176
- visitWithContext(visit, resolvedNode, context, ruleId, severity);
198
+ visitWithContext(visit, resolvedNode, node, context, ruleId, severity);
177
199
  }
178
200
  }
179
201
  }
@@ -186,54 +208,41 @@ function walkDocument(opts) {
186
208
  visitor(node, {
187
209
  report,
188
210
  resolve,
211
+ rawNode: node,
212
+ rawLocation,
189
213
  location,
190
214
  type,
191
215
  parent,
192
216
  key,
193
217
  parentLocations: {},
194
218
  oasVersion: ctx.oasVersion,
195
- getVisitorData: getVisitorDataFn.bind(undefined, ruleId)
219
+ getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
196
220
  }, { node: resolvedNode, location: resolvedLocation, error });
197
221
  }
198
222
  }
199
223
  }
200
224
  // returns true ignores all the next visitors on the specific node
201
- function visitWithContext(visit, node, context, ruleId, severity) {
225
+ function visitWithContext(visit, resolvedNode, node, context, ruleId, severity) {
202
226
  const report = reportFn.bind(undefined, ruleId, severity);
203
227
  let ignoreNextVisitorsOnNode = false;
204
- visit(node, {
228
+ visit(resolvedNode, {
205
229
  report,
206
230
  resolve,
231
+ rawNode: node,
207
232
  location: currentLocation,
233
+ rawLocation,
208
234
  type,
209
235
  parent,
210
236
  key,
211
237
  parentLocations: collectParentsLocations(context),
212
238
  oasVersion: ctx.oasVersion,
213
- ignoreNextVisitorsOnNode: () => { ignoreNextVisitorsOnNode = true; },
239
+ ignoreNextVisitorsOnNode: () => {
240
+ ignoreNextVisitorsOnNode = true;
241
+ },
214
242
  getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
215
243
  }, collectParents(context), context);
216
244
  return { ignoreNextVisitorsOnNode };
217
245
  }
218
- function resolve(ref, from = currentLocation.source.absoluteRef) {
219
- if (!ref_utils_1.isRef(ref))
220
- return { location, node: ref };
221
- const refId = resolve_1.makeRefId(from, ref.$ref);
222
- const resolvedRef = resolvedRefMap.get(refId);
223
- if (!resolvedRef) {
224
- return {
225
- location: undefined,
226
- node: undefined,
227
- };
228
- }
229
- const { resolved, node, document, nodePointer, error } = resolvedRef;
230
- const newLocation = resolved
231
- ? new ref_utils_1.Location(document.source, nodePointer)
232
- : error instanceof resolve_1.YamlParseError
233
- ? new ref_utils_1.Location(error.source, '')
234
- : undefined;
235
- return { location: newLocation, node, error };
236
- }
237
246
  function reportFn(ruleId, severity, opts) {
238
247
  const loc = opts.location
239
248
  ? Array.isArray(opts.location)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/openapi-core",
3
- "version": "1.0.0-beta.101",
3
+ "version": "1.0.0-beta.104",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "engines": {
@@ -16,7 +16,8 @@
16
16
  "fs": false,
17
17
  "path": "path-browserify",
18
18
  "os": false,
19
- "node-fetch": false
19
+ "node-fetch": false,
20
+ "colorette": false
20
21
  },
21
22
  "homepage": "https://github.com/Redocly/redocly-cli",
22
23
  "keywords": [
@@ -85,7 +85,7 @@ describe('lint', () => {
85
85
  },
86
86
  ],
87
87
  "message": "Expected type \`ConfigApis\` (object) but got \`string\`",
88
- "ruleId": "spec",
88
+ "ruleId": "configuration spec",
89
89
  "severity": "error",
90
90
  "suggest": Array [],
91
91
  },
@@ -97,8 +97,8 @@ describe('lint', () => {
97
97
  "source": "",
98
98
  },
99
99
  ],
100
- "message": "Expected type \`string\` but got \`object\`.",
101
- "ruleId": "spec",
100
+ "message": "\`layout\` can be one of the following only: \\"stacked\\", \\"three-panel\\".",
101
+ "ruleId": "configuration spec",
102
102
  "severity": "error",
103
103
  "suggest": Array [],
104
104
  },
@@ -123,6 +123,19 @@ describe('lint', () => {
123
123
 
124
124
  expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
125
125
  Array [
126
+ Object {
127
+ "location": Array [
128
+ Object {
129
+ "pointer": "#/apis/lint",
130
+ "reportOnKey": true,
131
+ "source": "",
132
+ },
133
+ ],
134
+ "message": "The field \`root\` must be present on this level.",
135
+ "ruleId": "configuration spec",
136
+ "severity": "error",
137
+ "suggest": Array [],
138
+ },
126
139
  Object {
127
140
  "location": Array [
128
141
  Object {
@@ -132,7 +145,7 @@ describe('lint', () => {
132
145
  },
133
146
  ],
134
147
  "message": "Property \`plugins\` is not expected here.",
135
- "ruleId": "spec",
148
+ "ruleId": "configuration spec",
136
149
  "severity": "error",
137
150
  "suggest": Array [],
138
151
  },
@@ -44,6 +44,12 @@ describe('loadConfig', () => {
44
44
  },
45
45
  ]);
46
46
  });
47
+
48
+ it('should call callback if such passed', async () => {
49
+ const mockFn = jest.fn();
50
+ await loadConfig(undefined, undefined, mockFn);
51
+ expect(mockFn).toHaveBeenCalled();
52
+ });
47
53
  });
48
54
 
49
55
  describe('findConfig', () => {
@@ -8,11 +8,16 @@ import { resolveConfig } from './config-resolvers';
8
8
 
9
9
  import type { RawConfig, Region } from './types';
10
10
 
11
- export async function loadConfig(
12
- configPath: string | undefined = findConfig(),
13
- customExtends?: string[],
14
- ): Promise<Config> {
15
- const rawConfig = await getConfig(configPath);
11
+ async function addConfigMetadata({
12
+ rawConfig,
13
+ customExtends,
14
+ configPath
15
+ }: {
16
+ rawConfig: RawConfig;
17
+ customExtends?: string[];
18
+ configPath?: string;
19
+
20
+ }): Promise<Config> {
16
21
  if (customExtends !== undefined) {
17
22
  rawConfig.lint = rawConfig.lint || {};
18
23
  rawConfig.lint.extends = customExtends;
@@ -56,6 +61,24 @@ export async function loadConfig(
56
61
  return resolveConfig(rawConfig, configPath);
57
62
  }
58
63
 
64
+ export async function loadConfig(
65
+ configPath: string | undefined = findConfig(),
66
+ customExtends?: string[],
67
+ processRawConfig?: (rawConfig: RawConfig) => void | Promise<void>,
68
+ ): Promise<Config> {
69
+ const rawConfig = await getConfig(configPath);
70
+
71
+ if (typeof processRawConfig === 'function') {
72
+ await processRawConfig(rawConfig);
73
+ }
74
+
75
+ return await addConfigMetadata({
76
+ rawConfig,
77
+ customExtends,
78
+ configPath,
79
+ });
80
+ };
81
+
59
82
  export const CONFIG_FILE_NAMES = ['redocly.yaml', 'redocly.yml', '.redocly.yaml', '.redocly.yml'];
60
83
 
61
84
  export function findConfig(dir?: string): string | undefined {
@@ -11,17 +11,18 @@ import type {
11
11
  } from '../oas-types';
12
12
  import type { NodeType } from '../types';
13
13
 
14
+ export type RuleSeverity = ProblemSeverity | 'off';
15
+
16
+ export type PreprocessorSeverity = RuleSeverity | 'on';
17
+
14
18
  export type RuleConfig =
15
- | ProblemSeverity
16
- | 'off'
19
+ | RuleSeverity
17
20
  | ({
18
21
  severity?: ProblemSeverity;
19
22
  } & Record<string, any>);
20
23
 
21
24
  export type PreprocessorConfig =
22
- | ProblemSeverity
23
- | 'off'
24
- | 'on'
25
+ | PreprocessorSeverity
25
26
  | ({
26
27
  severity?: ProblemSeverity;
27
28
  } & Record<string, any>);
@@ -0,0 +1,310 @@
1
+ import { outdent } from 'outdent';
2
+ import { bundleDocument } from '../../bundle';
3
+ import { BaseResolver } from '../../resolve';
4
+ import { makeConfig, parseYamlToDocument, yamlSerializer } from '../../../__tests__/utils';
5
+
6
+ describe('oas3 filter-in', () => {
7
+ expect.addSnapshotSerializer(yamlSerializer);
8
+
9
+ const inputDoc = parseYamlToDocument(
10
+ outdent`
11
+ openapi: 3.0.0
12
+ paths:
13
+ /pet:
14
+ x-audience: Global
15
+ post:
16
+ summary: test
17
+ /user:
18
+ x-audience: [Public, Global]
19
+ post:
20
+ summary: test
21
+ /post:
22
+ get:
23
+ summary: test
24
+ /order:
25
+ x-audience: [Public, Protected]
26
+ post:
27
+ operationId: storeOrder
28
+ callbacks:
29
+ x-access: protected`
30
+ );
31
+
32
+ it('should include /user path and remove y parameter', async () => {
33
+ const testDocument = parseYamlToDocument(
34
+ outdent`
35
+ openapi: 3.0.0
36
+ paths:
37
+ /pet:
38
+ x-access: private
39
+ get:
40
+ parameters:
41
+ - $ref: '#/components/parameters/x'
42
+ /user:
43
+ x-access: public
44
+ get:
45
+ parameters:
46
+ - $ref: '#/components/parameters/y'
47
+ components:
48
+ parameters:
49
+ x:
50
+ name: x
51
+ y:
52
+ x-access: private
53
+ name: y
54
+ `
55
+ );
56
+ const { bundle: res } = await bundleDocument({
57
+ document: testDocument,
58
+ externalRefResolver: new BaseResolver(),
59
+ config: await makeConfig({}, { 'filter-in': { value: 'public', property: 'x-access' } }),
60
+ });
61
+ expect(res.parsed).toMatchInlineSnapshot(`
62
+ openapi: 3.0.0
63
+ paths:
64
+ /user:
65
+ x-access: public
66
+ get: {}
67
+ components:
68
+ parameters:
69
+ x:
70
+ name: x
71
+
72
+ `);
73
+ });
74
+
75
+ it('should include /order and /post paths', async () => {
76
+ const { bundle: res } = await bundleDocument({
77
+ document: inputDoc,
78
+ externalRefResolver: new BaseResolver(),
79
+ config: await makeConfig(
80
+ {},
81
+ {
82
+ 'filter-in': {
83
+ property: 'x-audience',
84
+ value: ['Public', 'Protected'],
85
+ matchStrategy: 'all',
86
+ },
87
+ }
88
+ ),
89
+ });
90
+ expect(res.parsed).toMatchInlineSnapshot(`
91
+ openapi: 3.0.0
92
+ paths:
93
+ /post:
94
+ get:
95
+ summary: test
96
+ /order:
97
+ x-audience:
98
+ - Public
99
+ - Protected
100
+ post:
101
+ operationId: storeOrder
102
+ callbacks:
103
+ x-access: protected
104
+ components: {}
105
+
106
+ `);
107
+ });
108
+
109
+ it('should include all paths', async () => {
110
+ const testDoc = parseYamlToDocument(
111
+ outdent`
112
+ openapi: 3.0.0
113
+ paths:
114
+ /pet:
115
+ x-audience: Global
116
+ post:
117
+ summary: test
118
+ /user:
119
+ x-audience: [Public, Global]
120
+ post:
121
+ summary: test
122
+ /order:
123
+ x-audience: [Public, Protected]
124
+ post:
125
+ operationId: storeOrder
126
+ parameters:
127
+ - name: api_key
128
+ `
129
+ );
130
+ const { bundle: res } = await bundleDocument({
131
+ document: testDoc,
132
+ externalRefResolver: new BaseResolver(),
133
+ config: await makeConfig(
134
+ {},
135
+ {
136
+ 'filter-in': {
137
+ property: 'x-audience',
138
+ value: ['Public', 'Global'],
139
+ matchStrategy: 'any',
140
+ },
141
+ }
142
+ ),
143
+ });
144
+ expect(res.parsed).toMatchInlineSnapshot(`
145
+ openapi: 3.0.0
146
+ paths:
147
+ /pet:
148
+ x-audience: Global
149
+ post:
150
+ summary: test
151
+ /user:
152
+ x-audience:
153
+ - Public
154
+ - Global
155
+ post:
156
+ summary: test
157
+ /order:
158
+ x-audience:
159
+ - Public
160
+ - Protected
161
+ post:
162
+ operationId: storeOrder
163
+ parameters:
164
+ - name: api_key
165
+ components: {}
166
+
167
+ `);
168
+ });
169
+
170
+ it('should include path without x-audience property ', async () => {
171
+ const { bundle: res } = await bundleDocument({
172
+ document: inputDoc,
173
+ externalRefResolver: new BaseResolver(),
174
+ config: await makeConfig(
175
+ {},
176
+ {
177
+ 'filter-in': {
178
+ property: 'x-audience',
179
+ value: 'non-existing-audience',
180
+ matchStrategy: 'any',
181
+ },
182
+ }
183
+ ),
184
+ });
185
+ expect(res.parsed).toMatchInlineSnapshot(`
186
+ openapi: 3.0.0
187
+ paths:
188
+ /post:
189
+ get:
190
+ summary: test
191
+ components: {}
192
+
193
+ `);
194
+ });
195
+
196
+ it('should include /pet and /account without post method', async () => {
197
+ const testDoc = parseYamlToDocument(
198
+ outdent`
199
+ openapi: 3.0.0
200
+ paths:
201
+ /pet:
202
+ x-audience: Global
203
+ post:
204
+ summary: test
205
+ /user:
206
+ x-audience: Private
207
+ post:
208
+ summary: test
209
+ get:
210
+ summary: get
211
+ x-audience: [Public, Global]
212
+ /account:
213
+ get:
214
+ summary: get
215
+ post:
216
+ summary: test
217
+ x-audience: Private
218
+ `
219
+ );
220
+ const { bundle: res } = await bundleDocument({
221
+ document: testDoc,
222
+ externalRefResolver: new BaseResolver(),
223
+ config: await makeConfig(
224
+ {},
225
+ {
226
+ 'filter-in': {
227
+ property: 'x-audience',
228
+ value: ['Public', 'Global'],
229
+ matchStrategy: 'any',
230
+ },
231
+ }
232
+ ),
233
+ });
234
+ expect(res.parsed).toMatchInlineSnapshot(`
235
+ openapi: 3.0.0
236
+ paths:
237
+ /pet:
238
+ x-audience: Global
239
+ post:
240
+ summary: test
241
+ /account:
242
+ get:
243
+ summary: get
244
+ components: {}
245
+
246
+ `);
247
+ });
248
+ });
249
+
250
+ describe('oas2 filter-in', () => {
251
+ it('should include only one parameter and not include response', async () => {
252
+ const testDoc = parseYamlToDocument(
253
+ outdent`
254
+ swagger: '2.0'
255
+ host: api.instagram.com
256
+ paths:
257
+ '/geographies/{geo-id}/media/recent':
258
+ get:
259
+ parameters:
260
+ - description: The geography ID.
261
+ x-access: private
262
+ in: path
263
+ name: geo-id
264
+ required: true
265
+ type: string
266
+ - description: Max number of media to return.
267
+ x-access: public
268
+ format: int32
269
+ in: query
270
+ name: count
271
+ required: false
272
+ type: integer
273
+ responses:
274
+ '200':
275
+ description: List of recent media entries.
276
+ x-access: [private, protected]
277
+ `
278
+ );
279
+ const { bundle: res } = await bundleDocument({
280
+ document: testDoc,
281
+ externalRefResolver: new BaseResolver(),
282
+ config: await makeConfig(
283
+ {},
284
+ {
285
+ 'filter-in': {
286
+ property: 'x-access',
287
+ value: ['public', 'global'],
288
+ matchStrategy: 'any',
289
+ },
290
+ }
291
+ ),
292
+ });
293
+ expect(res.parsed).toMatchInlineSnapshot(`
294
+ swagger: '2.0'
295
+ host: api.instagram.com
296
+ paths:
297
+ /geographies/{geo-id}/media/recent:
298
+ get:
299
+ parameters:
300
+ - description: Max number of media to return.
301
+ x-access: public
302
+ format: int32
303
+ in: query
304
+ name: count
305
+ required: false
306
+ type: integer
307
+
308
+ `);
309
+ });
310
+ });