@redocly/openapi-core 1.0.0-beta.123 → 1.0.0-beta.125
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/bundle.d.ts +7 -12
- package/lib/config/config.js +9 -4
- package/lib/config/types.d.ts +5 -1
- package/lib/format/format.js +16 -7
- package/lib/rules/common/assertions/asserts.d.ts +6 -2
- package/lib/rules/common/assertions/asserts.js +30 -20
- package/lib/rules/common/assertions/utils.d.ts +8 -0
- package/lib/rules/common/assertions/utils.js +28 -34
- package/lib/rules/common/required-string-property-missing-min-length.d.ts +2 -0
- package/lib/rules/common/required-string-property-missing-min-length.js +37 -0
- package/lib/rules/oas2/index.d.ts +1 -0
- package/lib/rules/oas2/index.js +2 -0
- package/lib/rules/oas3/index.js +2 -0
- package/lib/types/redocly-yaml.js +1 -0
- package/package.json +1 -1
- package/src/bundle.ts +11 -2
- package/src/config/__tests__/__snapshots__/config.test.ts.snap +144 -0
- package/src/config/__tests__/config.test.ts +25 -0
- package/src/config/__tests__/fixtures/ingore-file.ts +8 -0
- package/src/config/config.ts +14 -4
- package/src/config/types.ts +4 -1
- package/src/format/format.ts +17 -6
- package/src/rules/common/assertions/__tests__/asserts.test.ts +264 -178
- package/src/rules/common/assertions/__tests__/index.test.ts +1 -1
- package/src/rules/common/assertions/__tests__/utils.test.ts +122 -1
- package/src/rules/common/assertions/asserts.ts +59 -28
- package/src/rules/common/assertions/utils.ts +46 -44
- package/src/rules/common/required-string-property-missing-min-length.ts +44 -0
- package/src/rules/oas2/index.ts +2 -0
- package/src/rules/oas3/index.ts +2 -0
- package/src/types/redocly-yaml.ts +1 -0
- package/tsconfig.tsbuildinfo +1 -1
package/lib/bundle.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BaseResolver, Document } from './resolve';
|
|
2
2
|
import { Oas3Rule } from './visitors';
|
|
3
3
|
import { NormalizedNodeType, NodeType } from './types';
|
|
4
|
+
import { NormalizedProblem } from './walk';
|
|
4
5
|
import { OasMajorVersion } from './oas-types';
|
|
5
6
|
import type { Config, StyleguideConfig } from './config';
|
|
6
7
|
export declare type Oas3RuleSet = Record<string, Oas3Rule>;
|
|
@@ -19,14 +20,15 @@ export declare function bundle(opts: {
|
|
|
19
20
|
skipRedoclyRegistryRefs?: boolean;
|
|
20
21
|
removeUnusedComponents?: boolean;
|
|
21
22
|
keepUrlRefs?: boolean;
|
|
22
|
-
}): Promise<
|
|
23
|
+
}): Promise<BundleResult>;
|
|
24
|
+
export declare type BundleResult = {
|
|
23
25
|
bundle: Document;
|
|
24
|
-
problems:
|
|
26
|
+
problems: NormalizedProblem[];
|
|
25
27
|
fileDependencies: Set<string>;
|
|
26
28
|
rootType: NormalizedNodeType;
|
|
27
|
-
refTypes
|
|
29
|
+
refTypes?: Map<string, NormalizedNodeType>;
|
|
28
30
|
visitorsData: Record<string, Record<string, unknown>>;
|
|
29
|
-
}
|
|
31
|
+
};
|
|
30
32
|
export declare function bundleDocument(opts: {
|
|
31
33
|
document: Document;
|
|
32
34
|
config: StyleguideConfig;
|
|
@@ -36,12 +38,5 @@ export declare function bundleDocument(opts: {
|
|
|
36
38
|
skipRedoclyRegistryRefs?: boolean;
|
|
37
39
|
removeUnusedComponents?: boolean;
|
|
38
40
|
keepUrlRefs?: boolean;
|
|
39
|
-
}): Promise<
|
|
40
|
-
bundle: Document;
|
|
41
|
-
problems: import("./walk").NormalizedProblem[];
|
|
42
|
-
fileDependencies: Set<string>;
|
|
43
|
-
rootType: NormalizedNodeType;
|
|
44
|
-
refTypes: Map<string, NormalizedNodeType> | undefined;
|
|
45
|
-
visitorsData: Record<string, Record<string, unknown>>;
|
|
46
|
-
}>;
|
|
41
|
+
}): Promise<BundleResult>;
|
|
47
42
|
export declare function mapTypeToComponent(typeName: string, version: OasMajorVersion): "headers" | "definitions" | "parameters" | "examples" | "schemas" | "responses" | "requestBodies" | "securitySchemes" | "links" | "callbacks" | null;
|
package/lib/config/config.js
CHANGED
|
@@ -8,6 +8,7 @@ const utils_1 = require("../utils");
|
|
|
8
8
|
const oas_types_1 = require("../oas-types");
|
|
9
9
|
const env_1 = require("../env");
|
|
10
10
|
const utils_2 = require("./utils");
|
|
11
|
+
const ref_utils_1 = require("../ref-utils");
|
|
11
12
|
exports.IGNORE_FILE = '.redocly.lint-ignore.yaml';
|
|
12
13
|
const IGNORE_BANNER = `# This file instructs Redocly's linter to ignore the rules contained for specific parts of your API.\n` +
|
|
13
14
|
`# See https://redoc.ly/docs/cli/ for more information.\n`;
|
|
@@ -75,11 +76,13 @@ class StyleguideConfig {
|
|
|
75
76
|
js_yaml_1.parseYaml(fs.readFileSync(ignoreFile, 'utf-8')) || {};
|
|
76
77
|
// resolve ignore paths
|
|
77
78
|
for (const fileName of Object.keys(this.ignore)) {
|
|
78
|
-
this.ignore[path.resolve(path.dirname(ignoreFile), fileName)] = this.ignore[fileName];
|
|
79
|
+
this.ignore[ref_utils_1.isAbsoluteUrl(fileName) ? fileName : path.resolve(path.dirname(ignoreFile), fileName)] = this.ignore[fileName];
|
|
79
80
|
for (const ruleId of Object.keys(this.ignore[fileName])) {
|
|
80
81
|
this.ignore[fileName][ruleId] = new Set(this.ignore[fileName][ruleId]);
|
|
81
82
|
}
|
|
82
|
-
|
|
83
|
+
if (!ref_utils_1.isAbsoluteUrl(fileName)) {
|
|
84
|
+
delete this.ignore[fileName];
|
|
85
|
+
}
|
|
83
86
|
}
|
|
84
87
|
}
|
|
85
88
|
saveIgnore() {
|
|
@@ -87,8 +90,10 @@ class StyleguideConfig {
|
|
|
87
90
|
const ignoreFile = path.join(dir, exports.IGNORE_FILE);
|
|
88
91
|
const mapped = {};
|
|
89
92
|
for (const absFileName of Object.keys(this.ignore)) {
|
|
90
|
-
const
|
|
91
|
-
|
|
93
|
+
const mappedDefinitionName = ref_utils_1.isAbsoluteUrl(absFileName)
|
|
94
|
+
? absFileName
|
|
95
|
+
: utils_1.slash(path.relative(dir, absFileName));
|
|
96
|
+
const ignoredRules = (mapped[mappedDefinitionName] = this.ignore[absFileName]);
|
|
92
97
|
for (const ruleId of Object.keys(ignoredRules)) {
|
|
93
98
|
ignoredRules[ruleId] = Array.from(ignoredRules[ruleId]);
|
|
94
99
|
}
|
package/lib/config/types.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { ProblemSeverity } from '../walk';
|
|
1
|
+
import type { ProblemSeverity, UserContext } from '../walk';
|
|
2
2
|
import type { Oas3PreprocessorsSet, OasMajorVersion, Oas3DecoratorsSet, Oas2RuleSet, Oas2PreprocessorsSet, Oas2DecoratorsSet, Oas3RuleSet, OasVersion } from '../oas-types';
|
|
3
3
|
import type { NodeType } from '../types';
|
|
4
4
|
import { Location } from '../ref-utils';
|
|
5
|
+
import { SkipFunctionContext } from '../visitors';
|
|
5
6
|
export declare type RuleSeverity = ProblemSeverity | 'off';
|
|
6
7
|
export declare type RuleSettings = {
|
|
7
8
|
severity: RuleSeverity;
|
|
@@ -54,6 +55,9 @@ export declare type CustomRulesConfig = {
|
|
|
54
55
|
oas3?: Oas3RuleSet;
|
|
55
56
|
oas2?: Oas2RuleSet;
|
|
56
57
|
};
|
|
58
|
+
export declare type AssertionContext = Partial<UserContext> & SkipFunctionContext & {
|
|
59
|
+
node: any;
|
|
60
|
+
};
|
|
57
61
|
export declare type AssertResult = {
|
|
58
62
|
message?: string;
|
|
59
63
|
location?: Location;
|
package/lib/format/format.js
CHANGED
|
@@ -7,6 +7,7 @@ const output_1 = require("../output");
|
|
|
7
7
|
const coreVersion = require('../../package.json').version;
|
|
8
8
|
const codeframes_1 = require("./codeframes");
|
|
9
9
|
const env_1 = require("../env");
|
|
10
|
+
const ref_utils_1 = require("../ref-utils");
|
|
10
11
|
const ERROR_MESSAGE = {
|
|
11
12
|
INVALID_SEVERITY_LEVEL: 'Invalid severity level; accepted values: error or warn',
|
|
12
13
|
};
|
|
@@ -75,7 +76,7 @@ function formatProblems(problems, opts) {
|
|
|
75
76
|
case 'stylish': {
|
|
76
77
|
const groupedByFile = groupByFiles(problems);
|
|
77
78
|
for (const [file, { ruleIdPad, locationPad: positionPad, fileProblems }] of Object.entries(groupedByFile)) {
|
|
78
|
-
logger_1.logger.info(`${logger_1.colorize.blue(path.relative(cwd, file))}:\n`);
|
|
79
|
+
logger_1.logger.info(`${logger_1.colorize.blue(ref_utils_1.isAbsoluteUrl(file) ? file : path.relative(cwd, file))}:\n`);
|
|
79
80
|
for (let i = 0; i < fileProblems.length; i++) {
|
|
80
81
|
const problem = fileProblems[i];
|
|
81
82
|
logger_1.logger.info(`${formatStylish(problem, positionPad, ruleIdPad)}\n`);
|
|
@@ -89,7 +90,7 @@ function formatProblems(problems, opts) {
|
|
|
89
90
|
output_1.output.write('<?xml version="1.0" encoding="UTF-8"?>\n');
|
|
90
91
|
output_1.output.write('<checkstyle version="4.3">\n');
|
|
91
92
|
for (const [file, { fileProblems }] of Object.entries(groupedByFile)) {
|
|
92
|
-
output_1.output.write(`<file name="${xmlEscape(path.relative(cwd, file))}">\n`);
|
|
93
|
+
output_1.output.write(`<file name="${xmlEscape(ref_utils_1.isAbsoluteUrl(file) ? file : path.relative(cwd, file))}">\n`);
|
|
93
94
|
fileProblems.forEach(formatCheckstyle);
|
|
94
95
|
output_1.output.write(`</file>\n`);
|
|
95
96
|
}
|
|
@@ -113,7 +114,9 @@ function formatProblems(problems, opts) {
|
|
|
113
114
|
return {
|
|
114
115
|
description: p.message,
|
|
115
116
|
location: {
|
|
116
|
-
path:
|
|
117
|
+
path: ref_utils_1.isAbsoluteUrl(location.source.absoluteRef)
|
|
118
|
+
? location.source.absoluteRef
|
|
119
|
+
: path.relative(cwd, location.source.absoluteRef),
|
|
117
120
|
lines: {
|
|
118
121
|
begin: lineCol.start.line,
|
|
119
122
|
},
|
|
@@ -129,12 +132,16 @@ function formatProblems(problems, opts) {
|
|
|
129
132
|
totals,
|
|
130
133
|
version,
|
|
131
134
|
problems: problems.map((p) => {
|
|
132
|
-
var _a;
|
|
135
|
+
var _a, _b, _c;
|
|
133
136
|
const problem = Object.assign(Object.assign({}, p), { location: p.location.map((location) => (Object.assign(Object.assign({}, location), { source: {
|
|
134
|
-
ref:
|
|
137
|
+
ref: ref_utils_1.isAbsoluteUrl(location.source.absoluteRef)
|
|
138
|
+
? location.source.absoluteRef
|
|
139
|
+
: path.relative(cwd, location.source.absoluteRef),
|
|
135
140
|
} }))), from: p.from
|
|
136
141
|
? Object.assign(Object.assign({}, p.from), { source: {
|
|
137
|
-
ref:
|
|
142
|
+
ref: ref_utils_1.isAbsoluteUrl((_a = p.from) === null || _a === void 0 ? void 0 : _a.source.absoluteRef)
|
|
143
|
+
? (_b = p.from) === null || _b === void 0 ? void 0 : _b.source.absoluteRef
|
|
144
|
+
: path.relative(cwd, ((_c = p.from) === null || _c === void 0 ? void 0 : _c.source.absoluteRef) || cwd),
|
|
138
145
|
} }) : undefined });
|
|
139
146
|
if (env_1.env.FORMAT_JSON_WITH_CODEFRAMES) {
|
|
140
147
|
const location = p.location[0]; // TODO: support multiple locations
|
|
@@ -156,7 +163,9 @@ function formatProblems(problems, opts) {
|
|
|
156
163
|
function formatCodeframe(problem, idx) {
|
|
157
164
|
const bgColor = getBgColor(problem);
|
|
158
165
|
const location = problem.location[0]; // TODO: support multiple locations
|
|
159
|
-
const relativePath =
|
|
166
|
+
const relativePath = ref_utils_1.isAbsoluteUrl(location.source.absoluteRef)
|
|
167
|
+
? location.source.absoluteRef
|
|
168
|
+
: path.relative(cwd, location.source.absoluteRef);
|
|
160
169
|
const loc = codeframes_1.getLineColLocation(location);
|
|
161
170
|
const atPointer = location.pointer ? logger_1.colorize.gray(`at ${location.pointer}`) : '';
|
|
162
171
|
const fileWithLoc = `${relativePath}:${loc.start.line}:${loc.start.col}`;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import { AssertResult, CustomFunction } from 'core/src/config/types';
|
|
1
|
+
import { AssertionContext, AssertResult, CustomFunction } from 'core/src/config/types';
|
|
2
2
|
import { Location } from '../../../ref-utils';
|
|
3
|
-
export declare type
|
|
3
|
+
export declare type AssertionFnContext = AssertionContext & {
|
|
4
|
+
baseLocation: Location;
|
|
5
|
+
rawValue?: any;
|
|
6
|
+
};
|
|
7
|
+
export declare type AssertionFn = (value: any, condition: any, ctx: AssertionFnContext) => AssertResult[];
|
|
4
8
|
export declare type Asserts = {
|
|
5
9
|
pattern: AssertionFn;
|
|
6
10
|
notPattern: AssertionFn;
|
|
@@ -35,7 +35,7 @@ exports.runOnValuesSet = new Set([
|
|
|
35
35
|
'const',
|
|
36
36
|
]);
|
|
37
37
|
exports.asserts = {
|
|
38
|
-
pattern: (value, condition, baseLocation) => {
|
|
38
|
+
pattern: (value, condition, { baseLocation }) => {
|
|
39
39
|
if (typeof value === 'undefined' || utils_1.isPlainObject(value))
|
|
40
40
|
return []; // property doesn't exist or is an object, no need to lint it with this assert
|
|
41
41
|
const values = Array.isArray(value) ? value : [value];
|
|
@@ -47,7 +47,7 @@ exports.asserts = {
|
|
|
47
47
|
})
|
|
48
48
|
.filter(utils_1.isTruthy);
|
|
49
49
|
},
|
|
50
|
-
notPattern: (value, condition, baseLocation) => {
|
|
50
|
+
notPattern: (value, condition, { baseLocation }) => {
|
|
51
51
|
if (typeof value === 'undefined' || utils_1.isPlainObject(value))
|
|
52
52
|
return []; // property doesn't exist or is an object, no need to lint it with this assert
|
|
53
53
|
const values = Array.isArray(value) ? value : [value];
|
|
@@ -59,7 +59,7 @@ exports.asserts = {
|
|
|
59
59
|
})
|
|
60
60
|
.filter(utils_1.isTruthy);
|
|
61
61
|
},
|
|
62
|
-
enum: (value, condition, baseLocation) => {
|
|
62
|
+
enum: (value, condition, { baseLocation }) => {
|
|
63
63
|
if (typeof value === 'undefined' || utils_1.isPlainObject(value))
|
|
64
64
|
return []; // property doesn't exist or is an object, no need to lint it with this assert
|
|
65
65
|
const values = Array.isArray(value) ? value : [value];
|
|
@@ -70,7 +70,7 @@ exports.asserts = {
|
|
|
70
70
|
})
|
|
71
71
|
.filter(utils_1.isTruthy);
|
|
72
72
|
},
|
|
73
|
-
defined: (value, condition = true, baseLocation) => {
|
|
73
|
+
defined: (value, condition = true, { baseLocation }) => {
|
|
74
74
|
const isDefined = typeof value !== 'undefined';
|
|
75
75
|
const isValid = condition ? isDefined : !isDefined;
|
|
76
76
|
return isValid
|
|
@@ -82,7 +82,7 @@ exports.asserts = {
|
|
|
82
82
|
},
|
|
83
83
|
];
|
|
84
84
|
},
|
|
85
|
-
required: (value, keys, baseLocation) => {
|
|
85
|
+
required: (value, keys, { baseLocation }) => {
|
|
86
86
|
return keys
|
|
87
87
|
.map((requiredKey) => !value.includes(requiredKey) && {
|
|
88
88
|
message: `${requiredKey} is required`,
|
|
@@ -90,7 +90,7 @@ exports.asserts = {
|
|
|
90
90
|
})
|
|
91
91
|
.filter(utils_1.isTruthy);
|
|
92
92
|
},
|
|
93
|
-
disallowed: (value, condition, baseLocation) => {
|
|
93
|
+
disallowed: (value, condition, { baseLocation }) => {
|
|
94
94
|
if (typeof value === 'undefined' || utils_1.isPlainObject(value))
|
|
95
95
|
return []; // property doesn't exist or is an object, no need to lint it with this assert
|
|
96
96
|
const values = Array.isArray(value) ? value : [value];
|
|
@@ -101,7 +101,7 @@ exports.asserts = {
|
|
|
101
101
|
})
|
|
102
102
|
.filter(utils_1.isTruthy);
|
|
103
103
|
},
|
|
104
|
-
const: (value, condition, baseLocation) => {
|
|
104
|
+
const: (value, condition, { baseLocation }) => {
|
|
105
105
|
if (typeof value === 'undefined')
|
|
106
106
|
return [];
|
|
107
107
|
if (Array.isArray(value)) {
|
|
@@ -123,7 +123,7 @@ exports.asserts = {
|
|
|
123
123
|
: [];
|
|
124
124
|
}
|
|
125
125
|
},
|
|
126
|
-
undefined: (value, condition = true, baseLocation) => {
|
|
126
|
+
undefined: (value, condition = true, { baseLocation }) => {
|
|
127
127
|
const isUndefined = typeof value === 'undefined';
|
|
128
128
|
const isValid = condition ? isUndefined : !isUndefined;
|
|
129
129
|
return isValid
|
|
@@ -135,7 +135,7 @@ exports.asserts = {
|
|
|
135
135
|
},
|
|
136
136
|
];
|
|
137
137
|
},
|
|
138
|
-
nonEmpty: (value, condition = true, baseLocation) => {
|
|
138
|
+
nonEmpty: (value, condition = true, { baseLocation }) => {
|
|
139
139
|
const isEmpty = typeof value === 'undefined' || value === null || value === '';
|
|
140
140
|
const isValid = condition ? !isEmpty : isEmpty;
|
|
141
141
|
return isValid
|
|
@@ -147,17 +147,27 @@ exports.asserts = {
|
|
|
147
147
|
},
|
|
148
148
|
];
|
|
149
149
|
},
|
|
150
|
-
minLength: (value, condition, baseLocation) => {
|
|
150
|
+
minLength: (value, condition, { baseLocation }) => {
|
|
151
151
|
if (typeof value === 'undefined' || value.length >= condition)
|
|
152
152
|
return []; // property doesn't exist, no need to lint it with this assert
|
|
153
|
-
return [
|
|
153
|
+
return [
|
|
154
|
+
{
|
|
155
|
+
message: `Should have at least ${condition} characters`,
|
|
156
|
+
location: baseLocation,
|
|
157
|
+
},
|
|
158
|
+
];
|
|
154
159
|
},
|
|
155
|
-
maxLength: (value, condition, baseLocation) => {
|
|
160
|
+
maxLength: (value, condition, { baseLocation }) => {
|
|
156
161
|
if (typeof value === 'undefined' || value.length <= condition)
|
|
157
162
|
return []; // property doesn't exist, no need to lint it with this assert
|
|
158
|
-
return [
|
|
163
|
+
return [
|
|
164
|
+
{
|
|
165
|
+
message: `Should have at most ${condition} characters`,
|
|
166
|
+
location: baseLocation,
|
|
167
|
+
},
|
|
168
|
+
];
|
|
159
169
|
},
|
|
160
|
-
casing: (value, condition, baseLocation) => {
|
|
170
|
+
casing: (value, condition, { baseLocation }) => {
|
|
161
171
|
if (typeof value === 'undefined' || utils_1.isPlainObject(value))
|
|
162
172
|
return []; // property doesn't exist or is an object, no need to lint it with this assert
|
|
163
173
|
const values = Array.isArray(value) ? value : [value];
|
|
@@ -177,7 +187,7 @@ exports.asserts = {
|
|
|
177
187
|
})
|
|
178
188
|
.filter(utils_1.isTruthy);
|
|
179
189
|
},
|
|
180
|
-
sortOrder: (value, condition, baseLocation) => {
|
|
190
|
+
sortOrder: (value, condition, { baseLocation }) => {
|
|
181
191
|
const direction = condition.direction || condition;
|
|
182
192
|
const property = condition.property;
|
|
183
193
|
if (Array.isArray(value) && value.length > 0 && typeof value[0] === 'object' && !property) {
|
|
@@ -197,7 +207,7 @@ exports.asserts = {
|
|
|
197
207
|
},
|
|
198
208
|
];
|
|
199
209
|
},
|
|
200
|
-
mutuallyExclusive: (value, condition, baseLocation) => {
|
|
210
|
+
mutuallyExclusive: (value, condition, { baseLocation }) => {
|
|
201
211
|
if (utils_2.getIntersectionLength(value, condition) < 2)
|
|
202
212
|
return [];
|
|
203
213
|
return [
|
|
@@ -207,7 +217,7 @@ exports.asserts = {
|
|
|
207
217
|
},
|
|
208
218
|
];
|
|
209
219
|
},
|
|
210
|
-
mutuallyRequired: (value, condition, baseLocation) => {
|
|
220
|
+
mutuallyRequired: (value, condition, { baseLocation }) => {
|
|
211
221
|
const isValid = utils_2.getIntersectionLength(value, condition) > 0
|
|
212
222
|
? utils_2.getIntersectionLength(value, condition) === condition.length
|
|
213
223
|
: true;
|
|
@@ -220,7 +230,7 @@ exports.asserts = {
|
|
|
220
230
|
},
|
|
221
231
|
];
|
|
222
232
|
},
|
|
223
|
-
requireAny: (value, condition, baseLocation) => {
|
|
233
|
+
requireAny: (value, condition, { baseLocation }) => {
|
|
224
234
|
return utils_2.getIntersectionLength(value, condition) >= 1
|
|
225
235
|
? []
|
|
226
236
|
: [
|
|
@@ -230,7 +240,7 @@ exports.asserts = {
|
|
|
230
240
|
},
|
|
231
241
|
];
|
|
232
242
|
},
|
|
233
|
-
ref: (_value, condition, baseLocation, rawValue) => {
|
|
243
|
+
ref: (_value, condition, { baseLocation, rawValue }) => {
|
|
234
244
|
if (typeof rawValue === 'undefined')
|
|
235
245
|
return []; // property doesn't exist, no need to lint it with this assert
|
|
236
246
|
const hasRef = rawValue.hasOwnProperty('$ref');
|
|
@@ -258,6 +268,6 @@ exports.asserts = {
|
|
|
258
268
|
},
|
|
259
269
|
};
|
|
260
270
|
function buildAssertCustomFunction(fn) {
|
|
261
|
-
return (value, options,
|
|
271
|
+
return (value, options, ctx) => fn.call(null, value, options, ctx);
|
|
262
272
|
}
|
|
263
273
|
exports.buildAssertCustomFunction = buildAssertCustomFunction;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Asserts } from './asserts';
|
|
2
|
+
import type { AssertionContext, AssertResult } from '../../../config';
|
|
2
3
|
import type { Assertion, AssertionDefinition } from '.';
|
|
3
4
|
import type { Oas2Visitor, Oas3Visitor, VisitFunction } from '../../../visitors';
|
|
4
5
|
export declare type OrderDirection = 'asc' | 'desc';
|
|
@@ -12,9 +13,16 @@ export declare type AssertToApply = {
|
|
|
12
13
|
runsOnKeys: boolean;
|
|
13
14
|
runsOnValues: boolean;
|
|
14
15
|
};
|
|
16
|
+
declare type RunAssertionParams = {
|
|
17
|
+
ctx: AssertionContext;
|
|
18
|
+
assert: AssertToApply;
|
|
19
|
+
assertionProperty?: string;
|
|
20
|
+
};
|
|
15
21
|
export declare function getAssertsToApply(assertion: AssertionDefinition): AssertToApply[];
|
|
16
22
|
export declare function buildVisitorObject(assertion: Assertion, subjectVisitor: VisitFunction<any>): Oas2Visitor | Oas3Visitor;
|
|
17
23
|
export declare function buildSubjectVisitor(assertId: string, assertion: Assertion): VisitFunction<any>;
|
|
18
24
|
export declare function getIntersectionLength(keys: string[], properties: string[]): number;
|
|
19
25
|
export declare function isOrdered(value: any[], options: OrderOptions | OrderDirection): boolean;
|
|
26
|
+
export declare function runAssertion({ assert, ctx, assertionProperty, }: RunAssertionParams): AssertResult[];
|
|
20
27
|
export declare function regexFromString(input: string): RegExp | null;
|
|
28
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.regexFromString = exports.isOrdered = exports.getIntersectionLength = exports.buildSubjectVisitor = exports.buildVisitorObject = exports.getAssertsToApply = void 0;
|
|
3
|
+
exports.regexFromString = exports.runAssertion = exports.isOrdered = exports.getIntersectionLength = exports.buildSubjectVisitor = exports.buildVisitorObject = exports.getAssertsToApply = void 0;
|
|
4
4
|
const asserts_1 = require("./asserts");
|
|
5
5
|
const logger_1 = require("../../../logger");
|
|
6
6
|
const ref_utils_1 = require("../../../ref-utils");
|
|
@@ -43,31 +43,23 @@ exports.getAssertsToApply = getAssertsToApply;
|
|
|
43
43
|
function getAssertionProperties({ subject }) {
|
|
44
44
|
return (Array.isArray(subject.property) ? subject.property : [subject === null || subject === void 0 ? void 0 : subject.property]).filter(Boolean);
|
|
45
45
|
}
|
|
46
|
-
function applyAssertions(assertionDefinition, asserts,
|
|
47
|
-
var _a;
|
|
46
|
+
function applyAssertions(assertionDefinition, asserts, ctx) {
|
|
48
47
|
const properties = getAssertionProperties(assertionDefinition);
|
|
49
48
|
const assertResults = [];
|
|
50
49
|
for (const assert of asserts) {
|
|
51
|
-
const currentLocation = assert.name === 'ref' ? rawLocation : location;
|
|
52
50
|
if (properties.length) {
|
|
53
51
|
for (const property of properties) {
|
|
54
|
-
// we can have resolvable scalar so need to resolve value here.
|
|
55
|
-
const value = ref_utils_1.isRef(node[property]) ? (_a = resolve(node[property])) === null || _a === void 0 ? void 0 : _a.node : node[property];
|
|
56
52
|
assertResults.push(runAssertion({
|
|
57
|
-
values: value,
|
|
58
|
-
rawValues: rawNode[property],
|
|
59
53
|
assert,
|
|
60
|
-
|
|
54
|
+
ctx,
|
|
55
|
+
assertionProperty: property,
|
|
61
56
|
}));
|
|
62
57
|
}
|
|
63
58
|
}
|
|
64
59
|
else {
|
|
65
|
-
const value = Array.isArray(node) ? node : Object.keys(node);
|
|
66
60
|
assertResults.push(runAssertion({
|
|
67
|
-
values: value,
|
|
68
|
-
rawValues: rawNode,
|
|
69
61
|
assert,
|
|
70
|
-
|
|
62
|
+
ctx,
|
|
71
63
|
}));
|
|
72
64
|
}
|
|
73
65
|
}
|
|
@@ -77,7 +69,7 @@ function buildVisitorObject(assertion, subjectVisitor) {
|
|
|
77
69
|
var _a, _b;
|
|
78
70
|
const targetVisitorLocatorPredicates = getPredicatesFromLocators(assertion.subject);
|
|
79
71
|
const targetVisitorSkipFunction = targetVisitorLocatorPredicates.length
|
|
80
|
-
? (
|
|
72
|
+
? (_, key) => !targetVisitorLocatorPredicates.every((predicate) => predicate(key))
|
|
81
73
|
: undefined;
|
|
82
74
|
const targetVisitor = {
|
|
83
75
|
[assertion.subject.type]: Object.assign({ enter: subjectVisitor }, (targetVisitorSkipFunction && { skip: targetVisitorSkipFunction })),
|
|
@@ -95,14 +87,8 @@ function buildVisitorObject(assertion, subjectVisitor) {
|
|
|
95
87
|
}
|
|
96
88
|
const locatorPredicates = getPredicatesFromLocators(assertionDefinitionNode.subject);
|
|
97
89
|
const assertsToApply = getAssertsToApply(assertionDefinitionNode);
|
|
98
|
-
const skipFunction = (node, key,
|
|
99
|
-
!!applyAssertions(assertionDefinitionNode, assertsToApply, {
|
|
100
|
-
location,
|
|
101
|
-
node,
|
|
102
|
-
rawLocation,
|
|
103
|
-
rawNode,
|
|
104
|
-
resolve,
|
|
105
|
-
}).length;
|
|
90
|
+
const skipFunction = (node, key, ctx) => !locatorPredicates.every((predicate) => predicate(key)) ||
|
|
91
|
+
!!applyAssertions(assertionDefinitionNode, assertsToApply, Object.assign(Object.assign({}, ctx), { node })).length;
|
|
106
92
|
const nodeVisitor = Object.assign({}, ((locatorPredicates.length || assertsToApply.length) && { skip: skipFunction }));
|
|
107
93
|
if (assertionDefinitionNode.subject.type === assertion.subject.type &&
|
|
108
94
|
index === context.length - 1) {
|
|
@@ -123,23 +109,17 @@ function buildVisitorObject(assertion, subjectVisitor) {
|
|
|
123
109
|
}
|
|
124
110
|
exports.buildVisitorObject = buildVisitorObject;
|
|
125
111
|
function buildSubjectVisitor(assertId, assertion) {
|
|
126
|
-
return (node,
|
|
112
|
+
return (node, ctx) => {
|
|
127
113
|
const properties = getAssertionProperties(assertion);
|
|
128
114
|
const defaultMessage = `${logger_1.colorize.blue(assertId)} failed because the ${logger_1.colorize.blue(assertion.subject.type)} ${logger_1.colorize.blue(properties.join(', '))} didn't meet the assertions: ${assertionMessageTemplates.problems}`.replace(/ +/g, ' ');
|
|
129
|
-
const problems = applyAssertions(assertion, getAssertsToApply(assertion), {
|
|
130
|
-
rawLocation,
|
|
131
|
-
rawNode,
|
|
132
|
-
resolve,
|
|
133
|
-
location,
|
|
134
|
-
node,
|
|
135
|
-
});
|
|
115
|
+
const problems = applyAssertions(assertion, getAssertsToApply(assertion), Object.assign(Object.assign({}, ctx), { node }));
|
|
136
116
|
if (problems.length) {
|
|
137
117
|
for (const problemGroup of groupProblemsByPointer(problems)) {
|
|
138
118
|
const message = assertion.message || defaultMessage;
|
|
139
119
|
const problemMessage = getProblemsMessage(problemGroup);
|
|
140
|
-
report({
|
|
120
|
+
ctx.report({
|
|
141
121
|
message: message.replace(assertionMessageTemplates.problems, problemMessage),
|
|
142
|
-
location: getProblemsLocation(problemGroup) || location,
|
|
122
|
+
location: getProblemsLocation(problemGroup) || ctx.location,
|
|
143
123
|
forceSeverity: assertion.severity || 'error',
|
|
144
124
|
suggest: assertion.suggest || [],
|
|
145
125
|
ruleId: assertId,
|
|
@@ -207,9 +187,23 @@ function isOrdered(value, options) {
|
|
|
207
187
|
return true;
|
|
208
188
|
}
|
|
209
189
|
exports.isOrdered = isOrdered;
|
|
210
|
-
function runAssertion({
|
|
211
|
-
|
|
190
|
+
function runAssertion({ assert, ctx, assertionProperty, }) {
|
|
191
|
+
var _a;
|
|
192
|
+
const currentLocation = assert.name === 'ref' ? ctx.rawLocation : ctx.location;
|
|
193
|
+
if (assertionProperty) {
|
|
194
|
+
const values = ref_utils_1.isRef(ctx.node[assertionProperty])
|
|
195
|
+
? (_a = ctx.resolve(ctx.node[assertionProperty])) === null || _a === void 0 ? void 0 : _a.node
|
|
196
|
+
: ctx.node[assertionProperty];
|
|
197
|
+
const rawValues = ctx.rawNode[assertionProperty];
|
|
198
|
+
const location = currentLocation.child(assertionProperty);
|
|
199
|
+
return asserts_1.asserts[assert.name](values, assert.conditions, Object.assign(Object.assign({}, ctx), { baseLocation: location, rawValue: rawValues }));
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
const value = Array.isArray(ctx.node) ? ctx.node : Object.keys(ctx.node);
|
|
203
|
+
return asserts_1.asserts[assert.name](value, assert.conditions, Object.assign(Object.assign({}, ctx), { rawValue: ctx.rawNode, baseLocation: currentLocation }));
|
|
204
|
+
}
|
|
212
205
|
}
|
|
206
|
+
exports.runAssertion = runAssertion;
|
|
213
207
|
function regexFromString(input) {
|
|
214
208
|
const matches = input.match(/^\/(.*)\/(.*)|(.*)/);
|
|
215
209
|
return matches && new RegExp(matches[1] || matches[3], matches[2]);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RequiredStringPropertyMissingMunLength = void 0;
|
|
4
|
+
const RequiredStringPropertyMissingMunLength = () => {
|
|
5
|
+
let skipSchemaProperties;
|
|
6
|
+
let requiredPropertiesSet;
|
|
7
|
+
return {
|
|
8
|
+
Schema: {
|
|
9
|
+
enter(schema) {
|
|
10
|
+
if (!(schema === null || schema === void 0 ? void 0 : schema.required)) {
|
|
11
|
+
skipSchemaProperties = true;
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
requiredPropertiesSet = new Set(schema.required);
|
|
15
|
+
skipSchemaProperties = false;
|
|
16
|
+
},
|
|
17
|
+
SchemaProperties: {
|
|
18
|
+
skip() {
|
|
19
|
+
return skipSchemaProperties;
|
|
20
|
+
},
|
|
21
|
+
Schema: {
|
|
22
|
+
enter(schema, { key, location, report }) {
|
|
23
|
+
if (requiredPropertiesSet.has(key) && schema.type === 'string') {
|
|
24
|
+
if (!(schema === null || schema === void 0 ? void 0 : schema.minLength)) {
|
|
25
|
+
report({
|
|
26
|
+
message: 'Property minLength is required.',
|
|
27
|
+
location: location.key(),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
exports.RequiredStringPropertyMissingMunLength = RequiredStringPropertyMissingMunLength;
|
|
@@ -41,5 +41,6 @@ export declare const rules: {
|
|
|
41
41
|
'response-contains-header': Oas2Rule;
|
|
42
42
|
'response-contains-property': Oas2Rule;
|
|
43
43
|
'scalar-property-missing-example': import("../../visitors").Oas3Rule | Oas2Rule;
|
|
44
|
+
'required-string-property-missing-min-length': import("../../visitors").Oas3Rule;
|
|
44
45
|
};
|
|
45
46
|
export declare const preprocessors: {};
|
package/lib/rules/oas2/index.js
CHANGED
|
@@ -41,6 +41,7 @@ const path_segment_plural_1 = require("../common/path-segment-plural");
|
|
|
41
41
|
const response_contains_header_1 = require("../common/response-contains-header");
|
|
42
42
|
const response_contains_property_1 = require("./response-contains-property");
|
|
43
43
|
const scalar_property_missing_example_1 = require("../common/scalar-property-missing-example");
|
|
44
|
+
const required_string_property_missing_min_length_1 = require("../common/required-string-property-missing-min-length");
|
|
44
45
|
exports.rules = {
|
|
45
46
|
spec: spec_1.OasSpec,
|
|
46
47
|
'no-invalid-schema-examples': no_invalid_schema_examples_1.NoInvalidSchemaExamples,
|
|
@@ -83,5 +84,6 @@ exports.rules = {
|
|
|
83
84
|
'response-contains-header': response_contains_header_1.ResponseContainsHeader,
|
|
84
85
|
'response-contains-property': response_contains_property_1.ResponseContainsProperty,
|
|
85
86
|
'scalar-property-missing-example': scalar_property_missing_example_1.ScalarPropertyMissingExample,
|
|
87
|
+
'required-string-property-missing-min-length': required_string_property_missing_min_length_1.RequiredStringPropertyMissingMunLength,
|
|
86
88
|
};
|
|
87
89
|
exports.preprocessors = {};
|
package/lib/rules/oas3/index.js
CHANGED
|
@@ -51,6 +51,7 @@ const response_contains_property_1 = require("./response-contains-property");
|
|
|
51
51
|
const scalar_property_missing_example_1 = require("../common/scalar-property-missing-example");
|
|
52
52
|
const spec_components_invalid_map_name_1 = require("./spec-components-invalid-map-name");
|
|
53
53
|
const operation_4xx_problem_details_rfc7807_1 = require("./operation-4xx-problem-details-rfc7807");
|
|
54
|
+
const required_string_property_missing_min_length_1 = require("../common/required-string-property-missing-min-length");
|
|
54
55
|
exports.rules = {
|
|
55
56
|
spec: spec_1.OasSpec,
|
|
56
57
|
'info-contact': info_contact_1.InfoContact,
|
|
@@ -103,5 +104,6 @@ exports.rules = {
|
|
|
103
104
|
'response-contains-property': response_contains_property_1.ResponseContainsProperty,
|
|
104
105
|
'scalar-property-missing-example': scalar_property_missing_example_1.ScalarPropertyMissingExample,
|
|
105
106
|
'spec-components-invalid-map-name': spec_components_invalid_map_name_1.SpecComponentsInvalidMapName,
|
|
107
|
+
'required-string-property-missing-min-length': required_string_property_missing_min_length_1.RequiredStringPropertyMissingMunLength,
|
|
106
108
|
};
|
|
107
109
|
exports.preprocessors = {};
|
package/package.json
CHANGED
package/src/bundle.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { Oas3Types } from './types/oas3';
|
|
|
5
5
|
import { Oas2Types } from './types/oas2';
|
|
6
6
|
import { Oas3_1Types } from './types/oas3_1';
|
|
7
7
|
import { NormalizedNodeType, normalizeTypes, NodeType } from './types';
|
|
8
|
-
import { WalkContext, walkDocument, UserContext, ResolveResult } from './walk';
|
|
8
|
+
import { WalkContext, walkDocument, UserContext, ResolveResult, NormalizedProblem } from './walk';
|
|
9
9
|
import { detectOpenAPI, openAPIMajor, OasMajorVersion } from './oas-types';
|
|
10
10
|
import { isAbsoluteUrl, isRef, Location, refBaseName } from './ref-utils';
|
|
11
11
|
import { initRules } from './config/rules';
|
|
@@ -64,6 +64,15 @@ export async function bundle(opts: {
|
|
|
64
64
|
|
|
65
65
|
type BundleContext = WalkContext;
|
|
66
66
|
|
|
67
|
+
export type BundleResult = {
|
|
68
|
+
bundle: Document;
|
|
69
|
+
problems: NormalizedProblem[];
|
|
70
|
+
fileDependencies: Set<string>;
|
|
71
|
+
rootType: NormalizedNodeType;
|
|
72
|
+
refTypes?: Map<string, NormalizedNodeType>;
|
|
73
|
+
visitorsData: Record<string, Record<string, unknown>>;
|
|
74
|
+
};
|
|
75
|
+
|
|
67
76
|
export async function bundleDocument(opts: {
|
|
68
77
|
document: Document;
|
|
69
78
|
config: StyleguideConfig;
|
|
@@ -73,7 +82,7 @@ export async function bundleDocument(opts: {
|
|
|
73
82
|
skipRedoclyRegistryRefs?: boolean;
|
|
74
83
|
removeUnusedComponents?: boolean;
|
|
75
84
|
keepUrlRefs?: boolean;
|
|
76
|
-
}) {
|
|
85
|
+
}): Promise<BundleResult> {
|
|
77
86
|
const {
|
|
78
87
|
document,
|
|
79
88
|
config,
|