@redocly/openapi-core 1.0.0-beta.89 → 1.0.0-beta.92
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/__tests__/__snapshots__/bundle.test.ts.snap +15 -13
- package/__tests__/ref-utils.test.ts +23 -1
- package/lib/config/all.js +1 -0
- package/lib/config/config.js +6 -11
- package/lib/config/minimal.js +1 -0
- package/lib/config/recommended.js +1 -0
- package/lib/config/rules.d.ts +1 -1
- package/lib/config/rules.js +10 -2
- package/lib/redocly/index.d.ts +4 -1
- package/lib/redocly/index.js +28 -19
- package/lib/redocly/registry-api.d.ts +4 -1
- package/lib/redocly/registry-api.js +3 -3
- package/lib/ref-utils.js +1 -1
- package/lib/rules/common/assertions/asserts.d.ts +5 -0
- package/lib/rules/common/assertions/asserts.js +143 -0
- package/lib/rules/common/assertions/index.d.ts +2 -0
- package/lib/rules/common/assertions/index.js +52 -0
- package/lib/rules/common/assertions/utils.d.ts +20 -0
- package/lib/rules/common/assertions/utils.js +123 -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 +24 -11
- package/lib/visitors.d.ts +2 -2
- package/lib/walk.d.ts +1 -0
- package/lib/walk.js +1 -1
- package/package.json +1 -1
- package/src/__tests__/lint.test.ts +40 -6
- package/src/bundle.ts +1 -1
- package/src/config/all.ts +1 -0
- package/src/config/config.ts +15 -12
- package/src/config/minimal.ts +1 -0
- package/src/config/recommended.ts +1 -0
- package/src/config/rules.ts +11 -2
- package/src/lint.ts +3 -3
- package/src/redocly/index.ts +49 -30
- package/src/redocly/registry-api.ts +38 -21
- package/src/ref-utils.ts +1 -1
- package/src/rules/common/assertions/__tests__/asserts.test.ts +231 -0
- package/src/rules/common/assertions/__tests__/index.test.ts +65 -0
- package/src/rules/common/assertions/__tests__/utils.test.ts +89 -0
- package/src/rules/common/assertions/asserts.ts +137 -0
- package/src/rules/common/assertions/index.ts +75 -0
- package/src/rules/common/assertions/utils.ts +164 -0
- package/src/rules/oas2/index.ts +2 -0
- package/src/rules/oas3/index.ts +2 -0
- package/src/types/redocly-yaml.ts +30 -11
- package/src/visitors.ts +2 -2
- package/src/walk.ts +2 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isOrdered = exports.getIntersectionLength = exports.buildSubjectVisitor = exports.buildVisitorObject = void 0;
|
|
4
|
+
const asserts_1 = require("./asserts");
|
|
5
|
+
function buildVisitorObject(subject, context, subjectVisitor) {
|
|
6
|
+
if (!context) {
|
|
7
|
+
return { [subject]: subjectVisitor };
|
|
8
|
+
}
|
|
9
|
+
let currentVisitorLevel = {};
|
|
10
|
+
const visitor = currentVisitorLevel;
|
|
11
|
+
for (let index = 0; index < context.length; index++) {
|
|
12
|
+
const node = context[index];
|
|
13
|
+
if (context.length === index + 1 && node.type === subject) {
|
|
14
|
+
// Visitors don't work properly for the same type nested nodes, so
|
|
15
|
+
// as a workaround for that we don't create separate visitor for the last element
|
|
16
|
+
// which is the same as subject;
|
|
17
|
+
// we will check includes/excludes it in the last visitor.
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const matchParentKeys = node.matchParentKeys;
|
|
21
|
+
const excludeParentKeys = node.excludeParentKeys;
|
|
22
|
+
if (matchParentKeys && excludeParentKeys) {
|
|
23
|
+
throw new Error(`Both 'matchParentKeys' and 'excludeParentKeys' can't be under one context item`);
|
|
24
|
+
}
|
|
25
|
+
if (matchParentKeys || excludeParentKeys) {
|
|
26
|
+
currentVisitorLevel[node.type] = {
|
|
27
|
+
skip: (_value, key) => {
|
|
28
|
+
if (matchParentKeys) {
|
|
29
|
+
return !matchParentKeys.includes(key);
|
|
30
|
+
}
|
|
31
|
+
if (excludeParentKeys) {
|
|
32
|
+
return excludeParentKeys.includes(key);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
currentVisitorLevel[node.type] = {};
|
|
39
|
+
}
|
|
40
|
+
currentVisitorLevel = currentVisitorLevel[node.type];
|
|
41
|
+
}
|
|
42
|
+
currentVisitorLevel[subject] = subjectVisitor;
|
|
43
|
+
return visitor;
|
|
44
|
+
}
|
|
45
|
+
exports.buildVisitorObject = buildVisitorObject;
|
|
46
|
+
function buildSubjectVisitor(properties, asserts, context) {
|
|
47
|
+
return function (node, { report, location, key, type }) {
|
|
48
|
+
// We need to check context's last node if it has the same type as subject node;
|
|
49
|
+
// if yes - that means we didn't create context's last node visitor,
|
|
50
|
+
// so we need to handle 'matchParentKeys' and 'excludeParentKeys' conditions here;
|
|
51
|
+
if (context) {
|
|
52
|
+
const lastContextNode = context[context.length - 1];
|
|
53
|
+
if (lastContextNode.type === type.name) {
|
|
54
|
+
const matchParentKeys = lastContextNode.matchParentKeys;
|
|
55
|
+
const excludeParentKeys = lastContextNode.excludeParentKeys;
|
|
56
|
+
if (matchParentKeys && !matchParentKeys.includes(key)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (excludeParentKeys && excludeParentKeys.includes(key)) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (properties) {
|
|
65
|
+
properties = Array.isArray(properties) ? properties : [properties];
|
|
66
|
+
}
|
|
67
|
+
for (const assert of asserts) {
|
|
68
|
+
if (properties) {
|
|
69
|
+
for (const property of properties) {
|
|
70
|
+
runAssertion(node[property], assert, location.child(property), report);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
runAssertion(Object.keys(node), assert, location.key(), report);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
exports.buildSubjectVisitor = buildSubjectVisitor;
|
|
80
|
+
function getIntersectionLength(keys, properties) {
|
|
81
|
+
const props = new Set(properties);
|
|
82
|
+
let count = 0;
|
|
83
|
+
for (const key of keys) {
|
|
84
|
+
if (props.has(key)) {
|
|
85
|
+
count++;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return count;
|
|
89
|
+
}
|
|
90
|
+
exports.getIntersectionLength = getIntersectionLength;
|
|
91
|
+
function isOrdered(value, options) {
|
|
92
|
+
const direction = options.direction || options;
|
|
93
|
+
const property = options.property;
|
|
94
|
+
for (let i = 1; i < value.length; i++) {
|
|
95
|
+
let currValue = value[i];
|
|
96
|
+
let prevVal = value[i - 1];
|
|
97
|
+
if (property) {
|
|
98
|
+
if (!value[i][property] || !value[i - 1][property]) {
|
|
99
|
+
return false; // property doesn't exist, so collection is not ordered
|
|
100
|
+
}
|
|
101
|
+
currValue = value[i][property];
|
|
102
|
+
prevVal = value[i - 1][property];
|
|
103
|
+
}
|
|
104
|
+
const result = direction === 'asc' ? currValue >= prevVal : currValue <= prevVal;
|
|
105
|
+
if (!result) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
exports.isOrdered = isOrdered;
|
|
112
|
+
function runAssertion(values, assert, location, report) {
|
|
113
|
+
const lintResult = asserts_1.asserts[assert.name](values, assert.conditions);
|
|
114
|
+
if (!lintResult) {
|
|
115
|
+
report({
|
|
116
|
+
message: assert.message || `The ${assert.assertId} doesn't meet required conditions`,
|
|
117
|
+
location,
|
|
118
|
+
forceSeverity: assert.severity,
|
|
119
|
+
suggest: assert.suggest,
|
|
120
|
+
ruleId: assert.assertId,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -15,6 +15,7 @@ export declare const rules: {
|
|
|
15
15
|
'no-path-trailing-slash': Oas2Rule;
|
|
16
16
|
'operation-2xx-response': Oas2Rule;
|
|
17
17
|
'operation-4xx-response': Oas2Rule;
|
|
18
|
+
assertions: Oas2Rule;
|
|
18
19
|
'operation-operationId-unique': Oas2Rule;
|
|
19
20
|
'operation-parameters-unique': Oas2Rule;
|
|
20
21
|
'path-parameters-defined': Oas2Rule;
|
package/lib/rules/oas2/index.js
CHANGED
|
@@ -16,6 +16,7 @@ const no_enum_type_mismatch_1 = require("../common/no-enum-type-mismatch");
|
|
|
16
16
|
const no_path_trailing_slash_1 = require("../common/no-path-trailing-slash");
|
|
17
17
|
const operation_2xx_response_1 = require("../common/operation-2xx-response");
|
|
18
18
|
const operation_4xx_response_1 = require("../common/operation-4xx-response");
|
|
19
|
+
const assertions_1 = require("../common/assertions");
|
|
19
20
|
const operation_operationId_unique_1 = require("../common/operation-operationId-unique");
|
|
20
21
|
const operation_parameters_unique_1 = require("../common/operation-parameters-unique");
|
|
21
22
|
const path_params_defined_1 = require("../common/path-params-defined");
|
|
@@ -54,6 +55,7 @@ exports.rules = {
|
|
|
54
55
|
'no-path-trailing-slash': no_path_trailing_slash_1.NoPathTrailingSlash,
|
|
55
56
|
'operation-2xx-response': operation_2xx_response_1.Operation2xxResponse,
|
|
56
57
|
'operation-4xx-response': operation_4xx_response_1.Operation4xxResponse,
|
|
58
|
+
'assertions': assertions_1.Assertions,
|
|
57
59
|
'operation-operationId-unique': operation_operationId_unique_1.OperationIdUnique,
|
|
58
60
|
'operation-parameters-unique': operation_parameters_unique_1.OperationParametersUnique,
|
|
59
61
|
'path-parameters-defined': path_params_defined_1.PathParamsDefined,
|
package/lib/rules/oas3/index.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.preprocessors = exports.rules = void 0;
|
|
|
4
4
|
const spec_1 = require("../common/spec");
|
|
5
5
|
const operation_2xx_response_1 = require("../common/operation-2xx-response");
|
|
6
6
|
const operation_4xx_response_1 = require("../common/operation-4xx-response");
|
|
7
|
+
const assertions_1 = require("../common/assertions");
|
|
7
8
|
const operation_operationId_unique_1 = require("../common/operation-operationId-unique");
|
|
8
9
|
const operation_parameters_unique_1 = require("../common/operation-parameters-unique");
|
|
9
10
|
const path_params_defined_1 = require("../common/path-params-defined");
|
|
@@ -54,6 +55,7 @@ exports.rules = {
|
|
|
54
55
|
'info-license-url': license_url_1.InfoLicenseUrl,
|
|
55
56
|
'operation-2xx-response': operation_2xx_response_1.Operation2xxResponse,
|
|
56
57
|
'operation-4xx-response': operation_4xx_response_1.Operation4xxResponse,
|
|
58
|
+
'assertions': assertions_1.Assertions,
|
|
57
59
|
'operation-operationId-unique': operation_operationId_unique_1.OperationIdUnique,
|
|
58
60
|
'operation-parameters-unique': operation_parameters_unique_1.OperationParametersUnique,
|
|
59
61
|
'path-parameters-defined': path_params_defined_1.PathParamsDefined,
|
|
@@ -5,16 +5,24 @@ const _1 = require(".");
|
|
|
5
5
|
const utils_1 = require("../utils");
|
|
6
6
|
const ConfigRoot = {
|
|
7
7
|
properties: {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
},
|
|
13
|
-
lint: 'ConfigLint',
|
|
14
|
-
referenceDocs: 'ConfigReferenceDocs',
|
|
8
|
+
organization: { type: 'string' },
|
|
9
|
+
apis: 'ConfigApis',
|
|
10
|
+
lint: 'RootConfigLint',
|
|
11
|
+
'features.openapi': 'ConfigReferenceDocs',
|
|
15
12
|
'features.mockServer': 'ConfigMockServer',
|
|
16
13
|
},
|
|
17
14
|
};
|
|
15
|
+
const ConfigApis = {
|
|
16
|
+
properties: {},
|
|
17
|
+
additionalProperties: 'ConfigApisProperties',
|
|
18
|
+
};
|
|
19
|
+
const ConfigApisProperties = {
|
|
20
|
+
properties: {
|
|
21
|
+
root: { type: 'string' },
|
|
22
|
+
lint: 'ConfigLint',
|
|
23
|
+
'features.openapi': 'ConfigReferenceDocs',
|
|
24
|
+
},
|
|
25
|
+
};
|
|
18
26
|
const ConfigHTTP = {
|
|
19
27
|
properties: {
|
|
20
28
|
headers: {
|
|
@@ -27,10 +35,6 @@ const ConfigHTTP = {
|
|
|
27
35
|
};
|
|
28
36
|
const ConfigLint = {
|
|
29
37
|
properties: {
|
|
30
|
-
plugins: {
|
|
31
|
-
type: 'array',
|
|
32
|
-
items: { type: 'string' },
|
|
33
|
-
},
|
|
34
38
|
extends: {
|
|
35
39
|
type: 'array',
|
|
36
40
|
items: {
|
|
@@ -57,6 +61,12 @@ const ConfigLint = {
|
|
|
57
61
|
},
|
|
58
62
|
},
|
|
59
63
|
};
|
|
64
|
+
const RootConfigLint = {
|
|
65
|
+
properties: Object.assign({ plugins: {
|
|
66
|
+
type: 'array',
|
|
67
|
+
items: { type: 'string' },
|
|
68
|
+
} }, ConfigLint.properties),
|
|
69
|
+
};
|
|
60
70
|
const ConfigLanguage = {
|
|
61
71
|
properties: {
|
|
62
72
|
label: { type: 'string' },
|
|
@@ -454,6 +464,9 @@ const ConfigMockServer = {
|
|
|
454
464
|
};
|
|
455
465
|
exports.ConfigTypes = {
|
|
456
466
|
ConfigRoot,
|
|
467
|
+
ConfigApis,
|
|
468
|
+
ConfigApisProperties,
|
|
469
|
+
RootConfigLint,
|
|
457
470
|
ConfigLint,
|
|
458
471
|
ConfigReferenceDocs,
|
|
459
472
|
ConfigMockServer,
|
package/lib/visitors.d.ts
CHANGED
|
@@ -156,8 +156,8 @@ export declare type NormalizedOasVisitors<T extends BaseVisitor> = {
|
|
|
156
156
|
leave: Array<VisitorNode<any>>;
|
|
157
157
|
};
|
|
158
158
|
};
|
|
159
|
-
export declare type Oas3Rule = (options: Record<string, any>) => Oas3Visitor;
|
|
160
|
-
export declare type Oas2Rule = (options: Record<string, any>) => Oas2Visitor;
|
|
159
|
+
export declare type Oas3Rule = (options: Record<string, any>) => Oas3Visitor | Oas3Visitor[];
|
|
160
|
+
export declare type Oas2Rule = (options: Record<string, any>) => Oas2Visitor | Oas2Visitor[];
|
|
161
161
|
export declare type Oas3Preprocessor = (options: Record<string, any>) => Oas3TransformVisitor;
|
|
162
162
|
export declare type Oas2Preprocessor = (options: Record<string, any>) => Oas2TransformVisitor;
|
|
163
163
|
export declare type Oas3Decorator = (options: Record<string, any>) => Oas3TransformVisitor;
|
package/lib/walk.d.ts
CHANGED
package/lib/walk.js
CHANGED
|
@@ -240,7 +240,7 @@ function walkDocument(opts) {
|
|
|
240
240
|
? opts.location
|
|
241
241
|
: [opts.location]
|
|
242
242
|
: [Object.assign(Object.assign({}, currentLocation), { reportOnKey: false })];
|
|
243
|
-
ctx.problems.push(Object.assign(Object.assign({ ruleId, severity: opts.forceSeverity || severity }, opts), { suggest: opts.suggest || [], location: loc.map((loc) => {
|
|
243
|
+
ctx.problems.push(Object.assign(Object.assign({ ruleId: opts.ruleId || ruleId, severity: opts.forceSeverity || severity }, opts), { suggest: opts.suggest || [], location: loc.map((loc) => {
|
|
244
244
|
return Object.assign(Object.assign(Object.assign({}, currentLocation), { reportOnKey: false }), loc);
|
|
245
245
|
}) }));
|
|
246
246
|
}
|
package/package.json
CHANGED
|
@@ -46,7 +46,7 @@ describe('lint', () => {
|
|
|
46
46
|
it('lintConfig should work', async () => {
|
|
47
47
|
const document = parseYamlToDocument(
|
|
48
48
|
outdent`
|
|
49
|
-
|
|
49
|
+
apis: error string
|
|
50
50
|
lint:
|
|
51
51
|
plugins:
|
|
52
52
|
- './local-plugin.js'
|
|
@@ -58,7 +58,7 @@ describe('lint', () => {
|
|
|
58
58
|
no-invalid-media-type-examples: error
|
|
59
59
|
path-http-verbs-order: error
|
|
60
60
|
boolean-parameter-prefixes: off
|
|
61
|
-
|
|
61
|
+
features.openapi:
|
|
62
62
|
showConsole: true
|
|
63
63
|
layout:
|
|
64
64
|
scope: section
|
|
@@ -78,12 +78,12 @@ describe('lint', () => {
|
|
|
78
78
|
Object {
|
|
79
79
|
"location": Array [
|
|
80
80
|
Object {
|
|
81
|
-
"pointer": "#/
|
|
81
|
+
"pointer": "#/apis",
|
|
82
82
|
"reportOnKey": false,
|
|
83
83
|
"source": "",
|
|
84
84
|
},
|
|
85
85
|
],
|
|
86
|
-
"message": "Expected type \`
|
|
86
|
+
"message": "Expected type \`ConfigApis\` (object) but got \`string\`",
|
|
87
87
|
"ruleId": "spec",
|
|
88
88
|
"severity": "error",
|
|
89
89
|
"suggest": Array [],
|
|
@@ -91,7 +91,7 @@ describe('lint', () => {
|
|
|
91
91
|
Object {
|
|
92
92
|
"location": Array [
|
|
93
93
|
Object {
|
|
94
|
-
"pointer": "#/
|
|
94
|
+
"pointer": "#/features.openapi/layout",
|
|
95
95
|
"reportOnKey": false,
|
|
96
96
|
"source": "",
|
|
97
97
|
},
|
|
@@ -105,6 +105,40 @@ describe('lint', () => {
|
|
|
105
105
|
`);
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
+
it("'plugins' shouldn't be allowed in 'apis' -> 'lint' field", async () => {
|
|
109
|
+
const document = parseYamlToDocument(
|
|
110
|
+
outdent`
|
|
111
|
+
apis:
|
|
112
|
+
lint:
|
|
113
|
+
plugins:
|
|
114
|
+
- './local-plugin.js'
|
|
115
|
+
lint:
|
|
116
|
+
plugins:
|
|
117
|
+
- './local-plugin.js'
|
|
118
|
+
`,
|
|
119
|
+
'',
|
|
120
|
+
);
|
|
121
|
+
const results = await lintConfig({ document });
|
|
122
|
+
|
|
123
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
124
|
+
Array [
|
|
125
|
+
Object {
|
|
126
|
+
"location": Array [
|
|
127
|
+
Object {
|
|
128
|
+
"pointer": "#/apis/lint/plugins",
|
|
129
|
+
"reportOnKey": true,
|
|
130
|
+
"source": "",
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
"message": "Property \`plugins\` is not expected here.",
|
|
134
|
+
"ruleId": "spec",
|
|
135
|
+
"severity": "error",
|
|
136
|
+
"suggest": Array [],
|
|
137
|
+
},
|
|
138
|
+
]
|
|
139
|
+
`);
|
|
140
|
+
});
|
|
141
|
+
|
|
108
142
|
it("'const' can have any type", async () => {
|
|
109
143
|
const document = parseYamlToDocument(
|
|
110
144
|
outdent`
|
|
@@ -140,7 +174,7 @@ describe('lint', () => {
|
|
|
140
174
|
const results = await lintDocument({
|
|
141
175
|
externalRefResolver: new BaseResolver(),
|
|
142
176
|
document,
|
|
143
|
-
config: makeConfig({ spec: 'error'
|
|
177
|
+
config: makeConfig({ spec: 'error' }),
|
|
144
178
|
});
|
|
145
179
|
|
|
146
180
|
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
|
package/src/bundle.ts
CHANGED
package/src/config/all.ts
CHANGED
|
@@ -19,6 +19,7 @@ export default {
|
|
|
19
19
|
'operation-description': 'error',
|
|
20
20
|
'operation-2xx-response': 'error',
|
|
21
21
|
'operation-4xx-response': 'error',
|
|
22
|
+
'assertions': 'error',
|
|
22
23
|
'operation-operationId': 'error',
|
|
23
24
|
'operation-summary': 'error',
|
|
24
25
|
'operation-operationId-unique': 'error',
|
package/src/config/config.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { dirname } from 'path';
|
|
4
|
-
import { red, blue } from 'colorette';
|
|
4
|
+
import { red, blue, yellow, green } from 'colorette';
|
|
5
5
|
import { parseYaml, stringifyYaml } from '../js-yaml';
|
|
6
6
|
import { notUndefined, slash } from '../utils';
|
|
7
7
|
import {
|
|
@@ -695,17 +695,20 @@ export function transformConfig(rawConfig: DeprecatedRawConfig | RawConfig): Raw
|
|
|
695
695
|
throw new Error("Do not use 'referenceDocs' field. Use 'features.openapi' instead.\n");
|
|
696
696
|
}
|
|
697
697
|
const { apiDefinitions, referenceDocs, ...rest } = rawConfig as DeprecatedRawConfig & RawConfig;
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
698
|
+
if (apiDefinitions) {
|
|
699
|
+
process.stderr.write(
|
|
700
|
+
`The ${yellow('apiDefinitions')} field is deprecated. Use ${green(
|
|
701
|
+
'apis',
|
|
702
|
+
)} instead. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties\n`,
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
if (referenceDocs) {
|
|
706
|
+
process.stderr.write(
|
|
707
|
+
`The ${yellow('referenceDocs')} field is deprecated. Use ${green(
|
|
708
|
+
'features.openapi',
|
|
709
|
+
)} instead. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties\n`,
|
|
710
|
+
);
|
|
711
|
+
}
|
|
709
712
|
return {
|
|
710
713
|
'features.openapi': referenceDocs,
|
|
711
714
|
apis: transformApiDefinitionsToApis(apiDefinitions),
|
package/src/config/minimal.ts
CHANGED
package/src/config/rules.ts
CHANGED
|
@@ -24,14 +24,23 @@ export function initRules<T extends Function, P extends RuleSet<T>>(
|
|
|
24
24
|
return undefined;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
const
|
|
27
|
+
const visitors = rule(ruleSettings);
|
|
28
|
+
|
|
29
|
+
if (Array.isArray(visitors)) {
|
|
30
|
+
return visitors.map((visitor: any) => ({
|
|
31
|
+
severity: ruleSettings.severity,
|
|
32
|
+
ruleId,
|
|
33
|
+
visitor: visitor,
|
|
34
|
+
}))
|
|
35
|
+
}
|
|
28
36
|
|
|
29
37
|
return {
|
|
30
38
|
severity: ruleSettings.severity,
|
|
31
39
|
ruleId,
|
|
32
|
-
visitor,
|
|
40
|
+
visitor: visitors, // note: actually it is only one visitor object
|
|
33
41
|
};
|
|
34
42
|
}),
|
|
35
43
|
)
|
|
44
|
+
.flatMap(visitor => visitor)
|
|
36
45
|
.filter(notUndefined);
|
|
37
46
|
}
|
package/src/lint.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { LintConfig, Config } from './config/config';
|
|
|
11
11
|
import { normalizeTypes } from './types';
|
|
12
12
|
import { initRules } from './config/rules';
|
|
13
13
|
import { releaseAjvInstance } from './rules/ajv';
|
|
14
|
-
import { detectOpenAPI, OasMajorVersion, OasVersion, openAPIMajor } from './oas-types';
|
|
14
|
+
import { detectOpenAPI, Oas3RuleSet, OasMajorVersion, OasVersion, openAPIMajor } from './oas-types';
|
|
15
15
|
import { ConfigTypes } from './types/redocly-yaml';
|
|
16
16
|
import { OasSpec } from './rules/common/spec';
|
|
17
17
|
import { defaultPlugin } from './config/builtIn';
|
|
@@ -76,8 +76,8 @@ export async function lintDocument(opts: {
|
|
|
76
76
|
};
|
|
77
77
|
|
|
78
78
|
const preprocessors = initRules(rules as any, config, 'preprocessors', oasVersion);
|
|
79
|
-
const regularRules = initRules(rules as
|
|
80
|
-
const normalizedVisitors = normalizeVisitors([...preprocessors, ...regularRules], types);
|
|
79
|
+
const regularRules = initRules(rules as Oas3RuleSet[], config, 'rules', oasVersion);
|
|
80
|
+
const normalizedVisitors = normalizeVisitors([...preprocessors, ...regularRules] as any, types);
|
|
81
81
|
const resolvedRefMap = await resolveDocument({
|
|
82
82
|
rootDocument: document,
|
|
83
83
|
rootType: types.DefinitionRoot,
|
package/src/redocly/index.ts
CHANGED
|
@@ -19,9 +19,7 @@ export class RedoclyClient {
|
|
|
19
19
|
constructor(region?: Region) {
|
|
20
20
|
this.region = this.loadRegion(region);
|
|
21
21
|
this.loadTokens();
|
|
22
|
-
this.domain = region
|
|
23
|
-
? DOMAINS[region]
|
|
24
|
-
: process.env.REDOCLY_DOMAIN || DOMAINS[DEFAULT_REGION];
|
|
22
|
+
this.domain = region ? DOMAINS[region] : process.env.REDOCLY_DOMAIN || DOMAINS[DEFAULT_REGION];
|
|
25
23
|
|
|
26
24
|
/*
|
|
27
25
|
* We can't use process.env here because it is replaced by a const in some client-side bundles,
|
|
@@ -34,7 +32,11 @@ export class RedoclyClient {
|
|
|
34
32
|
loadRegion(region?: Region) {
|
|
35
33
|
if (region && !DOMAINS[region]) {
|
|
36
34
|
process.stdout.write(
|
|
37
|
-
red(
|
|
35
|
+
red(
|
|
36
|
+
`Invalid argument: region in config file.\nGiven: ${green(
|
|
37
|
+
region,
|
|
38
|
+
)}, choices: "us", "eu".\n`,
|
|
39
|
+
),
|
|
38
40
|
);
|
|
39
41
|
process.exit(1);
|
|
40
42
|
}
|
|
@@ -86,37 +88,36 @@ export class RedoclyClient {
|
|
|
86
88
|
if (isNotEmptyObject(credentials)) {
|
|
87
89
|
this.setAccessTokens({
|
|
88
90
|
...credentials,
|
|
89
|
-
...(credentials.token &&
|
|
90
|
-
[this.region]
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
...(credentials.token &&
|
|
92
|
+
!credentials[this.region] && {
|
|
93
|
+
[this.region]: credentials.token,
|
|
94
|
+
}),
|
|
95
|
+
});
|
|
93
96
|
}
|
|
94
97
|
if (process.env.REDOCLY_AUTHORIZATION) {
|
|
95
98
|
this.setAccessTokens({
|
|
96
99
|
...this.accessTokens,
|
|
97
|
-
[this.region]: process.env.REDOCLY_AUTHORIZATION
|
|
98
|
-
})
|
|
100
|
+
[this.region]: process.env.REDOCLY_AUTHORIZATION,
|
|
101
|
+
});
|
|
99
102
|
}
|
|
100
103
|
}
|
|
101
104
|
|
|
102
|
-
getAllTokens
|
|
103
|
-
return (<[Region, string][]>Object.entries(this.accessTokens))
|
|
104
|
-
([region]) => AVAILABLE_REGIONS.includes(region)
|
|
105
|
-
|
|
106
|
-
([region, token]) => ({ region, token })
|
|
107
|
-
);
|
|
105
|
+
getAllTokens(): RegionalToken[] {
|
|
106
|
+
return (<[Region, string][]>Object.entries(this.accessTokens))
|
|
107
|
+
.filter(([region]) => AVAILABLE_REGIONS.includes(region))
|
|
108
|
+
.map(([region, token]) => ({ region, token }));
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
async getValidTokens(): Promise<RegionalTokenWithValidity[]> {
|
|
111
|
-
const
|
|
112
|
+
const allTokens = this.getAllTokens();
|
|
112
113
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
114
|
+
const verifiedTokens = await Promise.allSettled(
|
|
115
|
+
allTokens.map(({ token, region }) => this.verifyToken(token, region)),
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
return allTokens
|
|
119
|
+
.filter((_, index) => verifiedTokens[index].status === 'fulfilled')
|
|
120
|
+
.map(({ token, region }) => ({ token, region, valid: true }));
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
async getTokens() {
|
|
@@ -124,9 +125,23 @@ export class RedoclyClient {
|
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
async isAuthorizedWithRedoclyByRegion(): Promise<boolean> {
|
|
127
|
-
if (!this.hasTokens())
|
|
128
|
+
if (!this.hasTokens()) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
128
132
|
const accessToken = this.accessTokens[this.region];
|
|
129
|
-
|
|
133
|
+
|
|
134
|
+
if (!accessToken) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
await this.verifyToken(accessToken, this.region);
|
|
140
|
+
|
|
141
|
+
return true;
|
|
142
|
+
} catch (err) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
130
145
|
}
|
|
131
146
|
|
|
132
147
|
async isAuthorizedWithRedocly(): Promise<boolean> {
|
|
@@ -137,8 +152,11 @@ export class RedoclyClient {
|
|
|
137
152
|
return existsSync(credentialsPath) ? JSON.parse(readFileSync(credentialsPath, 'utf-8')) : {};
|
|
138
153
|
}
|
|
139
154
|
|
|
140
|
-
async verifyToken(
|
|
141
|
-
|
|
155
|
+
async verifyToken(
|
|
156
|
+
accessToken: string,
|
|
157
|
+
region: Region,
|
|
158
|
+
verbose: boolean = false,
|
|
159
|
+
): Promise<{ viewerId: string; organizations: string[] }> {
|
|
142
160
|
return this.registryApi.authStatus(accessToken, region, verbose);
|
|
143
161
|
}
|
|
144
162
|
|
|
@@ -146,8 +164,9 @@ export class RedoclyClient {
|
|
|
146
164
|
const credentialsPath = resolve(homedir(), TOKEN_FILENAME);
|
|
147
165
|
process.stdout.write(gray('\n Logging in...\n'));
|
|
148
166
|
|
|
149
|
-
|
|
150
|
-
|
|
167
|
+
try {
|
|
168
|
+
await this.verifyToken(accessToken, this.region, verbose);
|
|
169
|
+
} catch (err) {
|
|
151
170
|
process.stdout.write(
|
|
152
171
|
red('Authorization failed. Please check if you entered a valid API key.\n'),
|
|
153
172
|
);
|