@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.
- package/CHANGELOG.md +12 -0
- package/README.md +5 -5
- package/lib/bundle.d.ts +8 -6
- package/lib/bundle.js +48 -14
- package/lib/config/all.js +2 -0
- package/lib/config/config-resolvers.d.ts +9 -1
- package/lib/config/config-resolvers.js +22 -1
- package/lib/config/load.d.ts +11 -3
- package/lib/config/load.js +12 -6
- package/lib/config/minimal.js +2 -0
- package/lib/config/recommended-strict.js +2 -0
- package/lib/config/recommended.js +2 -0
- package/lib/lint.d.ts +6 -3
- package/lib/lint.js +14 -3
- 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/portal-config-schema.d.ts +22 -2465
- package/lib/types/portal-config-schema.js +56 -38
- package/lib/types/redocly-yaml.d.ts +1 -1
- package/lib/types/redocly-yaml.js +5 -4
- package/lib/typings/openapi.d.ts +1 -0
- package/lib/visitors.d.ts +5 -5
- package/lib/visitors.js +4 -6
- package/lib/walk.d.ts +3 -3
- package/lib/walk.js +2 -2
- package/package.json +1 -1
- package/src/__tests__/lint.test.ts +2 -2
- package/src/bundle.ts +67 -26
- package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +4 -0
- package/src/config/__tests__/fixtures/resolve-refs-in-config/config-with-refs.yaml +8 -0
- package/src/config/__tests__/fixtures/resolve-refs-in-config/rules.yaml +2 -0
- package/src/config/__tests__/fixtures/resolve-refs-in-config/seo.yaml +1 -0
- package/src/config/__tests__/load.test.ts +80 -1
- package/src/config/all.ts +2 -0
- package/src/config/config-resolvers.ts +41 -11
- package/src/config/load.ts +36 -19
- package/src/config/minimal.ts +2 -0
- package/src/config/recommended-strict.ts +2 -0
- package/src/config/recommended.ts +2 -0
- package/src/lint.ts +32 -10
- 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/portal-config-schema.ts +65 -42
- package/src/types/redocly-yaml.ts +3 -4
- package/src/typings/openapi.ts +1 -0
- package/src/visitors.ts +20 -22
- package/src/walk.ts +8 -8
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.rootRedoclyConfigSchema = exports.environmentSchema = exports.redoclyConfigSchema = exports.apiConfigSchema = exports.ssoConfigSchema = void 0;
|
|
3
|
+
exports.rootRedoclyConfigSchema = exports.environmentSchema = exports.redoclyConfigSchema = exports.PortalConfigNodeTypes = exports.apiConfigSchema = exports.ssoConfigSchema = void 0;
|
|
4
4
|
const config_1 = require("../config");
|
|
5
5
|
const oidcIssuerMetadataSchema = {
|
|
6
6
|
type: 'object',
|
|
@@ -80,6 +80,7 @@ const authProviderConfigSchema = {
|
|
|
80
80
|
};
|
|
81
81
|
exports.ssoConfigSchema = {
|
|
82
82
|
type: 'object',
|
|
83
|
+
properties: {},
|
|
83
84
|
additionalProperties: authProviderConfigSchema,
|
|
84
85
|
};
|
|
85
86
|
const redirectConfigSchema = {
|
|
@@ -89,7 +90,12 @@ const redirectConfigSchema = {
|
|
|
89
90
|
type: { type: 'number', default: 301 },
|
|
90
91
|
},
|
|
91
92
|
required: ['to'],
|
|
92
|
-
|
|
93
|
+
};
|
|
94
|
+
const redirectsConfigSchema = {
|
|
95
|
+
type: 'object',
|
|
96
|
+
properties: {},
|
|
97
|
+
additionalProperties: 'redirectConfigSchema',
|
|
98
|
+
default: {},
|
|
93
99
|
};
|
|
94
100
|
exports.apiConfigSchema = {
|
|
95
101
|
type: 'object',
|
|
@@ -136,13 +142,16 @@ const seoConfigSchema = {
|
|
|
136
142
|
},
|
|
137
143
|
},
|
|
138
144
|
},
|
|
139
|
-
additionalProperties: false,
|
|
140
145
|
};
|
|
141
|
-
const rbacScopeItemsSchema = {
|
|
146
|
+
const rbacScopeItemsSchema = {
|
|
147
|
+
type: 'object',
|
|
148
|
+
properties: {},
|
|
149
|
+
additionalProperties: { type: 'string' },
|
|
150
|
+
};
|
|
142
151
|
const rbacConfigSchema = {
|
|
143
152
|
type: 'object',
|
|
144
153
|
properties: {
|
|
145
|
-
defaults: rbacScopeItemsSchema,
|
|
154
|
+
defaults: 'rbacScopeItemsSchema',
|
|
146
155
|
},
|
|
147
156
|
additionalProperties: rbacScopeItemsSchema,
|
|
148
157
|
};
|
|
@@ -207,7 +216,6 @@ const devOnboardingAdapterConfigSchema = {
|
|
|
207
216
|
const devOnboardingConfigSchema = {
|
|
208
217
|
type: 'object',
|
|
209
218
|
required: ['adapters'],
|
|
210
|
-
additionalProperties: false,
|
|
211
219
|
properties: {
|
|
212
220
|
adapters: {
|
|
213
221
|
type: 'array',
|
|
@@ -215,6 +223,30 @@ const devOnboardingConfigSchema = {
|
|
|
215
223
|
},
|
|
216
224
|
},
|
|
217
225
|
};
|
|
226
|
+
const i18ConfigSchema = {
|
|
227
|
+
type: 'object',
|
|
228
|
+
properties: {
|
|
229
|
+
defaultLocale: {
|
|
230
|
+
type: 'string',
|
|
231
|
+
},
|
|
232
|
+
locales: {
|
|
233
|
+
type: 'array',
|
|
234
|
+
items: {
|
|
235
|
+
type: 'object',
|
|
236
|
+
properties: {
|
|
237
|
+
code: {
|
|
238
|
+
type: 'string',
|
|
239
|
+
},
|
|
240
|
+
name: {
|
|
241
|
+
type: 'string',
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
required: ['code'],
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
required: ['defaultLocale', 'locales'],
|
|
249
|
+
};
|
|
218
250
|
const responseHeaderSchema = {
|
|
219
251
|
type: 'object',
|
|
220
252
|
properties: {
|
|
@@ -224,14 +256,24 @@ const responseHeaderSchema = {
|
|
|
224
256
|
additionalProperties: false,
|
|
225
257
|
required: ['name', 'value'],
|
|
226
258
|
};
|
|
259
|
+
exports.PortalConfigNodeTypes = {
|
|
260
|
+
seoConfigSchema,
|
|
261
|
+
rbacConfigSchema,
|
|
262
|
+
rbacScopeItemsSchema,
|
|
263
|
+
ssoConfigSchema: exports.ssoConfigSchema,
|
|
264
|
+
devOnboardingConfigSchema,
|
|
265
|
+
i18ConfigSchema,
|
|
266
|
+
redirectsConfigSchema,
|
|
267
|
+
redirectConfigSchema,
|
|
268
|
+
// TODO: Extract other types that need to be linted in the config
|
|
269
|
+
};
|
|
227
270
|
exports.redoclyConfigSchema = {
|
|
228
271
|
type: 'object',
|
|
229
272
|
properties: {
|
|
230
273
|
licenseKey: { type: 'string' },
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
rbac: rbacConfigSchema,
|
|
274
|
+
redirects: 'redirectsConfigSchema',
|
|
275
|
+
seo: 'seoConfigSchema',
|
|
276
|
+
rbac: 'rbacConfigSchema',
|
|
235
277
|
responseHeaders: {
|
|
236
278
|
type: 'object',
|
|
237
279
|
additionalProperties: {
|
|
@@ -253,37 +295,12 @@ exports.redoclyConfigSchema = {
|
|
|
253
295
|
type: 'object',
|
|
254
296
|
additionalProperties: exports.apiConfigSchema,
|
|
255
297
|
},
|
|
256
|
-
sso:
|
|
257
|
-
developerOnboarding: devOnboardingConfigSchema,
|
|
258
|
-
i18n:
|
|
259
|
-
type: 'object',
|
|
260
|
-
properties: {
|
|
261
|
-
defaultLocale: {
|
|
262
|
-
type: 'string',
|
|
263
|
-
},
|
|
264
|
-
locales: {
|
|
265
|
-
type: 'array',
|
|
266
|
-
items: {
|
|
267
|
-
type: 'object',
|
|
268
|
-
properties: {
|
|
269
|
-
code: {
|
|
270
|
-
type: 'string',
|
|
271
|
-
},
|
|
272
|
-
name: {
|
|
273
|
-
type: 'string',
|
|
274
|
-
},
|
|
275
|
-
},
|
|
276
|
-
required: ['code'],
|
|
277
|
-
},
|
|
278
|
-
},
|
|
279
|
-
},
|
|
280
|
-
additionalProperties: false,
|
|
281
|
-
required: ['defaultLocale', 'locales'],
|
|
282
|
-
},
|
|
298
|
+
sso: 'ssoConfigSchema',
|
|
299
|
+
developerOnboarding: 'devOnboardingConfigSchema',
|
|
300
|
+
i18n: 'i18ConfigSchema',
|
|
283
301
|
metadata: metadataConfigSchema,
|
|
284
302
|
},
|
|
285
303
|
default: {},
|
|
286
|
-
additionalProperties: true,
|
|
287
304
|
};
|
|
288
305
|
exports.environmentSchema = {
|
|
289
306
|
oneOf: [
|
|
@@ -300,5 +317,6 @@ exports.environmentSchema = {
|
|
|
300
317
|
};
|
|
301
318
|
exports.rootRedoclyConfigSchema = Object.assign(Object.assign({}, exports.redoclyConfigSchema), { properties: Object.assign(Object.assign({}, exports.redoclyConfigSchema.properties), { env: {
|
|
302
319
|
type: 'object',
|
|
320
|
+
properties: {},
|
|
303
321
|
additionalProperties: exports.environmentSchema,
|
|
304
322
|
} }), default: {}, required: ['redirects'] });
|
|
@@ -5,7 +5,7 @@ declare const builtInCommonOASRules: readonly ["info-license-url", "info-license
|
|
|
5
5
|
export type BuiltInCommonOASRuleId = typeof builtInCommonOASRules[number];
|
|
6
6
|
declare const builtInOAS2Rules: readonly ["boolean-parameter-prefixes", "request-mime-type", "response-contains-property", "response-mime-type"];
|
|
7
7
|
export type BuiltInOAS2RuleId = typeof builtInOAS2Rules[number];
|
|
8
|
-
declare const builtInOAS3Rules: readonly ["boolean-parameter-prefixes", "component-name-unique", "no-empty-servers", "no-example-value-and-externalValue", "no-invalid-media-type-examples", "no-server-example.com", "no-server-trailing-slash", "no-server-variables-empty-enum", "no-undefined-server-variable", "no-unused-components", "operation-4xx-problem-details-rfc7807", "request-mime-type", "response-contains-property", "response-mime-type", "spec-components-invalid-map-name"];
|
|
8
|
+
declare const builtInOAS3Rules: readonly ["boolean-parameter-prefixes", "component-name-unique", "no-empty-servers", "no-example-value-and-externalValue", "no-invalid-media-type-examples", "no-server-example.com", "no-server-trailing-slash", "no-server-variables-empty-enum", "no-undefined-server-variable", "no-unused-components", "operation-4xx-problem-details-rfc7807", "request-mime-type", "response-contains-property", "response-mime-type", "spec-components-invalid-map-name", "array-parameter-serialization"];
|
|
9
9
|
export type BuiltInOAS3RuleId = typeof builtInOAS3Rules[number];
|
|
10
10
|
declare const builtInAsync2Rules: readonly ["channels-kebab-case", "no-channel-trailing-slash"];
|
|
11
11
|
export type BuiltInAsync2RuleId = typeof builtInAsync2Rules[number];
|
|
@@ -4,8 +4,8 @@ exports.ConfigTypes = void 0;
|
|
|
4
4
|
const portal_config_schema_1 = require("./portal-config-schema");
|
|
5
5
|
const theme_config_1 = require("./theme-config");
|
|
6
6
|
const _1 = require(".");
|
|
7
|
-
const oas3_1_1 = require("./oas3_1");
|
|
8
7
|
const utils_1 = require("../utils");
|
|
8
|
+
const portal_config_schema_2 = require("./portal-config-schema");
|
|
9
9
|
const builtInCommonRules = [
|
|
10
10
|
'spec',
|
|
11
11
|
'info-contact',
|
|
@@ -70,6 +70,7 @@ const builtInOAS3Rules = [
|
|
|
70
70
|
'response-contains-property',
|
|
71
71
|
'response-mime-type',
|
|
72
72
|
'spec-components-invalid-map-name',
|
|
73
|
+
'array-parameter-serialization',
|
|
73
74
|
];
|
|
74
75
|
const builtInAsync2Rules = ['channels-kebab-case', 'no-channel-trailing-slash'];
|
|
75
76
|
const builtInRules = [
|
|
@@ -195,7 +196,7 @@ const ConfigApisProperties = {
|
|
|
195
196
|
items: {
|
|
196
197
|
type: 'string',
|
|
197
198
|
},
|
|
198
|
-
}
|
|
199
|
+
} }), ConfigStyleguide.properties), { 'features.openapi': 'ConfigReferenceDocs', 'features.mockServer': 'ConfigMockServer', theme: 'ConfigRootTheme', files: {
|
|
199
200
|
type: 'array',
|
|
200
201
|
items: {
|
|
201
202
|
type: 'string',
|
|
@@ -837,7 +838,7 @@ const ConfigMockServer = {
|
|
|
837
838
|
errorIfForcedExampleNotFound: { type: 'boolean' },
|
|
838
839
|
},
|
|
839
840
|
};
|
|
840
|
-
exports.ConfigTypes = Object.assign(
|
|
841
|
+
exports.ConfigTypes = Object.assign({ Assert,
|
|
841
842
|
ConfigRoot,
|
|
842
843
|
ConfigApis,
|
|
843
844
|
ConfigApisProperties,
|
|
@@ -900,4 +901,4 @@ exports.ConfigTypes = Object.assign(Object.assign({}, oas3_1_1.Oas3_1Types), { A
|
|
|
900
901
|
Heading,
|
|
901
902
|
Typography,
|
|
902
903
|
AssertionDefinitionAssertions,
|
|
903
|
-
AssertionDefinitionSubject });
|
|
904
|
+
AssertionDefinitionSubject }, portal_config_schema_2.PortalConfigNodeTypes);
|
package/lib/typings/openapi.d.ts
CHANGED
|
@@ -150,6 +150,7 @@ export interface Oas3Schema {
|
|
|
150
150
|
export type Oas3_1Schema = Oas3Schema & {
|
|
151
151
|
type?: string | string[];
|
|
152
152
|
examples?: any[];
|
|
153
|
+
prefixItems?: Oas3_1Schema[];
|
|
153
154
|
};
|
|
154
155
|
export interface Oas3_1Definition extends Oas3Definition {
|
|
155
156
|
webhooks?: Oas3_1Webhooks;
|
package/lib/visitors.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { Oas2Definition, Oas2Tag, Oas2ExternalDocs, Oas2SecurityRequirement, Oas2Info, Oas2Contact, Oas2License, Oas2PathItem, Oas2Operation, Oas2Header, Oas2Response, Oas2Schema, Oas2Xml, Oas2Parameter, Oas2SecurityScheme } from './typings/swagger';
|
|
3
|
-
import { NormalizedNodeType } from './types';
|
|
1
|
+
import type { NormalizedNodeType } from './types';
|
|
4
2
|
import type { Stack } from './utils';
|
|
5
3
|
import type { UserContext, ResolveResult, ProblemSeverity } from './walk';
|
|
6
4
|
import type { Location } from './ref-utils';
|
|
7
|
-
import {
|
|
5
|
+
import type { Oas3Definition, Oas3ExternalDocs, Oas3Info, Oas3Contact, Oas3Components, Oas3License, Oas3Schema, Oas3Header, Oas3Parameter, Oas3Operation, Oas3PathItem, Oas3ServerVariable, Oas3Server, Oas3MediaType, Oas3Response, Oas3Example, Oas3RequestBody, Oas3Tag, OasRef, Oas3SecurityScheme, Oas3SecurityRequirement, Oas3Encoding, Oas3Link, Oas3Xml, Oas3Discriminator, Oas3Callback } from './typings/openapi';
|
|
6
|
+
import type { Oas2Definition, Oas2Tag, Oas2ExternalDocs, Oas2SecurityRequirement, Oas2Info, Oas2Contact, Oas2License, Oas2PathItem, Oas2Operation, Oas2Header, Oas2Response, Oas2Schema, Oas2Xml, Oas2Parameter, Oas2SecurityScheme } from './typings/swagger';
|
|
7
|
+
import type { Async2Definition } from './typings/asyncapi';
|
|
8
8
|
export type SkipFunctionContext = Pick<UserContext, 'location' | 'rawNode' | 'resolve' | 'rawLocation'>;
|
|
9
9
|
export type VisitFunction<T> = (node: T, ctx: UserContext & {
|
|
10
10
|
ignoreNextVisitorsOnNode: () => void;
|
|
@@ -184,6 +184,6 @@ export type RuleInstanceConfig = {
|
|
|
184
184
|
severity: ProblemSeverity;
|
|
185
185
|
};
|
|
186
186
|
export declare function normalizeVisitors<T extends BaseVisitor>(visitorsConfig: (RuleInstanceConfig & {
|
|
187
|
-
visitor: NestedVisitObject<
|
|
187
|
+
visitor: NestedVisitObject<unknown, T>;
|
|
188
188
|
})[], types: Record<keyof T, NormalizedNodeType>): NormalizedOasVisitors<T>;
|
|
189
189
|
export {};
|
package/lib/visitors.js
CHANGED
|
@@ -77,12 +77,10 @@ function normalizeVisitors(visitorsConfig, types) {
|
|
|
77
77
|
}
|
|
78
78
|
function addWeakFromStack(ruleConf, stack) {
|
|
79
79
|
for (const interType of stack.slice(1)) {
|
|
80
|
-
normalizedVisitors[interType.name] =
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
leave: [],
|
|
85
|
-
};
|
|
80
|
+
normalizedVisitors[interType.name] = normalizedVisitors[interType.name] || {
|
|
81
|
+
enter: [],
|
|
82
|
+
leave: [],
|
|
83
|
+
};
|
|
86
84
|
normalizedVisitors[interType.name].enter.push(Object.assign(Object.assign({}, ruleConf), { visit: () => undefined, depth: 0, context: {
|
|
87
85
|
isSkippedLevel: true,
|
|
88
86
|
seen: new Set(),
|
package/lib/walk.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { Location } from './ref-utils';
|
|
2
|
+
import { ResolveError, YamlParseError, Source } from './resolve';
|
|
3
|
+
import { SpecVersion } from './oas-types';
|
|
1
4
|
import type { Referenced } from './typings/openapi';
|
|
2
5
|
import type { NormalizedOasVisitors, BaseVisitor } from './visitors';
|
|
3
6
|
import type { ResolvedRefMap, Document } from './resolve';
|
|
4
7
|
import type { NormalizedNodeType } from './types';
|
|
5
8
|
import type { RuleSeverity } from './config';
|
|
6
|
-
import { Location } from './ref-utils';
|
|
7
|
-
import { ResolveError, YamlParseError, Source } from './resolve';
|
|
8
|
-
import { SpecVersion } from './oas-types';
|
|
9
9
|
export type NonUndefined = string | number | boolean | symbol | bigint | object | Record<string, any>;
|
|
10
10
|
export type ResolveResult<T extends NonUndefined> = {
|
|
11
11
|
node: T;
|
package/lib/walk.js
CHANGED
|
@@ -86,7 +86,7 @@ function walkDocument(opts) {
|
|
|
86
86
|
const currentEnterVisitors = anyEnterVisitors.concat(((_c = normalizedVisitors[type.name]) === null || _c === void 0 ? void 0 : _c.enter) || []);
|
|
87
87
|
const activatedContexts = [];
|
|
88
88
|
for (const { context, visit, skip, ruleId, severity } of currentEnterVisitors) {
|
|
89
|
-
if (ignoredNodes.has(currentLocation.pointer))
|
|
89
|
+
if (ignoredNodes.has(`${currentLocation.absolutePointer}${currentLocation.pointer}`))
|
|
90
90
|
break;
|
|
91
91
|
if (context.isSkippedLevel) {
|
|
92
92
|
if (context.parent.activatedOn &&
|
|
@@ -248,7 +248,7 @@ function walkDocument(opts) {
|
|
|
248
248
|
parentLocations: collectParentsLocations(context),
|
|
249
249
|
oasVersion: ctx.oasVersion,
|
|
250
250
|
ignoreNextVisitorsOnNode: () => {
|
|
251
|
-
ignoredNodes.add(currentLocation.pointer);
|
|
251
|
+
ignoredNodes.add(`${currentLocation.absolutePointer}${currentLocation.pointer}`);
|
|
252
252
|
},
|
|
253
253
|
getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
|
|
254
254
|
}, collectParents(context), context);
|
package/package.json
CHANGED
|
@@ -22,7 +22,7 @@ describe('lint', () => {
|
|
|
22
22
|
servers:
|
|
23
23
|
- url: http://redocly-example.com
|
|
24
24
|
paths: {}
|
|
25
|
-
|
|
25
|
+
`,
|
|
26
26
|
config: await loadConfig(),
|
|
27
27
|
});
|
|
28
28
|
|
|
@@ -139,8 +139,8 @@ describe('lint', () => {
|
|
|
139
139
|
"ruleId": "configuration spec",
|
|
140
140
|
"severity": "error",
|
|
141
141
|
"suggest": [
|
|
142
|
-
"theme",
|
|
143
142
|
"env",
|
|
143
|
+
"theme",
|
|
144
144
|
"seo",
|
|
145
145
|
"sso",
|
|
146
146
|
],
|
package/src/bundle.ts
CHANGED
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
import isEqual = require('lodash.isequal');
|
|
2
|
+
import { BaseResolver, resolveDocument, makeRefId, makeDocumentFromString } from './resolve';
|
|
3
|
+
import { normalizeVisitors } from './visitors';
|
|
4
|
+
import { normalizeTypes } from './types';
|
|
5
|
+
import { walkDocument } from './walk';
|
|
2
6
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from './resolve';
|
|
10
|
-
import { Oas3Rule, normalizeVisitors, Oas3Visitor, Oas2Visitor } from './visitors';
|
|
11
|
-
import { NormalizedNodeType, normalizeTypes, NodeType } from './types';
|
|
12
|
-
import { WalkContext, walkDocument, UserContext, ResolveResult, NormalizedProblem } from './walk';
|
|
13
|
-
import { detectSpec, getTypes, getMajorSpecVersion, SpecMajorVersion } from './oas-types';
|
|
7
|
+
detectSpec,
|
|
8
|
+
getTypes,
|
|
9
|
+
getMajorSpecVersion,
|
|
10
|
+
SpecMajorVersion,
|
|
11
|
+
SpecVersion,
|
|
12
|
+
} from './oas-types';
|
|
14
13
|
import { isAbsoluteUrl, isRef, Location, refBaseName } from './ref-utils';
|
|
15
14
|
import { initRules } from './config/rules';
|
|
16
15
|
import { reportUnresolvedRef } from './rules/no-unresolved-refs';
|
|
17
16
|
import { isPlainObject, isTruthy } from './utils';
|
|
18
|
-
import { OasRef } from './typings/openapi';
|
|
19
17
|
import { isRedoclyRegistryURL } from './redocly';
|
|
20
18
|
import { RemoveUnusedComponents as RemoveUnusedComponentsOas2 } from './decorators/oas2/remove-unused-components';
|
|
21
19
|
import { RemoveUnusedComponents as RemoveUnusedComponentsOas3 } from './decorators/oas3/remove-unused-components';
|
|
20
|
+
import { ConfigTypes } from './types/redocly-yaml';
|
|
22
21
|
|
|
22
|
+
import type { Oas3Rule, Oas3Visitor, Oas2Visitor } from './visitors';
|
|
23
|
+
import type { NormalizedNodeType, NodeType } from './types';
|
|
24
|
+
import type { WalkContext, UserContext, ResolveResult, NormalizedProblem } from './walk';
|
|
23
25
|
import type { Config, StyleguideConfig } from './config';
|
|
26
|
+
import type { OasRef } from './typings/openapi';
|
|
27
|
+
import type { Document, ResolvedRefMap } from './resolve';
|
|
24
28
|
|
|
25
29
|
export type Oas3RuleSet = Record<string, Oas3Rule>;
|
|
26
30
|
|
|
@@ -33,12 +37,50 @@ export type BundleOptions = {
|
|
|
33
37
|
externalRefResolver?: BaseResolver;
|
|
34
38
|
config: Config;
|
|
35
39
|
dereference?: boolean;
|
|
36
|
-
base?: string;
|
|
40
|
+
base?: string | null;
|
|
37
41
|
skipRedoclyRegistryRefs?: boolean;
|
|
38
42
|
removeUnusedComponents?: boolean;
|
|
39
43
|
keepUrlRefs?: boolean;
|
|
40
44
|
};
|
|
41
45
|
|
|
46
|
+
export async function bundleConfig(document: Document, resolvedRefMap: ResolvedRefMap) {
|
|
47
|
+
const types = normalizeTypes(ConfigTypes);
|
|
48
|
+
|
|
49
|
+
const ctx: BundleContext = {
|
|
50
|
+
problems: [],
|
|
51
|
+
oasVersion: SpecVersion.OAS3_0,
|
|
52
|
+
refTypes: new Map<string, NormalizedNodeType>(),
|
|
53
|
+
visitorsData: {},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const bundleVisitor = normalizeVisitors(
|
|
57
|
+
[
|
|
58
|
+
{
|
|
59
|
+
severity: 'error',
|
|
60
|
+
ruleId: 'configBundler',
|
|
61
|
+
visitor: {
|
|
62
|
+
ref: {
|
|
63
|
+
leave(node: OasRef, ctx: UserContext, resolved: ResolveResult<any>) {
|
|
64
|
+
replaceRef(node, resolved, ctx);
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
types
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
walkDocument({
|
|
74
|
+
document,
|
|
75
|
+
rootType: types.ConfigRoot,
|
|
76
|
+
normalizedVisitors: bundleVisitor,
|
|
77
|
+
resolvedRefMap,
|
|
78
|
+
ctx,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return document.parsed ?? {};
|
|
82
|
+
}
|
|
83
|
+
|
|
42
84
|
export async function bundle(
|
|
43
85
|
opts: {
|
|
44
86
|
ref?: string;
|
|
@@ -254,6 +296,18 @@ export function mapTypeToComponent(typeName: string, version: SpecMajorVersion)
|
|
|
254
296
|
}
|
|
255
297
|
}
|
|
256
298
|
|
|
299
|
+
function replaceRef(ref: OasRef, resolved: ResolveResult<any>, ctx: UserContext) {
|
|
300
|
+
if (!isPlainObject(resolved.node)) {
|
|
301
|
+
ctx.parent[ctx.key] = resolved.node;
|
|
302
|
+
} else {
|
|
303
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
304
|
+
// @ts-ignore
|
|
305
|
+
delete ref.$ref;
|
|
306
|
+
const obj = Object.assign({}, resolved.node, ref);
|
|
307
|
+
Object.assign(ref, obj); // assign ref itself again so ref fields take precedence
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
257
311
|
// function oas3Move
|
|
258
312
|
|
|
259
313
|
function makeBundleVisitor(
|
|
@@ -351,19 +405,6 @@ function makeBundleVisitor(
|
|
|
351
405
|
});
|
|
352
406
|
}
|
|
353
407
|
|
|
354
|
-
function replaceRef(ref: OasRef, resolved: ResolveResult<any>, ctx: UserContext) {
|
|
355
|
-
if (!isPlainObject(resolved.node)) {
|
|
356
|
-
ctx.parent[ctx.key] = resolved.node;
|
|
357
|
-
} else {
|
|
358
|
-
// TODO: why $ref isn't optional in OasRef?
|
|
359
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
360
|
-
// @ts-ignore
|
|
361
|
-
delete ref.$ref;
|
|
362
|
-
const obj = Object.assign({}, resolved.node, ref);
|
|
363
|
-
Object.assign(ref, obj); // assign ref itself again so ref fields take precedence
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
408
|
function saveComponent(
|
|
368
409
|
componentType: string,
|
|
369
410
|
target: { node: any; location: Location },
|
|
@@ -21,6 +21,7 @@ exports[`resolveConfig should ignore minimal from the root and read local file 1
|
|
|
21
21
|
"oas3_0Decorators": {},
|
|
22
22
|
"oas3_0Preprocessors": {},
|
|
23
23
|
"oas3_0Rules": {
|
|
24
|
+
"array-parameter-serialization": "off",
|
|
24
25
|
"boolean-parameter-prefixes": "error",
|
|
25
26
|
"component-name-unique": "off",
|
|
26
27
|
"no-empty-servers": "error",
|
|
@@ -40,6 +41,7 @@ exports[`resolveConfig should ignore minimal from the root and read local file 1
|
|
|
40
41
|
"oas3_1Decorators": {},
|
|
41
42
|
"oas3_1Preprocessors": {},
|
|
42
43
|
"oas3_1Rules": {
|
|
44
|
+
"array-parameter-serialization": "off",
|
|
43
45
|
"boolean-parameter-prefixes": "error",
|
|
44
46
|
"component-name-unique": "off",
|
|
45
47
|
"no-empty-servers": "error",
|
|
@@ -125,6 +127,7 @@ exports[`resolveStyleguideConfig should resolve extends with local file config w
|
|
|
125
127
|
"oas3_0Decorators": {},
|
|
126
128
|
"oas3_0Preprocessors": {},
|
|
127
129
|
"oas3_0Rules": {
|
|
130
|
+
"array-parameter-serialization": "off",
|
|
128
131
|
"boolean-parameter-prefixes": "error",
|
|
129
132
|
"component-name-unique": "off",
|
|
130
133
|
"no-empty-servers": "error",
|
|
@@ -144,6 +147,7 @@ exports[`resolveStyleguideConfig should resolve extends with local file config w
|
|
|
144
147
|
"oas3_1Decorators": {},
|
|
145
148
|
"oas3_1Preprocessors": {},
|
|
146
149
|
"oas3_1Rules": {
|
|
150
|
+
"array-parameter-serialization": "off",
|
|
147
151
|
"boolean-parameter-prefixes": "error",
|
|
148
152
|
"component-name-unique": "off",
|
|
149
153
|
"no-empty-servers": "error",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
title: 1 # fail
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { loadConfig, findConfig, getConfig, createConfig } from '../load';
|
|
2
2
|
import { RedoclyClient } from '../../redocly';
|
|
3
|
-
import { RuleConfig, FlatRawConfig } from './../types';
|
|
4
3
|
import { Config } from '../config';
|
|
4
|
+
import { lintConfig } from '../../lint';
|
|
5
|
+
import { replaceSourceWithRef } from '../../../__tests__/utils';
|
|
6
|
+
import type { RuleConfig, FlatRawConfig } from './../types';
|
|
7
|
+
import type { NormalizedProblem } from '../../walk';
|
|
5
8
|
|
|
6
9
|
const fs = require('fs');
|
|
7
10
|
const path = require('path');
|
|
@@ -91,6 +94,82 @@ describe('getConfig', () => {
|
|
|
91
94
|
it('should return empty object if there is no configPath and config file is not found', () => {
|
|
92
95
|
expect(getConfig()).toEqual(Promise.resolve({}));
|
|
93
96
|
});
|
|
97
|
+
|
|
98
|
+
it('should resolve refs in config', async () => {
|
|
99
|
+
let problems: NormalizedProblem[];
|
|
100
|
+
const result = await getConfig({
|
|
101
|
+
configPath: path.join(__dirname, './fixtures/resolve-refs-in-config/config-with-refs.yaml'),
|
|
102
|
+
processRawConfig: async (config, resolvedRefMap) => {
|
|
103
|
+
problems = await lintConfig({
|
|
104
|
+
document: config,
|
|
105
|
+
severity: 'warn',
|
|
106
|
+
resolvedRefMap,
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
expect(result).toEqual({
|
|
111
|
+
seo: {
|
|
112
|
+
title: 1,
|
|
113
|
+
},
|
|
114
|
+
styleguide: {
|
|
115
|
+
rules: {
|
|
116
|
+
'info-license': 'error',
|
|
117
|
+
'non-existing-rule': 'warn',
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
expect(replaceSourceWithRef(problems!, __dirname)).toMatchInlineSnapshot(`
|
|
122
|
+
[
|
|
123
|
+
{
|
|
124
|
+
"from": {
|
|
125
|
+
"pointer": "#/seo",
|
|
126
|
+
"source": "fixtures/resolve-refs-in-config/config-with-refs.yaml",
|
|
127
|
+
},
|
|
128
|
+
"location": [
|
|
129
|
+
{
|
|
130
|
+
"pointer": "#/title",
|
|
131
|
+
"reportOnKey": false,
|
|
132
|
+
"source": "fixtures/resolve-refs-in-config/seo.yaml",
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
"message": "Expected type \`string\` but got \`integer\`.",
|
|
136
|
+
"ruleId": "configuration spec",
|
|
137
|
+
"severity": "warn",
|
|
138
|
+
"suggest": [],
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"from": {
|
|
142
|
+
"pointer": "#/rules",
|
|
143
|
+
"source": "fixtures/resolve-refs-in-config/config-with-refs.yaml",
|
|
144
|
+
},
|
|
145
|
+
"location": [
|
|
146
|
+
{
|
|
147
|
+
"pointer": "#/non-existing-rule",
|
|
148
|
+
"reportOnKey": true,
|
|
149
|
+
"source": "fixtures/resolve-refs-in-config/rules.yaml",
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
"message": "Property \`non-existing-rule\` is not expected here.",
|
|
153
|
+
"ruleId": "configuration spec",
|
|
154
|
+
"severity": "warn",
|
|
155
|
+
"suggest": [],
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"location": [
|
|
159
|
+
{
|
|
160
|
+
"pointer": "#/theme",
|
|
161
|
+
"reportOnKey": false,
|
|
162
|
+
"source": "fixtures/resolve-refs-in-config/config-with-refs.yaml",
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
"message": "Can't resolve $ref: ENOENT: no such file or directory 'fixtures/resolve-refs-in-config/wrong-ref.yaml'",
|
|
166
|
+
"ruleId": "configuration no-unresolved-refs",
|
|
167
|
+
"severity": "warn",
|
|
168
|
+
"suggest": [],
|
|
169
|
+
},
|
|
170
|
+
]
|
|
171
|
+
`);
|
|
172
|
+
});
|
|
94
173
|
});
|
|
95
174
|
|
|
96
175
|
describe('createConfig', () => {
|
package/src/config/all.ts
CHANGED
|
@@ -78,6 +78,7 @@ const all: PluginStyleguideConfig<'built-in'> = {
|
|
|
78
78
|
'component-name-unique': 'error',
|
|
79
79
|
'response-contains-property': 'error',
|
|
80
80
|
'spec-components-invalid-map-name': 'error',
|
|
81
|
+
'array-parameter-serialization': 'error',
|
|
81
82
|
},
|
|
82
83
|
oas3_1Rules: {
|
|
83
84
|
'no-invalid-media-type-examples': 'error',
|
|
@@ -101,6 +102,7 @@ const all: PluginStyleguideConfig<'built-in'> = {
|
|
|
101
102
|
'component-name-unique': 'error',
|
|
102
103
|
'response-contains-property': 'error',
|
|
103
104
|
'spec-components-invalid-map-name': 'error',
|
|
105
|
+
'array-parameter-serialization': 'error',
|
|
104
106
|
},
|
|
105
107
|
async2Rules: {
|
|
106
108
|
'channels-kebab-case': 'error',
|