@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.
- package/CHANGELOG.md +18 -0
- package/README.md +5 -5
- package/lib/bundle.js +2 -2
- package/lib/config/all.js +2 -0
- package/lib/config/minimal.js +2 -0
- package/lib/config/recommended-strict.js +2 -0
- package/lib/config/recommended.js +2 -0
- package/lib/decorators/oas2/remove-unused-components.d.ts +2 -0
- package/lib/decorators/oas3/remove-unused-components.d.ts +2 -0
- package/lib/env.js +1 -1
- package/lib/rules/oas3/array-parameter-serialization.d.ts +5 -0
- package/lib/rules/oas3/array-parameter-serialization.js +31 -0
- package/lib/rules/oas3/index.js +2 -0
- package/lib/types/oas3_1.js +12 -0
- package/lib/types/redocly-yaml.d.ts +1 -1
- package/lib/types/redocly-yaml.js +1 -0
- package/lib/typings/openapi.d.ts +1 -0
- package/lib/visitors.d.ts +7 -9
- package/package.json +1 -1
- package/src/bundle.ts +4 -4
- package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +4 -0
- package/src/config/all.ts +2 -0
- package/src/config/minimal.ts +2 -0
- package/src/config/recommended-strict.ts +2 -0
- package/src/config/recommended.ts +2 -0
- package/src/{rules → decorators}/oas2/remove-unused-components.ts +3 -3
- package/src/{rules → decorators}/oas3/remove-unused-components.ts +3 -3
- package/src/env.ts +1 -1
- package/src/rules/oas3/__tests__/array-parameter-serialization.test.ts +263 -0
- package/src/rules/oas3/array-parameter-serialization.ts +43 -0
- package/src/rules/oas3/index.ts +2 -0
- package/src/types/oas3_1.ts +12 -0
- package/src/types/redocly-yaml.ts +1 -0
- package/src/typings/openapi.ts +1 -0
- package/src/visitors.ts +7 -18
- package/tsconfig.tsbuildinfo +1 -1
- package/lib/rules/oas2/remove-unused-components.d.ts +0 -2
- package/lib/rules/oas3/remove-unused-components.d.ts +0 -2
- /package/lib/{rules → decorators}/oas2/remove-unused-components.js +0 -0
- /package/lib/{rules → decorators}/oas3/remove-unused-components.js +0 -0
- /package/src/{rules → decorators}/oas2/__tests__/remove-unused-components.test.ts +0 -0
- /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
|
+
}
|
package/src/rules/oas3/index.ts
CHANGED
|
@@ -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 = {};
|
package/src/types/oas3_1.ts
CHANGED
|
@@ -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'),
|
package/src/typings/openapi.ts
CHANGED
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>) =>
|
|
293
|
-
export type Oas2Preprocessor = (options: Record<string, any>) =>
|
|
294
|
-
export type Async2Preprocessor = (options: Record<string, any>) =>
|
|
295
|
-
export type Oas3Decorator = (options: Record<string, any>) =>
|
|
296
|
-
export type Oas2Decorator = (options: Record<string, any>) =>
|
|
297
|
-
export type Async2Decorator = (options: Record<string, any>) =>
|
|
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
|