@redocly/openapi-core 1.4.0 → 1.5.0

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 (42) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +5 -5
  3. package/lib/bundle.js +2 -2
  4. package/lib/config/all.js +2 -0
  5. package/lib/config/minimal.js +2 -0
  6. package/lib/config/recommended-strict.js +2 -0
  7. package/lib/config/recommended.js +2 -0
  8. package/lib/decorators/oas2/remove-unused-components.d.ts +2 -0
  9. package/lib/decorators/oas3/remove-unused-components.d.ts +2 -0
  10. package/lib/env.js +1 -1
  11. package/lib/rules/oas3/array-parameter-serialization.d.ts +5 -0
  12. package/lib/rules/oas3/array-parameter-serialization.js +31 -0
  13. package/lib/rules/oas3/index.js +2 -0
  14. package/lib/types/oas3_1.js +12 -0
  15. package/lib/types/redocly-yaml.d.ts +1 -1
  16. package/lib/types/redocly-yaml.js +1 -0
  17. package/lib/typings/openapi.d.ts +1 -0
  18. package/lib/visitors.d.ts +7 -9
  19. package/package.json +1 -1
  20. package/src/bundle.ts +4 -4
  21. package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +4 -0
  22. package/src/config/all.ts +2 -0
  23. package/src/config/minimal.ts +2 -0
  24. package/src/config/recommended-strict.ts +2 -0
  25. package/src/config/recommended.ts +2 -0
  26. package/src/{rules → decorators}/oas2/remove-unused-components.ts +3 -3
  27. package/src/{rules → decorators}/oas3/remove-unused-components.ts +3 -3
  28. package/src/env.ts +1 -1
  29. package/src/rules/oas3/__tests__/array-parameter-serialization.test.ts +263 -0
  30. package/src/rules/oas3/array-parameter-serialization.ts +43 -0
  31. package/src/rules/oas3/index.ts +2 -0
  32. package/src/types/oas3_1.ts +12 -0
  33. package/src/types/redocly-yaml.ts +1 -0
  34. package/src/typings/openapi.ts +1 -0
  35. package/src/visitors.ts +7 -18
  36. package/tsconfig.tsbuildinfo +1 -1
  37. package/lib/rules/oas2/remove-unused-components.d.ts +0 -2
  38. package/lib/rules/oas3/remove-unused-components.d.ts +0 -2
  39. /package/lib/{rules → decorators}/oas2/remove-unused-components.js +0 -0
  40. /package/lib/{rules → decorators}/oas3/remove-unused-components.js +0 -0
  41. /package/src/{rules → decorators}/oas2/__tests__/remove-unused-components.test.ts +0 -0
  42. /package/src/{rules → decorators}/oas3/__tests__/remove-unused-components.test.ts +0 -0
