@redocly/openapi-core 1.4.1 → 1.6.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 (50) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +5 -5
  3. package/lib/bundle.d.ts +8 -6
  4. package/lib/bundle.js +48 -14
  5. package/lib/config/all.js +2 -0
  6. package/lib/config/config-resolvers.d.ts +9 -1
  7. package/lib/config/config-resolvers.js +22 -1
  8. package/lib/config/load.d.ts +11 -3
  9. package/lib/config/load.js +12 -6
  10. package/lib/config/minimal.js +2 -0
  11. package/lib/config/recommended-strict.js +2 -0
  12. package/lib/config/recommended.js +2 -0
  13. package/lib/lint.d.ts +6 -3
  14. package/lib/lint.js +14 -3
  15. package/lib/rules/oas3/array-parameter-serialization.d.ts +5 -0
  16. package/lib/rules/oas3/array-parameter-serialization.js +31 -0
  17. package/lib/rules/oas3/index.js +2 -0
  18. package/lib/types/portal-config-schema.d.ts +22 -2465
  19. package/lib/types/portal-config-schema.js +56 -38
  20. package/lib/types/redocly-yaml.d.ts +1 -1
  21. package/lib/types/redocly-yaml.js +5 -4
  22. package/lib/typings/openapi.d.ts +1 -0
  23. package/lib/visitors.d.ts +5 -5
  24. package/lib/visitors.js +4 -6
  25. package/lib/walk.d.ts +3 -3
  26. package/lib/walk.js +2 -2
  27. package/package.json +1 -1
  28. package/src/__tests__/lint.test.ts +2 -2
  29. package/src/bundle.ts +67 -26
  30. package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +4 -0
  31. package/src/config/__tests__/fixtures/resolve-refs-in-config/config-with-refs.yaml +8 -0
  32. package/src/config/__tests__/fixtures/resolve-refs-in-config/rules.yaml +2 -0
  33. package/src/config/__tests__/fixtures/resolve-refs-in-config/seo.yaml +1 -0
  34. package/src/config/__tests__/load.test.ts +80 -1
  35. package/src/config/all.ts +2 -0
  36. package/src/config/config-resolvers.ts +41 -11
  37. package/src/config/load.ts +36 -19
  38. package/src/config/minimal.ts +2 -0
  39. package/src/config/recommended-strict.ts +2 -0
  40. package/src/config/recommended.ts +2 -0
  41. package/src/lint.ts +32 -10
  42. package/src/rules/oas3/__tests__/array-parameter-serialization.test.ts +263 -0
  43. package/src/rules/oas3/array-parameter-serialization.ts +43 -0
  44. package/src/rules/oas3/index.ts +2 -0
  45. package/src/types/portal-config-schema.ts +65 -42
  46. package/src/types/redocly-yaml.ts +3 -4
  47. package/src/typings/openapi.ts +1 -0
  48. package/src/visitors.ts +20 -22
  49. package/src/walk.ts +8 -8
  50. package/tsconfig.tsbuildinfo +1 -1
@@ -1,7 +1,7 @@
1
1
  import * as path from 'path';
2
2
  import { isAbsoluteUrl } from '../ref-utils';
3
3
  import { pickDefined } from '../utils';
4
- import { BaseResolver } from '../resolve';
4
+ import { resolveDocument, BaseResolver } from '../resolve';
5
5
  import { defaultPlugin } from './builtIn';
