@redocly/openapi-core 1.0.0-beta.111 → 1.0.0-beta.112
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/lib/config/all.js +0 -1
- package/lib/config/config-resolvers.js +19 -12
- package/lib/config/load.d.ts +1 -1
- package/lib/config/load.js +5 -5
- package/lib/config/minimal.js +0 -1
- package/lib/config/recommended.js +0 -1
- package/lib/rules/common/assertions/asserts.d.ts +22 -5
- package/lib/rules/common/assertions/asserts.js +25 -0
- package/lib/rules/common/assertions/index.d.ts +27 -2
- package/lib/rules/common/assertions/index.js +6 -29
- package/lib/rules/common/assertions/utils.d.ts +7 -14
- package/lib/rules/common/assertions/utils.js +129 -97
- package/lib/rules/oas2/index.d.ts +0 -1
- package/lib/rules/oas2/index.js +0 -2
- package/lib/rules/oas3/index.js +0 -2
- package/lib/types/redocly-yaml.js +44 -27
- package/lib/utils.d.ts +1 -0
- package/lib/utils.js +7 -1
- package/lib/visitors.d.ts +2 -1
- package/lib/visitors.js +1 -0
- package/lib/walk.js +7 -1
- package/package.json +1 -1
- package/src/__tests__/lint.test.ts +24 -5
- package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +1 -3
- package/src/config/__tests__/config-resolvers.test.ts +5 -5
- package/src/config/__tests__/fixtures/load-redocly.yaml +4 -0
- package/src/config/__tests__/fixtures/resolve-config/local-config-with-custom-function.yaml +6 -4
- package/src/config/__tests__/load.test.ts +4 -1
- package/src/config/all.ts +0 -1
- package/src/config/config-resolvers.ts +42 -19
- package/src/config/load.ts +8 -5
- package/src/config/minimal.ts +0 -1
- package/src/config/recommended.ts +0 -1
- package/src/rules/common/assertions/__tests__/asserts.test.ts +7 -3
- package/src/rules/common/assertions/__tests__/index.test.ts +41 -20
- package/src/rules/common/assertions/__tests__/utils.test.ts +43 -17
- package/src/rules/common/assertions/asserts.ts +60 -8
- package/src/rules/common/assertions/index.ts +36 -46
- package/src/rules/common/assertions/utils.ts +204 -127
- package/src/rules/oas2/index.ts +0 -2
- package/src/rules/oas3/index.ts +0 -2
- package/src/types/redocly-yaml.ts +44 -29
- package/src/utils.ts +5 -0
- package/src/visitors.ts +7 -1
- package/src/walk.ts +8 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/lib/rules/common/info-description.d.ts +0 -2
- package/lib/rules/common/info-description.js +0 -12
- package/src/rules/common/__tests__/info-description.test.ts +0 -102
- package/src/rules/common/info-description.ts +0 -10
|
@@ -5,7 +5,6 @@ const _1 = require(".");
|
|
|
5
5
|
const utils_1 = require("../utils");
|
|
6
6
|
const builtInRulesList = [
|
|
7
7
|
'spec',
|
|
8
|
-
'info-description',
|
|
9
8
|
'info-contact',
|
|
10
9
|
'info-license',
|
|
11
10
|
'info-license-url',
|
|
@@ -140,11 +139,7 @@ const RootConfigStyleguide = {
|
|
|
140
139
|
} }, ConfigStyleguide.properties),
|
|
141
140
|
};
|
|
142
141
|
const ConfigRoot = {
|
|
143
|
-
properties: Object.assign(Object.assign({ organization: { type: 'string' }, apis: 'ConfigApis',
|
|
144
|
-
type: 'object',
|
|
145
|
-
properties: {},
|
|
146
|
-
additionalProperties: { properties: { type: 'string' } },
|
|
147
|
-
} }, RootConfigStyleguide.properties), { styleguide: 'RootConfigStyleguide', lint: 'RootConfigStyleguide', 'features.openapi': 'ConfigReferenceDocs', referenceDocs: 'ConfigReferenceDocs', 'features.mockServer': 'ConfigMockServer', region: { enum: ['us', 'eu'] }, resolve: {
|
|
142
|
+
properties: Object.assign(Object.assign({ organization: { type: 'string' }, apis: 'ConfigApis' }, RootConfigStyleguide.properties), { 'features.openapi': 'ConfigReferenceDocs', 'features.mockServer': 'ConfigMockServer', region: { enum: ['us', 'eu'] }, resolve: {
|
|
148
143
|
properties: {
|
|
149
144
|
http: 'ConfigHTTP',
|
|
150
145
|
doNotResolveExamples: { type: 'boolean' },
|
|
@@ -209,16 +204,9 @@ const ObjectRule = {
|
|
|
209
204
|
additionalProperties: {},
|
|
210
205
|
required: ['severity'],
|
|
211
206
|
};
|
|
212
|
-
const
|
|
207
|
+
const AssertionDefinitionSubject = {
|
|
213
208
|
properties: {
|
|
214
|
-
|
|
215
|
-
if (Array.isArray(value)) {
|
|
216
|
-
return { type: 'array', items: { enum: nodeTypesList } };
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
return { enum: nodeTypesList };
|
|
220
|
-
}
|
|
221
|
-
},
|
|
209
|
+
type: { enum: nodeTypesList },
|
|
222
210
|
property: (value) => {
|
|
223
211
|
if (Array.isArray(value)) {
|
|
224
212
|
return { type: 'array', items: { type: 'string' } };
|
|
@@ -230,10 +218,14 @@ const Assert = {
|
|
|
230
218
|
return { type: 'string' };
|
|
231
219
|
}
|
|
232
220
|
},
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
221
|
+
filterInParentKeys: { type: 'array', items: { type: 'string' } },
|
|
222
|
+
filterOutParentKeys: { type: 'array', items: { type: 'string' } },
|
|
223
|
+
matchParentKeys: { type: 'string' },
|
|
224
|
+
},
|
|
225
|
+
required: ['type'],
|
|
226
|
+
};
|
|
227
|
+
const AssertionDefinitionAssertions = {
|
|
228
|
+
properties: {
|
|
237
229
|
enum: { type: 'array', items: { type: 'string' } },
|
|
238
230
|
pattern: { type: 'string' },
|
|
239
231
|
casing: {
|
|
@@ -253,26 +245,49 @@ const Assert = {
|
|
|
253
245
|
requireAny: { type: 'array', items: { type: 'string' } },
|
|
254
246
|
disallowed: { type: 'array', items: { type: 'string' } },
|
|
255
247
|
defined: { type: 'boolean' },
|
|
256
|
-
undefined: { type: 'boolean' },
|
|
248
|
+
// undefined: { type: 'boolean' }, // TODO: Remove `undefined` assertion from codebase overall
|
|
257
249
|
nonEmpty: { type: 'boolean' },
|
|
258
250
|
minLength: { type: 'integer' },
|
|
259
251
|
maxLength: { type: 'integer' },
|
|
260
252
|
ref: (value) => typeof value === 'string' ? { type: 'string' } : { type: 'boolean' },
|
|
253
|
+
const: (value) => {
|
|
254
|
+
if (typeof value === 'string') {
|
|
255
|
+
return { type: 'string' };
|
|
256
|
+
}
|
|
257
|
+
if (typeof value === 'number') {
|
|
258
|
+
return { type: 'number' };
|
|
259
|
+
}
|
|
260
|
+
if (typeof value === 'boolean') {
|
|
261
|
+
return { type: 'boolean' };
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
},
|
|
261
267
|
},
|
|
262
268
|
additionalProperties: (_value, key) => {
|
|
263
269
|
if (/^\w+\/\w+$/.test(key))
|
|
264
270
|
return { type: 'object' };
|
|
265
271
|
return;
|
|
266
272
|
},
|
|
267
|
-
required: ['subject'],
|
|
268
273
|
};
|
|
269
|
-
const
|
|
274
|
+
const AssertDefinition = {
|
|
270
275
|
properties: {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
excludeParentKeys: { type: 'array', items: { type: 'string' } },
|
|
276
|
+
subject: 'AssertionDefinitionSubject',
|
|
277
|
+
assertions: 'AssertionDefinitionAssertions',
|
|
274
278
|
},
|
|
275
|
-
required: ['
|
|
279
|
+
required: ['subject', 'assertions'],
|
|
280
|
+
};
|
|
281
|
+
const Assert = {
|
|
282
|
+
properties: {
|
|
283
|
+
subject: 'AssertionDefinitionSubject',
|
|
284
|
+
assertions: 'AssertionDefinitionAssertions',
|
|
285
|
+
where: _1.listOf('AssertDefinition'),
|
|
286
|
+
message: { type: 'string' },
|
|
287
|
+
suggest: { type: 'array', items: { type: 'string' } },
|
|
288
|
+
severity: { enum: ['error', 'warn', 'off'] },
|
|
289
|
+
},
|
|
290
|
+
required: ['subject', 'assertions'],
|
|
276
291
|
};
|
|
277
292
|
const ConfigLanguage = {
|
|
278
293
|
properties: {
|
|
@@ -792,7 +807,7 @@ exports.ConfigTypes = {
|
|
|
792
807
|
ConfigSidebarLinks,
|
|
793
808
|
CommonConfigSidebarLinks,
|
|
794
809
|
ConfigTheme,
|
|
795
|
-
|
|
810
|
+
AssertDefinition,
|
|
796
811
|
ThemeColors,
|
|
797
812
|
CommonThemeColors,
|
|
798
813
|
BorderThemeColors,
|
|
@@ -839,4 +854,6 @@ exports.ConfigTypes = {
|
|
|
839
854
|
Sidebar,
|
|
840
855
|
Heading,
|
|
841
856
|
Typography,
|
|
857
|
+
AssertionDefinitionAssertions,
|
|
858
|
+
AssertionDefinitionSubject,
|
|
842
859
|
};
|
package/lib/utils.d.ts
CHANGED
|
@@ -46,4 +46,5 @@ export declare function showErrorForDeprecatedField(deprecatedField: string, upd
|
|
|
46
46
|
export declare type Falsy = undefined | null | false | '' | 0;
|
|
47
47
|
export declare function isTruthy<Truthy>(value: Truthy | Falsy): value is Truthy;
|
|
48
48
|
export declare function identity<T>(value: T): T;
|
|
49
|
+
export declare function keysOf<T>(obj: T): (keyof T)[];
|
|
49
50
|
export declare function pickDefined<T extends Record<string, unknown>>(obj?: T): Record<string, unknown> | undefined;
|
package/lib/utils.js
CHANGED
|
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.pickDefined = exports.identity = exports.isTruthy = exports.showErrorForDeprecatedField = exports.showWarningForDeprecatedField = exports.doesYamlFileExist = exports.isCustomRuleId = exports.getMatchingStatusCodeRange = exports.assignExisting = exports.isNotString = exports.isString = exports.isNotEmptyObject = exports.slash = exports.isPathParameter = exports.readFileAsStringSync = exports.isSingular = exports.validateMimeTypeOAS3 = exports.validateMimeType = exports.splitCamelCaseIntoWords = exports.omitObjectProps = exports.pickObjectProps = exports.readFileFromUrl = exports.isEmptyArray = exports.isEmptyObject = exports.isPlainObject = exports.isDefined = exports.loadYaml = exports.popStack = exports.pushStack = exports.stringifyYaml = exports.parseYaml = void 0;
|
|
12
|
+
exports.pickDefined = exports.keysOf = exports.identity = exports.isTruthy = exports.showErrorForDeprecatedField = exports.showWarningForDeprecatedField = exports.doesYamlFileExist = exports.isCustomRuleId = exports.getMatchingStatusCodeRange = exports.assignExisting = exports.isNotString = exports.isString = exports.isNotEmptyObject = exports.slash = exports.isPathParameter = exports.readFileAsStringSync = exports.isSingular = exports.validateMimeTypeOAS3 = exports.validateMimeType = exports.splitCamelCaseIntoWords = exports.omitObjectProps = exports.pickObjectProps = exports.readFileFromUrl = exports.isEmptyArray = exports.isEmptyObject = exports.isPlainObject = exports.isDefined = exports.loadYaml = exports.popStack = exports.pushStack = exports.stringifyYaml = exports.parseYaml = void 0;
|
|
13
13
|
const fs = require("fs");
|
|
14
14
|
const path_1 = require("path");
|
|
15
15
|
const minimatch = require("minimatch");
|
|
@@ -205,6 +205,12 @@ function identity(value) {
|
|
|
205
205
|
return value;
|
|
206
206
|
}
|
|
207
207
|
exports.identity = identity;
|
|
208
|
+
function keysOf(obj) {
|
|
209
|
+
if (!obj)
|
|
210
|
+
return [];
|
|
211
|
+
return Object.keys(obj);
|
|
212
|
+
}
|
|
213
|
+
exports.keysOf = keysOf;
|
|
208
214
|
function pickDefined(obj) {
|
|
209
215
|
if (!obj)
|
|
210
216
|
return undefined;
|
package/lib/visitors.d.ts
CHANGED
|
@@ -4,11 +4,12 @@ import type { NormalizedNodeType } from './types';
|
|
|
4
4
|
import type { Stack } from './utils';
|
|
5
5
|
import type { UserContext, ResolveResult, ProblemSeverity } from './walk';
|
|
6
6
|
import type { Location } from './ref-utils';
|
|
7
|
+
export declare type SkipFunctionContext = Pick<UserContext, 'location' | 'rawNode' | 'resolve' | 'rawLocation'>;
|
|
7
8
|
export declare type VisitFunction<T> = (node: T, ctx: UserContext & {
|
|
8
9
|
ignoreNextVisitorsOnNode: () => void;
|
|
9
10
|
}, parents?: any, context?: any) => void;
|
|
10
11
|
declare type VisitRefFunction = (node: OasRef, ctx: UserContext, resolved: ResolveResult<any>) => void;
|
|
11
|
-
declare type SkipFunction<T> = (node: T, key: string | number) => boolean;
|
|
12
|
+
declare type SkipFunction<T> = (node: T, key: string | number, ctx: SkipFunctionContext) => boolean;
|
|
12
13
|
declare type VisitObject<T> = {
|
|
13
14
|
enter?: VisitFunction<T>;
|
|
14
15
|
leave?: VisitFunction<T>;
|
package/lib/visitors.js
CHANGED
package/lib/walk.js
CHANGED
|
@@ -112,7 +112,13 @@ function walkDocument(opts) {
|
|
|
112
112
|
location: resolvedLocation,
|
|
113
113
|
nextLevelTypeActivated: null,
|
|
114
114
|
withParentNode: (_g = (_f = context.parent) === null || _f === void 0 ? void 0 : _f.activatedOn) === null || _g === void 0 ? void 0 : _g.value.node,
|
|
115
|
-
skipped: (_k = (((_j = (_h = context.parent) === null || _h === void 0 ? void 0 : _h.activatedOn) === null || _j === void 0 ? void 0 : _j.value.skipped) ||
|
|
115
|
+
skipped: (_k = (((_j = (_h = context.parent) === null || _h === void 0 ? void 0 : _h.activatedOn) === null || _j === void 0 ? void 0 : _j.value.skipped) ||
|
|
116
|
+
(skip === null || skip === void 0 ? void 0 : skip(resolvedNode, key, {
|
|
117
|
+
location,
|
|
118
|
+
rawLocation,
|
|
119
|
+
resolve,
|
|
120
|
+
rawNode: node,
|
|
121
|
+
})))) !== null && _k !== void 0 ? _k : false,
|
|
116
122
|
};
|
|
117
123
|
context.activatedOn = utils_1.pushStack(context.activatedOn, activatedOn);
|
|
118
124
|
let ctx = context.parent;
|
package/package.json
CHANGED
|
@@ -61,11 +61,13 @@ describe('lint', () => {
|
|
|
61
61
|
path-http-verbs-order: error
|
|
62
62
|
boolean-parameter-prefixes: off
|
|
63
63
|
assert/operation-summary-length:
|
|
64
|
-
subject:
|
|
65
|
-
|
|
64
|
+
subject:
|
|
65
|
+
type: Operation
|
|
66
|
+
property: summary
|
|
66
67
|
message: Operation summary should start with an active verb
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
assertions:
|
|
69
|
+
local/checkWordsCount:
|
|
70
|
+
min: 3
|
|
69
71
|
features.openapi:
|
|
70
72
|
showConsole: true
|
|
71
73
|
layout:
|
|
@@ -247,7 +249,24 @@ describe('lint', () => {
|
|
|
247
249
|
);
|
|
248
250
|
const results = await lintConfig({ document });
|
|
249
251
|
|
|
250
|
-
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
252
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
253
|
+
Array [
|
|
254
|
+
Object {
|
|
255
|
+
"from": undefined,
|
|
256
|
+
"location": Array [
|
|
257
|
+
Object {
|
|
258
|
+
"pointer": "#/referenceDocs",
|
|
259
|
+
"reportOnKey": true,
|
|
260
|
+
"source": "",
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
"message": "Property \`referenceDocs\` is not expected here.",
|
|
264
|
+
"ruleId": "configuration spec",
|
|
265
|
+
"severity": "error",
|
|
266
|
+
"suggest": Array [],
|
|
267
|
+
},
|
|
268
|
+
]
|
|
269
|
+
`);
|
|
251
270
|
});
|
|
252
271
|
|
|
253
272
|
it("'plugins' shouldn't be allowed in 'apis'", async () => {
|
|
@@ -39,7 +39,6 @@ Object {
|
|
|
39
39
|
"assertions": "warn",
|
|
40
40
|
"boolean-parameter-prefixes": "error",
|
|
41
41
|
"info-contact": "off",
|
|
42
|
-
"info-description": "warn",
|
|
43
42
|
"info-license": "warn",
|
|
44
43
|
"info-license-url": "warn",
|
|
45
44
|
"local/operation-id-not-test": "error",
|
|
@@ -73,7 +72,7 @@ Object {
|
|
|
73
72
|
}
|
|
74
73
|
`;
|
|
75
74
|
|
|
76
|
-
exports[`resolveStyleguideConfig should resolve extends with local file config
|
|
75
|
+
exports[`resolveStyleguideConfig should resolve extends with local file config which contains path to nested config 1`] = `
|
|
77
76
|
Object {
|
|
78
77
|
"decorators": Object {},
|
|
79
78
|
"doNotResolveExamples": undefined,
|
|
@@ -129,7 +128,6 @@ Object {
|
|
|
129
128
|
],
|
|
130
129
|
"boolean-parameter-prefixes": "error",
|
|
131
130
|
"info-contact": "off",
|
|
132
|
-
"info-description": "warn",
|
|
133
131
|
"info-license": "warn",
|
|
134
132
|
"info-license-url": "warn",
|
|
135
133
|
"local/operation-id-not-test": "error",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { colorize } from '../../logger';
|
|
2
|
-
import { asserts } from '../../rules/common/assertions/asserts';
|
|
2
|
+
import { Asserts, asserts } from '../../rules/common/assertions/asserts';
|
|
3
3
|
import { resolveStyleguideConfig, resolveApis, resolveConfig } from '../config-resolvers';
|
|
4
4
|
const path = require('path');
|
|
5
5
|
|
|
@@ -100,7 +100,7 @@ describe('resolveStyleguideConfig', () => {
|
|
|
100
100
|
}).toThrow('Circular dependency in config file');
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
-
it('should resolve extends with local file config
|
|
103
|
+
it('should resolve extends with local file config which contains path to nested config', async () => {
|
|
104
104
|
const styleguideConfig = {
|
|
105
105
|
extends: ['local-config-with-file.yaml'],
|
|
106
106
|
};
|
|
@@ -143,7 +143,7 @@ describe('resolveStyleguideConfig', () => {
|
|
|
143
143
|
|
|
144
144
|
expect(plugins).toBeDefined();
|
|
145
145
|
expect(plugins?.length).toBe(2);
|
|
146
|
-
expect(asserts['test-plugin/checkWordsCount']).toBeDefined();
|
|
146
|
+
expect(asserts['test-plugin/checkWordsCount' as keyof Asserts]).toBeDefined();
|
|
147
147
|
});
|
|
148
148
|
|
|
149
149
|
it('should throw error when custom assertion load not exist plugin', async () => {
|
|
@@ -163,7 +163,7 @@ describe('resolveStyleguideConfig', () => {
|
|
|
163
163
|
);
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
expect(asserts['test-plugin/checkWordsCount']).toBeDefined();
|
|
166
|
+
expect(asserts['test-plugin/checkWordsCount' as keyof Asserts]).toBeDefined();
|
|
167
167
|
});
|
|
168
168
|
|
|
169
169
|
it('should correctly merge assertions from nested config', async () => {
|
|
@@ -197,7 +197,7 @@ describe('resolveStyleguideConfig', () => {
|
|
|
197
197
|
]);
|
|
198
198
|
});
|
|
199
199
|
|
|
200
|
-
it('should resolve extends with url file config
|
|
200
|
+
it('should resolve extends with url file config which contains path to nested config', async () => {
|
|
201
201
|
const styleguideConfig = {
|
|
202
202
|
// This points to ./fixtures/resolve-remote-configs/remote-config.yaml
|
|
203
203
|
extends: [
|
|
@@ -3,12 +3,14 @@ lint:
|
|
|
3
3
|
no-invalid-media-type-examples: warn
|
|
4
4
|
operation-4xx-response: off
|
|
5
5
|
assert/tag-description:
|
|
6
|
-
subject:
|
|
7
|
-
|
|
6
|
+
subject:
|
|
7
|
+
type: Tag
|
|
8
|
+
property: description
|
|
8
9
|
message: Tag description must have at least 3 words.
|
|
9
10
|
severity: error
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
assertions:
|
|
12
|
+
test-plugin/checkWordsCount:
|
|
13
|
+
min: 3
|
|
12
14
|
plugins:
|
|
13
15
|
- plugin.js
|
|
14
16
|
extends:
|
|
@@ -49,7 +49,10 @@ describe('loadConfig', () => {
|
|
|
49
49
|
|
|
50
50
|
it('should call callback if such passed', async () => {
|
|
51
51
|
const mockFn = jest.fn();
|
|
52
|
-
await loadConfig({
|
|
52
|
+
await loadConfig({
|
|
53
|
+
configPath: path.join(__dirname, './fixtures/load-redocly.yaml'),
|
|
54
|
+
processRawConfig: mockFn,
|
|
55
|
+
});
|
|
53
56
|
expect(mockFn).toHaveBeenCalled();
|
|
54
57
|
});
|
|
55
58
|
});
|
package/src/config/all.ts
CHANGED
|
@@ -22,10 +22,16 @@ import type {
|
|
|
22
22
|
DeprecatedInRawConfig,
|
|
23
23
|
} from './types';
|
|
24
24
|
import { isBrowser } from '../env';
|
|
25
|
-
import { isNotString, isString, isDefined, parseYaml } from '../utils';
|
|
25
|
+
import { isNotString, isString, isDefined, parseYaml, keysOf } from '../utils';
|
|
26
26
|
import { Config } from './config';
|
|
27
27
|
import { colorize, logger } from '../logger';
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
Asserts,
|
|
30
|
+
AssertionFn,
|
|
31
|
+
asserts,
|
|
32
|
+
buildAssertCustomFunction,
|
|
33
|
+
} from '../rules/common/assertions/asserts';
|
|
34
|
+
import type { Assertion, AssertionDefinition, RawAssertion } from '../rules/common/assertions';
|
|
29
35
|
|
|
30
36
|
export async function resolveConfig(rawConfig: RawConfig, configPath?: string): Promise<Config> {
|
|
31
37
|
if (rawConfig.styleguide?.extends?.some(isNotString)) {
|
|
@@ -409,26 +415,17 @@ function groupStyleguideAssertionRules({
|
|
|
409
415
|
const transformedRules: Record<string, RuleConfig> = {};
|
|
410
416
|
|
|
411
417
|
// Collect assertion rules
|
|
412
|
-
const assertions = [];
|
|
418
|
+
const assertions: Assertion[] = [];
|
|
413
419
|
for (const [ruleKey, rule] of Object.entries(rules)) {
|
|
414
420
|
if (ruleKey.startsWith('assert/') && typeof rule === 'object' && rule !== null) {
|
|
415
|
-
const assertion = rule;
|
|
421
|
+
const assertion = rule as RawAssertion;
|
|
422
|
+
|
|
416
423
|
if (plugins) {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
throw Error(colorize.red(`Plugin ${colorize.blue(pluginId)} isn't found.`));
|
|
423
|
-
}
|
|
424
|
-
if (!plugin.assertions || !plugin.assertions[fn]) {
|
|
425
|
-
throw Error(
|
|
426
|
-
`Plugin ${colorize.red(
|
|
427
|
-
pluginId
|
|
428
|
-
)} doesn't export assertions function with name ${colorize.red(fn)}.`
|
|
429
|
-
);
|
|
430
|
-
}
|
|
431
|
-
asserts[field] = buildAssertCustomFunction(plugin.assertions[fn]);
|
|
424
|
+
registerCustomAssertions(plugins, assertion);
|
|
425
|
+
|
|
426
|
+
// We may have custom assertion inside where block
|
|
427
|
+
for (const context of assertion.where || []) {
|
|
428
|
+
registerCustomAssertions(plugins, context);
|
|
432
429
|
}
|
|
433
430
|
}
|
|
434
431
|
assertions.push({
|
|
@@ -446,3 +443,29 @@ function groupStyleguideAssertionRules({
|
|
|
446
443
|
|
|
447
444
|
return transformedRules;
|
|
448
445
|
}
|
|
446
|
+
|
|
447
|
+
function registerCustomAssertions(plugins: Plugin[], assertion: AssertionDefinition) {
|
|
448
|
+
for (const field of keysOf(assertion.assertions)) {
|
|
449
|
+
const [pluginId, fn] = field.split('/');
|
|
450
|
+
|
|
451
|
+
if (!pluginId || !fn) continue;
|
|
452
|
+
|
|
453
|
+
const plugin = plugins.find((plugin) => plugin.id === pluginId);
|
|
454
|
+
|
|
455
|
+
if (!plugin) {
|
|
456
|
+
throw Error(colorize.red(`Plugin ${colorize.blue(pluginId)} isn't found.`));
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (!plugin.assertions || !plugin.assertions[fn]) {
|
|
460
|
+
throw Error(
|
|
461
|
+
`Plugin ${colorize.red(
|
|
462
|
+
pluginId
|
|
463
|
+
)} doesn't export assertions function with name ${colorize.red(fn)}.`
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
(asserts as Asserts & { [name: string]: AssertionFn })[field] = buildAssertCustomFunction(
|
|
468
|
+
plugin.assertions[fn]
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
}
|
package/src/config/load.ts
CHANGED
|
@@ -71,11 +71,8 @@ export async function loadConfig(
|
|
|
71
71
|
} = {}
|
|
72
72
|
): Promise<Config> {
|
|
73
73
|
const { configPath = findConfig(), customExtends, processRawConfig, files, region } = options;
|
|
74
|
-
const config = await getConfig(configPath);
|
|
74
|
+
const config = await getConfig(configPath, processRawConfig);
|
|
75
75
|
const rawConfig = { ...config, files: files ?? config.files, region: region ?? config.region };
|
|
76
|
-
if (typeof processRawConfig === 'function') {
|
|
77
|
-
await processRawConfig(rawConfig);
|
|
78
|
-
}
|
|
79
76
|
|
|
80
77
|
const redoclyClient = new RedoclyClient();
|
|
81
78
|
const tokens = await redoclyClient.getTokens();
|
|
@@ -105,11 +102,17 @@ export function findConfig(dir?: string): string | undefined {
|
|
|
105
102
|
return existingConfigFiles[0];
|
|
106
103
|
}
|
|
107
104
|
|
|
108
|
-
export async function getConfig(
|
|
105
|
+
export async function getConfig(
|
|
106
|
+
configPath: string | undefined = findConfig(),
|
|
107
|
+
processRawConfig?: (rawConfig: RawConfig) => void | Promise<void>
|
|
108
|
+
): Promise<RawConfig> {
|
|
109
109
|
if (!configPath || !doesYamlFileExist(configPath)) return {};
|
|
110
110
|
try {
|
|
111
111
|
const rawConfig =
|
|
112
112
|
(await loadYaml<RawConfig & DeprecatedInRawConfig & FlatRawConfig>(configPath)) || {};
|
|
113
|
+
if (typeof processRawConfig === 'function') {
|
|
114
|
+
await processRawConfig(rawConfig);
|
|
115
|
+
}
|
|
113
116
|
return transformConfig(rawConfig);
|
|
114
117
|
} catch (e) {
|
|
115
118
|
throw new Error(`Error parsing config file at '${configPath}': ${e.message}`);
|
package/src/config/minimal.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Location } from '../../../../ref-utils';
|
|
2
2
|
import { Source } from '../../../../resolve';
|
|
3
|
-
import { asserts, buildAssertCustomFunction } from '../asserts';
|
|
3
|
+
import { Asserts, asserts, buildAssertCustomFunction } from '../asserts';
|
|
4
4
|
|
|
5
5
|
let baseLocation = new Location(jest.fn() as any as Source, 'pointer');
|
|
6
6
|
|
|
@@ -674,9 +674,13 @@ describe('oas3 assertions', () => {
|
|
|
674
674
|
}
|
|
675
675
|
return [];
|
|
676
676
|
});
|
|
677
|
-
asserts['local/customFn'] = buildAssertCustomFunction(customFn);
|
|
677
|
+
asserts['local/customFn' as keyof Asserts] = buildAssertCustomFunction(customFn);
|
|
678
678
|
expect(
|
|
679
|
-
asserts['local/customFn'
|
|
679
|
+
asserts['local/customFn' as keyof Asserts](
|
|
680
|
+
Object.keys(fakeNode),
|
|
681
|
+
{ word: 'foo' },
|
|
682
|
+
baseLocation
|
|
683
|
+
)
|
|
680
684
|
).toEqual([
|
|
681
685
|
{
|
|
682
686
|
message: 'First value should be foo',
|
|
@@ -1,61 +1,82 @@
|
|
|
1
|
-
import { Assertions } from '../.';
|
|
1
|
+
import { Assertion, Assertions } from '../.';
|
|
2
2
|
|
|
3
3
|
const opts = {
|
|
4
4
|
'0': {
|
|
5
|
-
subject:
|
|
6
|
-
|
|
5
|
+
subject: {
|
|
6
|
+
type: 'Operation',
|
|
7
|
+
property: 'summary',
|
|
8
|
+
},
|
|
7
9
|
description: 'example warn text',
|
|
8
10
|
severity: 'warn',
|
|
9
|
-
pattern: '/example/',
|
|
11
|
+
assertions: { pattern: '/example/' },
|
|
10
12
|
},
|
|
11
13
|
'1': {
|
|
12
|
-
subject:
|
|
13
|
-
|
|
14
|
+
subject: {
|
|
15
|
+
type: 'PathItem',
|
|
16
|
+
},
|
|
17
|
+
where: [
|
|
18
|
+
{
|
|
19
|
+
subject: { type: 'Operation', filterInParentKeys: ['post'], property: 'responses' },
|
|
20
|
+
assertions: { defined: true },
|
|
21
|
+
},
|
|
22
|
+
],
|
|
14
23
|
description: 'example warn text',
|
|
15
24
|
severity: 'warn',
|
|
16
|
-
mutuallyExclusive: ['summary', 'security'],
|
|
25
|
+
assertions: { mutuallyExclusive: ['summary', 'security'] },
|
|
17
26
|
},
|
|
18
27
|
'2': {
|
|
19
|
-
subject:
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
subject: { type: 'PathItem', property: 'tags' },
|
|
29
|
+
where: [
|
|
30
|
+
{ subject: { type: 'Operation', property: 'responses' }, assertions: { defined: true } },
|
|
31
|
+
],
|
|
22
32
|
description: 'example warn text',
|
|
23
33
|
severity: 'warn',
|
|
24
|
-
sortOrder: 'desc',
|
|
34
|
+
assertions: { sortOrder: 'desc' },
|
|
25
35
|
},
|
|
26
36
|
'3': {
|
|
27
|
-
subject:
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
subject: { type: 'Foo', property: 'test' },
|
|
38
|
+
where: [
|
|
39
|
+
{ subject: { type: 'Bar' }, assertions: {} },
|
|
40
|
+
{ subject: { type: 'Baz' }, assertions: {} },
|
|
41
|
+
],
|
|
30
42
|
description: 'example warn text',
|
|
31
43
|
severity: 'warn',
|
|
32
|
-
sortOrder: 'desc',
|
|
44
|
+
assertions: { sortOrder: 'desc' },
|
|
33
45
|
},
|
|
34
46
|
};
|
|
35
47
|
|
|
36
48
|
describe('Oas3 assertions', () => {
|
|
37
49
|
it('should return the right visitor structure', () => {
|
|
38
|
-
const visitors = Assertions(opts
|
|
50
|
+
const visitors = Assertions(opts as any);
|
|
39
51
|
expect(visitors).toMatchInlineSnapshot(`
|
|
40
52
|
Array [
|
|
41
53
|
Object {
|
|
42
|
-
"Operation":
|
|
54
|
+
"Operation": Object {
|
|
55
|
+
"enter": [Function],
|
|
56
|
+
},
|
|
43
57
|
},
|
|
44
58
|
Object {
|
|
45
59
|
"Operation": Object {
|
|
46
|
-
"PathItem":
|
|
60
|
+
"PathItem": Object {
|
|
61
|
+
"enter": [Function],
|
|
62
|
+
},
|
|
47
63
|
"skip": [Function],
|
|
48
64
|
},
|
|
49
65
|
},
|
|
50
66
|
Object {
|
|
51
67
|
"Operation": Object {
|
|
52
|
-
"PathItem":
|
|
68
|
+
"PathItem": Object {
|
|
69
|
+
"enter": [Function],
|
|
70
|
+
},
|
|
71
|
+
"skip": [Function],
|
|
53
72
|
},
|
|
54
73
|
},
|
|
55
74
|
Object {
|
|
56
75
|
"Bar": Object {
|
|
57
76
|
"Baz": Object {
|
|
58
|
-
"Foo":
|
|
77
|
+
"Foo": Object {
|
|
78
|
+
"enter": [Function],
|
|
79
|
+
},
|
|
59
80
|
},
|
|
60
81
|
},
|
|
61
82
|
},
|