@@ -0,0 +1,263 @@
1
+ import { outdent } from 'outdent';
2
+ import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
3
+ import { lintDocument } from '../../../lint';
4
+ import { BaseResolver } from '../../../resolve';
5
+
6
+ describe('oas3 array-parameter-serialization', () => {
7
+ it('should report on array parameter without style and explode', async () => {
8
+ const document = parseYamlToDocument(
9
+ outdent`
10
+ openapi: 3.0.0
11
+ paths:
12
+ '/test':
13
+ parameters:
14
+ - name: a
15
+ in: query
16
+ schema:
17
+ type: array
18
+ items:
19
+ type: string
20
+ - name: b
21
+ in: header
22
+ schema:
23
+ type: array
24
+ items:
25
+ type: string
26
+ `,
27
+ 'foobar.yaml'
28
+ );
29
+ const results = await lintDocument({
30
+ externalRefResolver: new BaseResolver(),
31
+ document,
32
+ config: await makeConfig({
33
+ 'array-parameter-serialization': { severity: 'error', in: ['query'] },
34
+ }),
35
+ });
36
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
37
+ [
38
+ {
39
+ "location": [
40
+ {
41
+ "pointer": "#/paths/~1test/parameters/0",
42
+ "reportOnKey": false,
43
+ "source": "foobar.yaml",
44
+ },
45
+ ],
46
+ "message": "Parameter \`a\` should have \`style\` and \`explode \` fields",
47
+ "ruleId": "array-parameter-serialization",
48
+ "severity": "error",
49
+ "suggest": [],
50
+ },
51
+ ]
52
+ `);
53
+ });
54
+
55
+ it('should report on array parameter with style but without explode', async () => {
56
+ const document = parseYamlToDocument(
57
+ outdent`
58
+ openapi: 3.0.0
59
+ paths:
60
+ '/test':
61
+ parameters:
62
+ - name: a
63
+ in: query
64
+ style: form
65
+ schema:
66
+ type: array
67
+ items:
68
+ type: string
69
+ `,
70
+ 'foobar.yaml'
71
+ );
72
+ const results = await lintDocument({
73
+ externalRefResolver: new BaseResolver(),
74
+ document,
75
+ config: await makeConfig({
76
+ 'array-parameter-serialization': { severity: 'error', in: ['query'] },
77
+ }),
78
+ });
79
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
80
+ [
81
+ {
82
+ "location": [
83
+ {
84
+ "pointer": "#/paths/~1test/parameters/0",
85
+ "reportOnKey": false,
86
+ "source": "foobar.yaml",
87
+ },
88
+ ],
89
+ "message": "Parameter \`a\` should have \`style\` and \`explode \` fields",
90
+ "ruleId": "array-parameter-serialization",
91
+ "severity": "error",
92
+ "suggest": [],
93
+ },
94
+ ]
95
+ `);
96
+ });
97
+
98
+ it('should report on parameter without type but with items', async () => {
99
+ const document = parseYamlToDocument(
100
+ outdent`
101
+ openapi: 3.1.0
102
+ paths:
103
+ /test:
104
+ parameters:
105
+ - name: test only type, path level
106
+ in: query
107
+ schema:
108
+ type: array # no items
109
+ get:
110
+ parameters:
111
+ - name: test only items, operation level
112
+ in: header
113
+ items: # no type
114
+ type: string
115
+ components:
116
+ parameters:
117
+ TestParameter:
118
+ in: cookie
119
+ name: test only prefixItems, components level
120
+ prefixItems: # no type or items
121
+ - type: number
122
+ `,
123
+ 'foobar.yaml'
124
+ );
125
+ const results = await lintDocument({
126
+ externalRefResolver: new BaseResolver(),
127
+ document,
128
+ config: await makeConfig({
129
+ 'array-parameter-serialization': { severity: 'error', in: ['query'] },
130
+ }),
131
+ });
132
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
133
+ [
134
+ {
135
+ "location": [
136
+ {
137
+ "pointer": "#/paths/~1test/parameters/0",
138
+ "reportOnKey": false,
139
+ "source": "foobar.yaml",
140
+ },
141
+ ],
142
+ "message": "Parameter \`test only type, path level\` should have \`style\` and \`explode \` fields",
143
+ "ruleId": "array-parameter-serialization",
144
+ "severity": "error",
145
+ "suggest": [],
146
+ },
147
+ ]
148
+ `);
149
+ });
150
+
151
+ it('should not report on array parameter with style and explode', async () => {
152
+ const document = parseYamlToDocument(
153
+ outdent`
154
+ openapi: 3.0.0
155
+ paths:
156
+ '/test':
157
+ parameters:
158
+ - name: a
159
+ in: query
160
+ style: form
161
+ explode: false
162
+ schema:
163
+ type: array
164
+ items:
165
+ type: string
166
+ `,
167
+ 'foobar.yaml'
168
+ );
169
+ const results = await lintDocument({
170
+ externalRefResolver: new BaseResolver(),
171
+ document,
172
+ config: await makeConfig({
173
+ 'array-parameter-serialization': { severity: 'error', in: ['query'] },
174
+ }),
175
+ });
176
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
177
+ });
178
+
179
+ it('should not report non-array parameter without style and explode', async () => {
180
+ const document = parseYamlToDocument(
181
+ outdent`
182
+ openapi: 3.0.0
183
+ paths:
184
+ '/test':
185
+ parameters:
186
+ - name: a
187
+ in: query
188
+ schema:
189
+ type: string
190
+ `,
191
+ 'foobar.yaml'
192
+ );
193
+ const results = await lintDocument({
194
+ externalRefResolver: new BaseResolver(),
195
+ document,
196
+ config: await makeConfig({
197
+ 'array-parameter-serialization': { severity: 'error', in: ['query'] },
198
+ }),
199
+ });
200
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
201
+ });
202
+
203
+ it("should report all array parameter without style and explode if property 'in' not defined ", async () => {
204
+ const document = parseYamlToDocument(
205
+ outdent`
206
+ openapi: 3.0.0
207
+ paths:
208
+ '/test':
209
+ parameters:
210
+ - name: a
211
+ in: query
212
+ schema:
213
+ type: array
214
+ items:
215
+ type: string
216
+ - name: b
217
+ in: header
218
+ schema:
219
+ type: array
220
+ items:
221
+ type: string
222
+ `,
223
+ 'foobar.yaml'
224
+ );
225
+ const results = await lintDocument({
226
+ externalRefResolver: new BaseResolver(),
227
+ document,
228
+ config: await makeConfig({
229
+ 'array-parameter-serialization': { severity: 'error' },
230
+ }),
231
+ });
232
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
233
+ [
234
+ {
235
+ "location": [
236
+ {
237
+ "pointer": "#/paths/~1test/parameters/0",
238
+ "reportOnKey": false,
239
+ "source": "foobar.yaml",
240
+ },
241
+ ],
242
+ "message": "Parameter \`a\` should have \`style\` and \`explode \` fields",
243
+ "ruleId": "array-parameter-serialization",
244
+ "severity": "error",
245
+ "suggest": [],
246
+ },
247
+ {
248
+ "location": [
249
+ {
250
+ "pointer": "#/paths/~1test/parameters/1",
251
+ "reportOnKey": false,
252
+ "source": "foobar.yaml",
253
+ },
254
+ ],
255
+ "message": "Parameter \`b\` should have \`style\` and \`explode \` fields",
256
+ "ruleId": "array-parameter-serialization",
257
+ "severity": "error",
258
+ "suggest": [],
259
+ },
260
+ ]
261
+ `);
262
+ });
263
+ });
@@ -0,0 +1,43 @@
1
+ import { Oas3Rule, Oas3Visitor } from '../../visitors';
2
+ import { isRef } from '../../ref-utils';
3
+ import { Oas3_1Schema, Oas3Parameter } from '../../typings/openapi';
4
+
5
+ export type ArrayParameterSerializationOptions = {
6
+ in?: string[];
7
+ };
8
+
9
+ export const ArrayParameterSerialization: Oas3Rule = (
10
+ options: ArrayParameterSerializationOptions
11
+ ): Oas3Visitor => {
12
+ return {
13
+ Parameter: {
14
+ leave(node: Oas3Parameter, ctx) {
15
+ if (!node.schema) {
16
+ return;
17
+ }
18
+ const schema = isRef(node.schema)
19
+ ? ctx.resolve<Oas3_1Schema>(node.schema).node
20
+ : (node.schema as Oas3_1Schema);
21
+
22
+ if (schema && shouldReportMissingStyleAndExplode(node, schema, options)) {
23
+ ctx.report({
24
+ message: `Parameter \`${node.name}\` should have \`style\` and \`explode \` fields`,
25
+ location: ctx.location,
26
+ });
27
+ }
28
+ },
29
+ },
30
+ };
31
+ };
32
+
33
+ function shouldReportMissingStyleAndExplode(
34
+ node: Oas3Parameter,
35
+ schema: Oas3_1Schema,
36
+ options: ArrayParameterSerializationOptions
37
+ ) {
38
+ return (
39
+ (schema.type === 'array' || schema.items || schema.prefixItems) &&
40
+ (node.style === undefined || node.explode === undefined) &&
41
+ (!options.in || (node.in && options.in?.includes(node.in)))
42
+ );
43
+ }
@@ -52,6 +52,7 @@ import { Operation4xxProblemDetailsRfc7807 } from './operation-4xx-problem-detai
52
52
  import { RequiredStringPropertyMissingMinLength } from '../common/required-string-property-missing-min-length';