6
6
  import {
7
7
  getResolveConfig,
@@ -11,6 +11,14 @@ import {
11
11
  prefixRules,
12
12
  transformConfig,
13
13
  } from './utils';
14
+ import { isBrowser } from '../env';
15
+ import { isNotString, isString, isDefined, parseYaml, keysOf } from '../utils';
16
+ import { Config } from './config';
17
+ import { colorize, logger } from '../logger';
18
+ import { asserts, buildAssertCustomFunction } from '../rules/common/assertions/asserts';
19
+ import { normalizeTypes } from '../types';
20
+ import { ConfigTypes } from '../types/redocly-yaml';
21
+
14
22
  import type {
15
23
  StyleguideRawConfig,
16
24
  ApiStyleguideRawConfig,
@@ -21,17 +29,39 @@ import type {
21
29
  RuleConfig,
22
30
  DeprecatedInRawConfig,
23
31
  } from './types';
24
- import { isBrowser } from '../env';
25
- import { isNotString, isString, isDefined, parseYaml, keysOf } from '../utils';
26
- import { Config } from './config';
27
- import { colorize, logger } from '../logger';
28
- import {
29
- Asserts,
30
- AssertionFn,
31
- asserts,
32
- buildAssertCustomFunction,
33
- } from '../rules/common/assertions/asserts';
34
32
  import type { Assertion, AssertionDefinition, RawAssertion } from '../rules/common/assertions';
33
+ import type { Asserts, AssertionFn } from '../rules/common/assertions/asserts';
34
+ import type { BundleOptions } from '../bundle';
35
+ import type { Document, ResolvedRefMap } from '../resolve';
36
+
37
+ export async function resolveConfigFileAndRefs({
38
+ configPath,
39
+ externalRefResolver = new BaseResolver(),
40
+ base = null,
41
+ }: Omit<BundleOptions, 'config'> & { configPath?: string }): Promise<{
42
+ document: Document;
43
+ resolvedRefMap: ResolvedRefMap;
44
+ }> {
45
+ if (!configPath) {
46
+ throw new Error('Reference to a config is required.\n');
47
+ }
48
+
49
+ const document = await externalRefResolver.resolveDocument(base, configPath, true);
50
+
51
+ if (document instanceof Error) {
52
+ throw document;
53
+ }
54
+
55
+ const types = normalizeTypes(ConfigTypes);
56
+
57
+ const resolvedRefMap = await resolveDocument({
58
+ rootDocument: document,
59
+ rootType: types.ConfigRoot,
60
+ externalRefResolver,
61
+ });
62
+
63
+ return { document, resolvedRefMap };
64
+ }
35
65
 
36
66
  export async function resolveConfig(rawConfig: RawConfig, configPath?: string): Promise<Config> {
37
67
  if (rawConfig.styleguide?.extends?.some(isNotString)) {
@@ -1,20 +1,17 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { RedoclyClient } from '../redocly';
4
- import { isEmptyObject, loadYaml, doesYamlFileExist } from '../utils';
4
+ import { isEmptyObject, doesYamlFileExist } from '../utils';
5
5
  import { parseYaml } from '../js-yaml';
6
6
  import { Config, DOMAINS } from './config';
7
7
  import { ConfigValidationError, transformConfig } from './utils';
8
- import { resolveConfig } from './config-resolvers';
8
+ import { resolveConfig, resolveConfigFileAndRefs } from './config-resolvers';
9
+ import { bundleConfig } from '../bundle';
9
10
 
10
- import type {
11
- DeprecatedInRawConfig,
12
- FlatRawConfig,
13
- RawConfig,
14
- RawUniversalConfig,
15
- Region,
16
- } from './types';
17
- import { RegionalTokenWithValidity } from '../redocly/redocly-client-types';
11
+ import type { Document } from '../resolve';
12
+ import type { RegionalTokenWithValidity } from '../redocly/redocly-client-types';
13
+ import type { RawConfig, RawUniversalConfig, Region } from './types';
14
+ import type { BaseResolver, ResolvedRefMap } from '../resolve';
18
15
 
19
16
  async function addConfigMetadata({
20
17
  rawConfig,
@@ -73,17 +70,30 @@ async function addConfigMetadata({
73
70
  );
74
71
  }
75
72
 
73
+ export type RawConfigProcessor = (
74
+ rawConfig: Document,
75
+ resolvedRefMap: ResolvedRefMap
76
+ ) => void | Promise<void>;
77
+
76
78
  export async function loadConfig(
77
79
  options: {
78
80
  configPath?: string;
79
81
  customExtends?: string[];
80
- processRawConfig?: (rawConfig: RawConfig) => void | Promise<void>;
82
+ processRawConfig?: RawConfigProcessor;
83
+ externalRefResolver?: BaseResolver;
81
84
  files?: string[];
82
85
  region?: Region;
83
86
  } = {}
84
87
  ): Promise<Config> {
85
- const { configPath = findConfig(), customExtends, processRawConfig, files, region } = options;
86
- const rawConfig = await getConfig(configPath, processRawConfig);
88
+ const {
89
+ configPath = findConfig(),
90
+ customExtends,
91
+ processRawConfig,
92
+ files,
93
+ region,
94
+ externalRefResolver,
95
+ } = options;
96
+ const rawConfig = await getConfig({ configPath, processRawConfig, externalRefResolver });
87
97
 
88
98
  const redoclyClient = new RedoclyClient();
89
99
  const tokens = await redoclyClient.getTokens();
@@ -116,17 +126,24 @@ export function findConfig(dir?: string): string | undefined {
116
126
  }
117
127
 
118
128
  export async function getConfig(
119
- configPath: string | undefined = findConfig(),
120
- processRawConfig?: (rawConfig: RawConfig) => void | Promise<void>
129
+ options: {
130
+ configPath?: string;
131
+ processRawConfig?: RawConfigProcessor;
132
+ externalRefResolver?: BaseResolver;
133
+ } = {}
121
134
  ): Promise<RawConfig> {
135
+ const { configPath = findConfig(), processRawConfig, externalRefResolver } = options;
122
136
  if (!configPath || !doesYamlFileExist(configPath)) return {};
123
137
  try {
124
- const rawConfig =
125
- (await loadYaml<RawConfig & DeprecatedInRawConfig & FlatRawConfig>(configPath)) || {};
138
+ const { document, resolvedRefMap } = await resolveConfigFileAndRefs({
139
+ configPath,
140
+ externalRefResolver,
141
+ });
126
142
  if (typeof processRawConfig === 'function') {
127
- await processRawConfig(rawConfig);
143
+ await processRawConfig(document, resolvedRefMap);
128
144
  }
129
- return transformConfig(rawConfig);
145
+ const bundledConfig = await bundleConfig(document, resolvedRefMap);
146
+ return transformConfig(bundledConfig);
130
147
  } catch (e) {
131
148
  if (e instanceof ConfigValidationError) {
132
149
  throw e;
@@ -66,6 +66,7 @@ const minimal: PluginStyleguideConfig<'built-in'> = {
66
66
  'request-mime-type': 'off',
67
67
  'response-contains-property': 'off',
68
68
  'response-mime-type': 'off',
69
+ 'array-parameter-serialization': 'off',
69
70
  },
70
71
  oas3_1Rules: {
71
72
  'no-invalid-media-type-examples': 'warn',
@@ -83,6 +84,7 @@ const minimal: PluginStyleguideConfig<'built-in'> = {
83
84
  'request-mime-type': 'off',
84
85
  'response-contains-property': 'off',
85
86
  'response-mime-type': 'off',
87
+ 'array-parameter-serialization': 'off',
86
88
  },
87
89
  async2Rules: {
88
90
  'channels-kebab-case': 'off',
@@ -66,6 +66,7 @@ const recommendedStrict: PluginStyleguideConfig<'built-in'> = {
66
66
  'request-mime-type': 'off',
67
67
  'response-contains-property': 'off',
68
68
  'response-mime-type': 'off',
69
+ 'array-parameter-serialization': 'off',
69
70
  },
70
71
  oas3_1Rules: {
71
72
  'no-invalid-media-type-examples': 'error',
@@ -83,6 +84,7 @@ const recommendedStrict: PluginStyleguideConfig<'built-in'> = {
83
84
  'request-mime-type': 'off',
84
85
  'response-contains-property': 'off',
85
86
  'response-mime-type': 'off',
87
+ 'array-parameter-serialization': 'off',
86
88
  },
87
89
  async2Rules: {
88
90
  'channels-kebab-case': 'off',
@@ -66,6 +66,7 @@ const recommended: PluginStyleguideConfig<'built-in'> = {
66
66
  'request-mime-type': 'off',
67
67
  'response-contains-property': 'off',
68
68
  'response-mime-type': 'off',
69
+ 'array-parameter-serialization': 'off',
69
70
  },
70
71
  oas3_1Rules: {
71
72
  'no-invalid-media-type-examples': 'warn',
@@ -83,6 +84,7 @@ const recommended: PluginStyleguideConfig<'built-in'> = {
83
84
  'request-mime-type': 'off',
84
85
  'response-contains-property': 'off',
85
86
  'response-mime-type': 'off',
87
+ 'array-parameter-serialization': 'off',
86
88
  },
87
89
  async2Rules: {
88
90
  'channels-kebab-case': 'off',
package/src/lint.ts CHANGED
@@ -1,13 +1,18 @@
1
- import { BaseResolver, resolveDocument, Document, makeDocumentFromString } from './resolve';
1
+ import { BaseResolver, resolveDocument, makeDocumentFromString } from './resolve';
2
2
  import { normalizeVisitors } from './visitors';
3
- import { NodeType } from './types';
4
- import { ProblemSeverity, WalkContext, walkDocument } from './walk';
3
+ import { walkDocument } from './walk';
5
4
  import { StyleguideConfig, Config, initRules, defaultPlugin, resolvePlugins } from './config';
6
5
  import { normalizeTypes } from './types';
7
6
  import { releaseAjvInstance } from './rules/ajv';
8
7
  import { SpecVersion, getMajorSpecVersion, detectSpec, getTypes } from './oas-types';
9
8
  import { ConfigTypes } from './types/redocly-yaml';
10
9
  import { Spec } from './rules/common/spec';
10
+ import { NoUnresolvedRefs } from './rules/no-unresolved-refs';
11
+
12
+ import type { Document, ResolvedRefMap } from './resolve';
13
+ import type { ProblemSeverity, WalkContext } from './walk';
14
+ import type { NodeType } from './types';
15
+ import type { NestedVisitObject, Oas3Visitor, RuleInstanceConfig } from './visitors';
11
16
 
12
17
  export async function lint(opts: {
13
18
  ref: string;
@@ -102,8 +107,13 @@ export async function lintDocument(opts: {
102
107
  return ctx.problems.map((problem) => config.addProblemToIgnore(problem));
103
108
  }
104
109
 
105
- export async function lintConfig(opts: { document: Document; severity?: ProblemSeverity }) {
106
- const { document, severity } = opts;
110
+ export async function lintConfig(opts: {
111
+ document: Document;
112
+ resolvedRefMap?: ResolvedRefMap;
113
+ severity?: ProblemSeverity;
114
+ externalRefResolver?: BaseResolver;
115
+ }) {
116
+ const { document, severity, externalRefResolver = new BaseResolver() } = opts;
107
117
 
108
118
  const ctx: WalkContext = {
109
119
  problems: [],
@@ -117,21 +127,33 @@ export async function lintConfig(opts: { document: Document; severity?: ProblemS
117
127
  });
118
128
 
119
129
  const types = normalizeTypes(ConfigTypes, config);
120
- const rules = [
130
+ const rules: (RuleInstanceConfig & {
131
+ visitor: NestedVisitObject<unknown, Oas3Visitor | Oas3Visitor[]>;
132
+ })[] = [
121
133
  {
122
134
  severity: severity || 'error',
123
135
  ruleId: 'configuration spec',
124
136
  visitor: Spec({ severity: 'error' }),
125
137
  },
138
+ {
139
+ severity: severity || 'error',
140
+ ruleId: 'configuration no-unresolved-refs',
141
+ visitor: NoUnresolvedRefs({ severity: 'error' }),
142
+ },
126
143
  ];
127
- // TODO: check why any is needed
128
- const normalizedVisitors = normalizeVisitors(rules as any, types);
129
-
144
+ const normalizedVisitors = normalizeVisitors(rules, types);
145
+ const resolvedRefMap =
146
+ opts.resolvedRefMap ||
147
+ (await resolveDocument({
148
+ rootDocument: document,
149
+ rootType: types.ConfigRoot,
150
+ externalRefResolver,
151
+ }));
130
152
  walkDocument({
131
153
  document,
132
154
  rootType: types.ConfigRoot,
133
155
  normalizedVisitors,
134
- resolvedRefMap: new Map(),
156
+ resolvedRefMap,
135
157
  ctx,
136
158
  });
137
159
 
@@ -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 = {};