53
53
  import { SpecStrictRefs } from '../common/spec-strict-refs';
54
54
  import { ComponentNameUnique } from './component-name-unique';
55
+ import { ArrayParameterSerialization } from './array-parameter-serialization';
55
56
 
56
57
  export const rules: Oas3RuleSet<'built-in'> = {
57
58
  spec: Spec,
@@ -108,6 +109,7 @@ export const rules: Oas3RuleSet<'built-in'> = {
108
109
  'required-string-property-missing-min-length': RequiredStringPropertyMissingMinLength,
109
110
  'spec-strict-refs': SpecStrictRefs,
110
111
  'component-name-unique': ComponentNameUnique,
112
+ 'array-parameter-serialization': ArrayParameterSerialization,
111
113
  };
112
114
 
113
115
  export const preprocessors = {};
@@ -181,6 +181,17 @@ const Schema: NodeType = {
181
181
  extensionsPrefix: 'x-',
182
182
  };
183
183
 
184
+ const SchemaProperties: NodeType = {
185
+ properties: {},
186
+ additionalProperties: (value: any) => {
187
+ if (typeof value === 'boolean') {
188
+ return { type: 'boolean' };
189
+ } else {
190
+ return 'Schema';
191
+ }
192
+ },
193
+ };
194
+
184
195
  const SecurityScheme: NodeType = {
185
196
  properties: {
186
197
  type: { enum: ['apiKey', 'http', 'oauth2', 'openIdConnect', 'mutualTLS'] },
@@ -256,6 +267,7 @@ export const Oas3_1Types: Record<string, NodeType> = {
256
267
  Info,
257
268
  Root,
258
269
  Schema,
270
+ SchemaProperties,
259
271
  License,
260
272
  Components,
261
273
  NamedPathItems: mapOf('PathItem'),
@@ -77,6 +77,7 @@ const builtInOAS3Rules = [
77
77
  'response-contains-property',
78
78
  'response-mime-type',
79
79
  'spec-components-invalid-map-name',
80
+ 'array-parameter-serialization',
80
81
  ] as const;
81
82
 
82
83
  export type BuiltInOAS3RuleId = typeof builtInOAS3Rules[number];
@@ -156,6 +156,7 @@ export interface Oas3Schema {
156
156
  export type Oas3_1Schema = Oas3Schema & {
157
157
  type?: string | string[];
158
158
  examples?: any[];
159
+ prefixItems?: Oas3_1Schema[];
159
160
  };
160
161
 
161
162
  export interface Oas3_1Definition extends Oas3Definition {
package/src/visitors.ts CHANGED
@@ -204,6 +204,7 @@ type Oas2FlatVisitor = {
204
204
  NamedResponses?: VisitFunctionOrObject<Record<string, Oas2Response>>;
205
205
  NamedParameters?: VisitFunctionOrObject<Record<string, Oas2Parameter>>;
206
206
  SecurityScheme?: VisitFunctionOrObject<Oas2SecurityScheme>;
207
+ NamedSecuritySchemes?: VisitFunctionOrObject<Record<string, Oas2SecurityScheme>>;
207
208
  SpecExtension?: VisitFunctionOrObject<unknown>;
208
209
  };
209
210
 
@@ -255,18 +256,6 @@ export type Async2Visitor = BaseVisitor &
255
256
  Async2NestedVisitor &
256
257
  Record<string, VisitFunction<any> | NestedVisitObject<any, Async2NestedVisitor>>;
257
258
 
258
- export type Oas3TransformVisitor = BaseVisitor &
259
- Oas3FlatVisitor &
260
- Record<string, VisitFunction<any> | VisitObject<any>>;
261
-
262
- export type Oas2TransformVisitor = BaseVisitor &
263
- Oas2FlatVisitor &
264
- Record<string, VisitFunction<any> | VisitObject<any>>;
265
-
266
- export type Async2TransformVisitor = BaseVisitor &
267
- Async2FlatVisitor &
268
- Record<string, VisitFunction<any> | VisitObject<any>>;
269
-
270
259
  export type NestedVisitor<T> = Exclude<T, 'any' | 'ref' | 'Root'>;
271
260
 
272
261
  export type NormalizedOasVisitors<T extends BaseVisitor> = {
@@ -289,12 +278,12 @@ export type NormalizedOasVisitors<T extends BaseVisitor> = {
289
278
  export type Oas3Rule = (options: Record<string, any>) => Oas3Visitor | Oas3Visitor[];
290
279
  export type Oas2Rule = (options: Record<string, any>) => Oas2Visitor | Oas2Visitor[];
291
280
  export type Async2Rule = (options: Record<string, any>) => Async2Visitor | Async2Visitor[];
292
- export type Oas3Preprocessor = (options: Record<string, any>) => Oas3TransformVisitor;
293
- export type Oas2Preprocessor = (options: Record<string, any>) => Oas2TransformVisitor;
294
- export type Async2Preprocessor = (options: Record<string, any>) => Async2TransformVisitor;
295
- export type Oas3Decorator = (options: Record<string, any>) => Oas3TransformVisitor;
296
- export type Oas2Decorator = (options: Record<string, any>) => Oas2TransformVisitor;
297
- export type Async2Decorator = (options: Record<string, any>) => Async2TransformVisitor;
281
+ export type Oas3Preprocessor = (options: Record<string, any>) => Oas3Visitor;
282
+ export type Oas2Preprocessor = (options: Record<string, any>) => Oas2Visitor;
283
+ export type Async2Preprocessor = (options: Record<string, any>) => Async2Visitor;
284
+ export type Oas3Decorator = (options: Record<string, any>) => Oas3Visitor;
285
+ export type Oas2Decorator = (options: Record<string, any>) => Oas2Visitor;
286
+ export type Async2Decorator = (options: Record<string, any>) => Async2Visitor;
298
287
 
299
288
  // alias for the latest version supported
300
289
  // every time we update it - consider semver