@redocly/openapi-core 1.0.0-beta.67 → 1.0.0-beta.71
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 +126 -0
- package/__tests__/bundle.test.ts +53 -1
- package/__tests__/fixtures/refs/definitions.yaml +3 -0
- package/__tests__/fixtures/refs/external-request-body.yaml +13 -0
- package/__tests__/fixtures/refs/externalref.yaml +35 -0
- package/__tests__/fixtures/refs/hosted.yaml +35 -0
- package/__tests__/fixtures/refs/rename.yaml +1 -0
- package/__tests__/fixtures/refs/requestBody.yaml +9 -0
- package/__tests__/fixtures/refs/simple.yaml +1 -0
- package/__tests__/fixtures/refs/vendor.schema.yaml +20 -0
- package/__tests__/lint.test.ts +1 -1
- package/__tests__/login.test.ts +17 -0
- package/lib/bundle.d.ts +4 -0
- package/lib/bundle.js +25 -7
- package/lib/config/all.js +2 -0
- package/lib/config/config.d.ts +10 -0
- package/lib/config/config.js +7 -1
- package/lib/config/load.js +17 -8
- package/lib/index.d.ts +2 -2
- package/lib/lint.js +2 -0
- package/lib/redocly/index.d.ts +26 -20
- package/lib/redocly/index.js +83 -214
- package/lib/redocly/registry-api-types.d.ts +28 -0
- package/lib/redocly/registry-api-types.js +2 -0
- package/lib/redocly/registry-api.d.ts +14 -0
- package/lib/redocly/registry-api.js +105 -0
- package/lib/ref-utils.js +1 -2
- package/lib/rules/common/no-invalid-parameter-examples.d.ts +1 -0
- package/lib/rules/common/no-invalid-parameter-examples.js +25 -0
- package/lib/rules/common/no-invalid-schema-examples.d.ts +1 -0
- package/lib/rules/common/no-invalid-schema-examples.js +23 -0
- package/lib/rules/common/paths-kebab-case.js +1 -1
- package/lib/rules/common/registry-dependencies.js +4 -7
- package/lib/rules/oas2/index.d.ts +2 -0
- package/lib/rules/oas2/index.js +4 -0
- package/lib/rules/oas3/index.js +4 -0
- package/lib/rules/oas3/no-invalid-media-type-examples.js +5 -26
- package/lib/rules/oas3/no-server-trailing-slash.js +1 -1
- package/lib/rules/utils.d.ts +3 -0
- package/lib/rules/utils.js +26 -1
- package/lib/typings/openapi.d.ts +3 -0
- package/lib/utils.d.ts +1 -0
- package/lib/utils.js +5 -1
- package/lib/walk.d.ts +2 -0
- package/lib/walk.js +7 -0
- package/package.json +1 -1
- package/src/bundle.ts +51 -9
- package/src/config/__tests__/load.test.ts +35 -0
- package/src/config/all.ts +2 -0
- package/src/config/config.ts +11 -0
- package/src/config/load.ts +20 -9
- package/src/index.ts +2 -8
- package/src/lint.ts +2 -0
- package/src/redocly/__tests__/redocly-client.test.ts +120 -0
- package/src/redocly/index.ts +100 -227
- package/src/redocly/registry-api-types.ts +31 -0
- package/src/redocly/registry-api.ts +110 -0
- package/src/ref-utils.ts +1 -3
- package/src/rules/common/__tests__/paths-kebab-case.test.ts +23 -0
- package/src/rules/common/no-invalid-parameter-examples.ts +36 -0
- package/src/rules/common/no-invalid-schema-examples.ts +27 -0
- package/src/rules/common/paths-kebab-case.ts +1 -1
- package/src/rules/common/registry-dependencies.ts +6 -8
- package/src/rules/oas2/index.ts +4 -0
- package/src/rules/oas3/__tests__/no-server-trailing-slash.test.ts +19 -0
- package/src/rules/oas3/index.ts +4 -0
- package/src/rules/oas3/no-invalid-media-type-examples.ts +16 -36
- package/src/rules/oas3/no-server-trailing-slash.ts +1 -1
- package/src/rules/utils.ts +43 -2
- package/src/typings/openapi.ts +4 -0
- package/src/utils.ts +5 -1
- package/src/walk.ts +10 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/lib/redocly/query.d.ts +0 -4
- package/lib/redocly/query.js +0 -44
- package/src/redocly/query.ts +0 -38
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ValidContentExamples = void 0;
|
|
4
|
-
const ajv_1 = require("../ajv");
|
|
5
4
|
const ref_utils_1 = require("../../ref-utils");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
6
|
const ValidContentExamples = (opts) => {
|
|
7
7
|
var _a;
|
|
8
8
|
const disallowAdditionalProperties = (_a = opts.disallowAdditionalProperties) !== null && _a !== void 0 ? _a : true;
|
|
9
9
|
return {
|
|
10
10
|
MediaType: {
|
|
11
|
-
leave(mediaType,
|
|
11
|
+
leave(mediaType, ctx) {
|
|
12
|
+
const { location, resolve } = ctx;
|
|
12
13
|
if (!mediaType.schema)
|
|
13
14
|
return;
|
|
14
15
|
if (mediaType.example) {
|
|
15
|
-
validateExample(mediaType.example, location.child('example'));
|
|
16
|
+
utils_1.validateExample(mediaType.example, mediaType.schema, location.child('example'), ctx, disallowAdditionalProperties);
|
|
16
17
|
}
|
|
17
18
|
else if (mediaType.examples) {
|
|
18
19
|
for (const exampleName of Object.keys(mediaType.examples)) {
|
|
@@ -25,29 +26,7 @@ const ValidContentExamples = (opts) => {
|
|
|
25
26
|
dataLoc = resolved.location.child('value');
|
|
26
27
|
example = resolved.node;
|
|
27
28
|
}
|
|
28
|
-
validateExample(example.value, dataLoc);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
function validateExample(example, dataLoc) {
|
|
32
|
-
try {
|
|
33
|
-
const { valid, errors } = ajv_1.validateJsonSchema(example, mediaType.schema, location.child('schema'), dataLoc.pointer, resolve, disallowAdditionalProperties);
|
|
34
|
-
if (!valid) {
|
|
35
|
-
for (let error of errors) {
|
|
36
|
-
report({
|
|
37
|
-
message: `Example value must conform to the schema: ${error.message}.`,
|
|
38
|
-
location: Object.assign(Object.assign({}, new ref_utils_1.Location(dataLoc.source, error.instancePath)), { reportOnKey: error.keyword === 'additionalProperties' }),
|
|
39
|
-
from: location,
|
|
40
|
-
suggest: error.suggest,
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
catch (e) {
|
|
46
|
-
report({
|
|
47
|
-
message: `Example validation errored: ${e.message}.`,
|
|
48
|
-
location: location.child('schema'),
|
|
49
|
-
from: location
|
|
50
|
-
});
|
|
29
|
+
utils_1.validateExample(example.value, mediaType.schema, dataLoc, ctx, disallowAdditionalProperties);
|
|
51
30
|
}
|
|
52
31
|
}
|
|
53
32
|
},
|
|
@@ -6,7 +6,7 @@ const NoServerTrailingSlash = () => {
|
|
|
6
6
|
Server(server, { report, location }) {
|
|
7
7
|
if (!server.url)
|
|
8
8
|
return;
|
|
9
|
-
if (server.url.endsWith('/')) {
|
|
9
|
+
if (server.url.endsWith('/') && server.url !== '/') {
|
|
10
10
|
report({
|
|
11
11
|
message: 'Server `url` should not have a trailing slash.',
|
|
12
12
|
location: location.child(['url']),
|
package/lib/rules/utils.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { UserContext } from '../walk';
|
|
2
|
+
import { Location } from '../ref-utils';
|
|
3
|
+
import { Oas3Schema, Referenced } from '../typings/openapi';
|
|
2
4
|
export declare function oasTypeOf(value: unknown): "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | "array" | "null";
|
|
3
5
|
/**
|
|
4
6
|
* Checks if value matches specified JSON schema type
|
|
@@ -12,3 +14,4 @@ export declare function missingRequiredField(type: string, field: string): strin
|
|
|
12
14
|
export declare function fieldNonEmpty(type: string, field: string): string;
|
|
13
15
|
export declare function validateDefinedAndNonEmpty(fieldName: string, value: any, ctx: UserContext): void;
|
|
14
16
|
export declare function getSuggest(given: string, variants: string[]): string[];
|
|
17
|
+
export declare function validateExample(example: any, schema: Referenced<Oas3Schema>, dataLoc: Location, { resolve, location, report }: UserContext, disallowAdditionalProperties: boolean): void;
|
package/lib/rules/utils.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getSuggest = exports.validateDefinedAndNonEmpty = exports.fieldNonEmpty = exports.missingRequiredField = exports.matchesJsonSchemaType = exports.oasTypeOf = void 0;
|
|
3
|
+
exports.validateExample = exports.getSuggest = exports.validateDefinedAndNonEmpty = exports.fieldNonEmpty = exports.missingRequiredField = exports.matchesJsonSchemaType = exports.oasTypeOf = void 0;
|
|
4
4
|
const levenshtein = require("js-levenshtein");
|
|
5
|
+
const ref_utils_1 = require("../ref-utils");
|
|
6
|
+
const ajv_1 = require("./ajv");
|
|
5
7
|
function oasTypeOf(value) {
|
|
6
8
|
if (Array.isArray(value)) {
|
|
7
9
|
return 'array';
|
|
@@ -80,3 +82,26 @@ function getSuggest(given, variants) {
|
|
|
80
82
|
return distances.map((d) => d.variant);
|
|
81
83
|
}
|
|
82
84
|
exports.getSuggest = getSuggest;
|
|
85
|
+
function validateExample(example, schema, dataLoc, { resolve, location, report }, disallowAdditionalProperties) {
|
|
86
|
+
try {
|
|
87
|
+
const { valid, errors } = ajv_1.validateJsonSchema(example, schema, location.child('schema'), dataLoc.pointer, resolve, disallowAdditionalProperties);
|
|
88
|
+
if (!valid) {
|
|
89
|
+
for (let error of errors) {
|
|
90
|
+
report({
|
|
91
|
+
message: `Example value must conform to the schema: ${error.message}.`,
|
|
92
|
+
location: Object.assign(Object.assign({}, new ref_utils_1.Location(dataLoc.source, error.instancePath)), { reportOnKey: error.keyword === 'additionalProperties' }),
|
|
93
|
+
from: location,
|
|
94
|
+
suggest: error.suggest,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
report({
|
|
101
|
+
message: `Example validation errored: ${e.message}.`,
|
|
102
|
+
location: location.child('schema'),
|
|
103
|
+
from: location,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
exports.validateExample = validateExample;
|
package/lib/typings/openapi.d.ts
CHANGED
package/lib/utils.d.ts
CHANGED
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.slash = exports.isPathParameter = exports.readFileAsStringSync = exports.isSingular = exports.validateMimeTypeOAS3 = exports.validateMimeType = exports.splitCamelCaseIntoWords = exports.omitObjectProps = exports.pickObjectProps = exports.match = exports.readFileFromUrl = exports.isPlainObject = exports.notUndefined = exports.loadYaml = exports.popStack = exports.pushStack = exports.stringifyYaml = exports.parseYaml = void 0;
|
|
12
|
+
exports.isNotEmptyObject = exports.slash = exports.isPathParameter = exports.readFileAsStringSync = exports.isSingular = exports.validateMimeTypeOAS3 = exports.validateMimeType = exports.splitCamelCaseIntoWords = exports.omitObjectProps = exports.pickObjectProps = exports.match = exports.readFileFromUrl = exports.isPlainObject = exports.notUndefined = exports.loadYaml = exports.popStack = exports.pushStack = exports.stringifyYaml = exports.parseYaml = void 0;
|
|
13
13
|
const fs = require("fs");
|
|
14
14
|
const minimatch = require("minimatch");
|
|
15
15
|
const node_fetch_1 = require("node-fetch");
|
|
@@ -144,3 +144,7 @@ function slash(path) {
|
|
|
144
144
|
return path.replace(/\\/g, '/');
|
|
145
145
|
}
|
|
146
146
|
exports.slash = slash;
|
|
147
|
+
function isNotEmptyObject(obj) {
|
|
148
|
+
return !!obj && Object.keys(obj).length > 0;
|
|
149
|
+
}
|
|
150
|
+
exports.isNotEmptyObject = isNotEmptyObject;
|
package/lib/walk.d.ts
CHANGED
|
@@ -36,6 +36,7 @@ export declare type UserContext = {
|
|
|
36
36
|
key: string | number;
|
|
37
37
|
parent: any;
|
|
38
38
|
oasVersion: OasVersion;
|
|
39
|
+
getVisitorData: () => Record<string, unknown>;
|
|
39
40
|
};
|
|
40
41
|
export declare type Loc = {
|
|
41
42
|
line: number;
|
|
@@ -72,6 +73,7 @@ export declare type NormalizedProblem = {
|
|
|
72
73
|
export declare type WalkContext = {
|
|
73
74
|
problems: NormalizedProblem[];
|
|
74
75
|
oasVersion: OasVersion;
|
|
76
|
+
visitorsData: Record<string, Record<string, unknown>>;
|
|
75
77
|
refTypes?: Map<string, NormalizedNodeType>;
|
|
76
78
|
};
|
|
77
79
|
export declare function walkDocument<T>(opts: {
|
package/lib/walk.js
CHANGED
|
@@ -50,6 +50,7 @@ function walkDocument(opts) {
|
|
|
50
50
|
key,
|
|
51
51
|
parentLocations: {},
|
|
52
52
|
oasVersion: ctx.oasVersion,
|
|
53
|
+
getVisitorData: getVisitorDataFn.bind(undefined, ruleId)
|
|
53
54
|
}, { node: resolvedNode, location: resolvedLocation, error });
|
|
54
55
|
if ((resolvedLocation === null || resolvedLocation === void 0 ? void 0 : resolvedLocation.source.absoluteRef) && ctx.refTypes) {
|
|
55
56
|
ctx.refTypes.set(resolvedLocation === null || resolvedLocation === void 0 ? void 0 : resolvedLocation.source.absoluteRef, type);
|
|
@@ -192,6 +193,7 @@ function walkDocument(opts) {
|
|
|
192
193
|
key,
|
|
193
194
|
parentLocations: {},
|
|
194
195
|
oasVersion: ctx.oasVersion,
|
|
196
|
+
getVisitorData: getVisitorDataFn.bind(undefined, ruleId)
|
|
195
197
|
}, { node: resolvedNode, location: resolvedLocation, error });
|
|
196
198
|
}
|
|
197
199
|
}
|
|
@@ -212,6 +214,7 @@ function walkDocument(opts) {
|
|
|
212
214
|
ignoreNextVisitorsOnNode: () => {
|
|
213
215
|
ignoreNextVisitorsOnNode = true;
|
|
214
216
|
},
|
|
217
|
+
getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
|
|
215
218
|
}, collectParents(context), context);
|
|
216
219
|
return ignoreNextVisitorsOnNode;
|
|
217
220
|
}
|
|
@@ -244,6 +247,10 @@ function walkDocument(opts) {
|
|
|
244
247
|
return Object.assign(Object.assign(Object.assign({}, currentLocation), { reportOnKey: false }), loc);
|
|
245
248
|
}) }));
|
|
246
249
|
}
|
|
250
|
+
function getVisitorDataFn(ruleId) {
|
|
251
|
+
ctx.visitorsData[ruleId] = ctx.visitorsData[ruleId] || {};
|
|
252
|
+
return ctx.visitorsData[ruleId];
|
|
253
|
+
}
|
|
247
254
|
}
|
|
248
255
|
}
|
|
249
256
|
exports.walkDocument = walkDocument;
|
package/package.json
CHANGED
package/src/bundle.ts
CHANGED
|
@@ -7,12 +7,13 @@ import { Oas3_1Types } from './types/oas3_1';
|
|
|
7
7
|
import { NormalizedNodeType, normalizeTypes, NodeType } from './types';
|
|
8
8
|
import { WalkContext, walkDocument, UserContext, ResolveResult } from './walk';
|
|
9
9
|
import { detectOpenAPI, openAPIMajor, OasMajorVersion } from './oas-types';
|
|
10
|
-
import { Location, refBaseName } from './ref-utils';
|
|
10
|
+
import { isRef, Location, refBaseName } from './ref-utils';
|
|
11
11
|
import type { Config, LintConfig } from './config/config';
|
|
12
12
|
import { initRules } from './config/rules';
|
|
13
13
|
import { reportUnresolvedRef } from './rules/no-unresolved-refs';
|
|
14
14
|
import { isPlainObject } from './utils';
|
|
15
15
|
import { OasRef } from './typings/openapi';
|
|
16
|
+
import { isRedoclyRegistryURL } from './redocly';
|
|
16
17
|
|
|
17
18
|
export type Oas3RuleSet = Record<string, Oas3Rule>;
|
|
18
19
|
|
|
@@ -29,6 +30,7 @@ export async function bundle(opts: {
|
|
|
29
30
|
config: Config;
|
|
30
31
|
dereference?: boolean;
|
|
31
32
|
base?: string;
|
|
33
|
+
skipRedoclyRegistryRefs?: boolean;
|
|
32
34
|
}) {
|
|
33
35
|
const {
|
|
34
36
|
ref,
|
|
@@ -40,7 +42,8 @@ export async function bundle(opts: {
|
|
|
40
42
|
throw new Error('Document or reference is required.\n');
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
const document =
|
|
45
|
+
const document =
|
|
46
|
+
doc !== undefined ? doc : await externalRefResolver.resolveDocument(base, ref!, true);
|
|
44
47
|
|
|
45
48
|
if (document instanceof Error) {
|
|
46
49
|
throw document;
|
|
@@ -62,14 +65,26 @@ export async function bundleDocument(opts: {
|
|
|
62
65
|
customTypes?: Record<string, NodeType>;
|
|
63
66
|
externalRefResolver: BaseResolver;
|
|
64
67
|
dereference?: boolean;
|
|
68
|
+
skipRedoclyRegistryRefs?: boolean;
|
|
65
69
|
}) {
|
|
66
|
-
const {
|
|
70
|
+
const {
|
|
71
|
+
document,
|
|
72
|
+
config,
|
|
73
|
+
customTypes,
|
|
74
|
+
externalRefResolver,
|
|
75
|
+
dereference = false,
|
|
76
|
+
skipRedoclyRegistryRefs = false,
|
|
77
|
+
} = opts;
|
|
67
78
|
const oasVersion = detectOpenAPI(document.parsed);
|
|
68
79
|
const oasMajorVersion = openAPIMajor(oasVersion);
|
|
69
80
|
const rules = config.getRulesForOasVersion(oasMajorVersion);
|
|
70
81
|
const types = normalizeTypes(
|
|
71
82
|
config.extendTypes(
|
|
72
|
-
customTypes ?? oasMajorVersion === OasMajorVersion.Version3
|
|
83
|
+
customTypes ?? oasMajorVersion === OasMajorVersion.Version3
|
|
84
|
+
? oasVersion === OasVersion.Version3_1
|
|
85
|
+
? Oas3_1Types
|
|
86
|
+
: Oas3Types
|
|
87
|
+
: Oas2Types,
|
|
73
88
|
oasVersion,
|
|
74
89
|
),
|
|
75
90
|
config,
|
|
@@ -81,6 +96,7 @@ export async function bundleDocument(opts: {
|
|
|
81
96
|
problems: [],
|
|
82
97
|
oasVersion: oasVersion,
|
|
83
98
|
refTypes: new Map<string, NormalizedNodeType>(),
|
|
99
|
+
visitorsData: {},
|
|
84
100
|
};
|
|
85
101
|
|
|
86
102
|
const bundleVisitor = normalizeVisitors(
|
|
@@ -89,7 +105,7 @@ export async function bundleDocument(opts: {
|
|
|
89
105
|
{
|
|
90
106
|
severity: 'error',
|
|
91
107
|
ruleId: 'bundler',
|
|
92
|
-
visitor: makeBundleVisitor(oasMajorVersion, dereference, document),
|
|
108
|
+
visitor: makeBundleVisitor(oasMajorVersion, dereference, skipRedoclyRegistryRefs, document),
|
|
93
109
|
},
|
|
94
110
|
...decorators,
|
|
95
111
|
],
|
|
@@ -116,6 +132,7 @@ export async function bundleDocument(opts: {
|
|
|
116
132
|
fileDependencies: externalRefResolver.getFiles(),
|
|
117
133
|
rootType: types.DefinitionRoot,
|
|
118
134
|
refTypes: ctx.refTypes,
|
|
135
|
+
visitorsData: ctx.visitorsData,
|
|
119
136
|
};
|
|
120
137
|
}
|
|
121
138
|
|
|
@@ -160,7 +177,12 @@ function mapTypeToComponent(typeName: string, version: OasMajorVersion) {
|
|
|
160
177
|
|
|
161
178
|
// function oas3Move
|
|
162
179
|
|
|
163
|
-
function makeBundleVisitor(
|
|
180
|
+
function makeBundleVisitor(
|
|
181
|
+
version: OasMajorVersion,
|
|
182
|
+
dereference: boolean,
|
|
183
|
+
skipRedoclyRegistryRefs: boolean,
|
|
184
|
+
rootDocument: Document,
|
|
185
|
+
) {
|
|
164
186
|
let components: Record<string, Record<string, any>>;
|
|
165
187
|
|
|
166
188
|
const visitor: Oas3Visitor | Oas2Visitor = {
|
|
@@ -178,6 +200,11 @@ function makeBundleVisitor(version: OasMajorVersion, dereference: boolean, rootD
|
|
|
178
200
|
) {
|
|
179
201
|
return;
|
|
180
202
|
}
|
|
203
|
+
|
|
204
|
+
if (skipRedoclyRegistryRefs && isRedoclyRegistryURL(node.$ref)) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
181
208
|
const componentType = mapTypeToComponent(ctx.type.name, version);
|
|
182
209
|
if (!componentType) {
|
|
183
210
|
replaceRef(node, resolved, ctx);
|
|
@@ -249,6 +276,21 @@ function makeBundleVisitor(version: OasMajorVersion, dereference: boolean, rootD
|
|
|
249
276
|
}
|
|
250
277
|
}
|
|
251
278
|
|
|
279
|
+
function isEqualOrEqualRef(
|
|
280
|
+
node: any,
|
|
281
|
+
target: { node: any; location: Location },
|
|
282
|
+
ctx: UserContext,
|
|
283
|
+
) {
|
|
284
|
+
if (
|
|
285
|
+
isRef(node) &&
|
|
286
|
+
ctx.resolve(node).location?.absolutePointer === target.location.absolutePointer
|
|
287
|
+
) {
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return isEqual(node, target.node);
|
|
292
|
+
}
|
|
293
|
+
|
|
252
294
|
function getComponentName(
|
|
253
295
|
target: { node: any; location: Location },
|
|
254
296
|
componentType: string,
|
|
@@ -265,20 +307,20 @@ function makeBundleVisitor(version: OasMajorVersion, dereference: boolean, rootD
|
|
|
265
307
|
if (
|
|
266
308
|
!componentsGroup ||
|
|
267
309
|
!componentsGroup[name] ||
|
|
268
|
-
|
|
310
|
+
isEqualOrEqualRef(componentsGroup[name], target, ctx)
|
|
269
311
|
) {
|
|
270
312
|
return name;
|
|
271
313
|
}
|
|
272
314
|
}
|
|
273
315
|
|
|
274
316
|
name = refBaseName(fileRef) + (name ? `_${name}` : '');
|
|
275
|
-
if (!componentsGroup[name] ||
|
|
317
|
+
if (!componentsGroup[name] || isEqualOrEqualRef(componentsGroup[name], target, ctx)) {
|
|
276
318
|
return name;
|
|
277
319
|
}
|
|
278
320
|
|
|
279
321
|
const prevName = name;
|
|
280
322
|
let serialId = 2;
|
|
281
|
-
while (componentsGroup[name] && !
|
|
323
|
+
while (componentsGroup[name] && !isEqualOrEqualRef(componentsGroup[name], target, ctx)) {
|
|
282
324
|
name = `${prevName}-${serialId}`;
|
|
283
325
|
serialId++;
|
|
284
326
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { loadConfig } from '../load';
|
|
2
|
+
import { RedoclyClient } from '../../redocly';
|
|
3
|
+
|
|
4
|
+
describe('loadConfig', () => {
|
|
5
|
+
it('should resolve config http header by US region', async () => {
|
|
6
|
+
jest.spyOn(RedoclyClient.prototype, 'getTokens').mockImplementation(
|
|
7
|
+
() => Promise.resolve([{ region: 'us', token: "accessToken", valid: true }])
|
|
8
|
+
);
|
|
9
|
+
const config = await loadConfig();
|
|
10
|
+
expect(config.resolve.http.headers).toStrictEqual([{
|
|
11
|
+
"matches": 'https://api.redoc.ly/registry/**',
|
|
12
|
+
"name": "Authorization",
|
|
13
|
+
"envVariable": undefined,
|
|
14
|
+
"value": "accessToken"
|
|
15
|
+
}, {
|
|
16
|
+
"matches": 'https://api.redocly.com/registry/**',
|
|
17
|
+
"name": "Authorization",
|
|
18
|
+
"envVariable": undefined,
|
|
19
|
+
"value": "accessToken"
|
|
20
|
+
}]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should resolve config http header by EU region', async () => {
|
|
24
|
+
jest.spyOn(RedoclyClient.prototype, 'getTokens').mockImplementation(
|
|
25
|
+
() => Promise.resolve([{ region: 'eu', token: "accessToken", valid: true }])
|
|
26
|
+
);
|
|
27
|
+
const config = await loadConfig();
|
|
28
|
+
expect(config.resolve.http.headers).toStrictEqual([{
|
|
29
|
+
"matches": 'https://api.eu.redocly.com/registry/**',
|
|
30
|
+
"name": "Authorization",
|
|
31
|
+
"envVariable": undefined,
|
|
32
|
+
"value": "accessToken"
|
|
33
|
+
}]);
|
|
34
|
+
});
|
|
35
|
+
});
|
package/src/config/all.ts
CHANGED
package/src/config/config.ts
CHANGED
|
@@ -123,11 +123,20 @@ export type ResolveConfig = {
|
|
|
123
123
|
http: HttpResolveConfig;
|
|
124
124
|
};
|
|
125
125
|
|
|
126
|
+
export const DEFAULT_REGION = 'us';
|
|
127
|
+
export type Region = 'us' | 'eu';
|
|
128
|
+
export type AccessTokens = {[region in Region]?: string };
|
|
129
|
+
export const DOMAINS: { [region in Region]: string } = {
|
|
130
|
+
us: 'redoc.ly',
|
|
131
|
+
eu: 'eu.redocly.com',
|
|
132
|
+
};
|
|
133
|
+
|
|
126
134
|
export type RawConfig = {
|
|
127
135
|
referenceDocs?: any;
|
|
128
136
|
apiDefinitions?: Record<string, string>;
|
|
129
137
|
lint?: LintRawConfig;
|
|
130
138
|
resolve?: RawResolveConfig;
|
|
139
|
+
region?: Region;
|
|
131
140
|
};
|
|
132
141
|
|
|
133
142
|
export class LintConfig {
|
|
@@ -385,6 +394,7 @@ export class Config {
|
|
|
385
394
|
lint: LintConfig;
|
|
386
395
|
resolve: ResolveConfig;
|
|
387
396
|
licenseKey?: string;
|
|
397
|
+
region?: Region;
|
|
388
398
|
constructor(public rawConfig: RawConfig, public configFile?: string) {
|
|
389
399
|
this.apiDefinitions = rawConfig.apiDefinitions || {};
|
|
390
400
|
this.lint = new LintConfig(rawConfig.lint || {}, configFile);
|
|
@@ -395,6 +405,7 @@ export class Config {
|
|
|
395
405
|
customFetch: undefined,
|
|
396
406
|
},
|
|
397
407
|
};
|
|
408
|
+
this.region = rawConfig.region;
|
|
398
409
|
}
|
|
399
410
|
}
|
|
400
411
|
|
package/src/config/load.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
|
-
|
|
3
2
|
import { RedoclyClient } from '../redocly';
|
|
4
3
|
import { loadYaml } from '../utils';
|
|
5
|
-
import { Config, RawConfig } from './config';
|
|
4
|
+
import { Config, DOMAINS, RawConfig, Region } from './config';
|
|
6
5
|
|
|
7
6
|
import { defaultPlugin } from './builtIn';
|
|
8
7
|
|
|
@@ -25,19 +24,31 @@ export async function loadConfig(configPath?: string, customExtends?: string[]):
|
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
const redoclyClient = new RedoclyClient();
|
|
28
|
-
|
|
27
|
+
const tokens = await redoclyClient.getTokens();
|
|
28
|
+
|
|
29
|
+
if (tokens.length) {
|
|
29
30
|
if (!rawConfig.resolve) rawConfig.resolve = {};
|
|
30
31
|
if (!rawConfig.resolve.http) rawConfig.resolve.http = {};
|
|
31
|
-
rawConfig.resolve.http.headers = [
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
rawConfig.resolve.http.headers = [...(rawConfig.resolve.http.headers ?? [])];
|
|
33
|
+
|
|
34
|
+
for (const item of tokens) {
|
|
35
|
+
const domain = DOMAINS[item.region as Region];
|
|
36
|
+
rawConfig.resolve.http.headers.push({
|
|
37
|
+
matches: `https://api.${domain}/registry/**`,
|
|
34
38
|
name: 'Authorization',
|
|
35
39
|
envVariable: undefined,
|
|
36
|
-
value:
|
|
40
|
+
value: item.token,
|
|
37
41
|
},
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
//support redocly.com domain for future compatibility
|
|
43
|
+
...(item.region === 'us' ? [{
|
|
44
|
+
matches: `https://api.redocly.com/registry/**`,
|
|
45
|
+
name: 'Authorization',
|
|
46
|
+
envVariable: undefined,
|
|
47
|
+
value: item.token,
|
|
48
|
+
}] : []));
|
|
49
|
+
}
|
|
40
50
|
}
|
|
51
|
+
|
|
41
52
|
return new Config(
|
|
42
53
|
{
|
|
43
54
|
...rawConfig,
|
package/src/index.ts
CHANGED
|
@@ -17,7 +17,7 @@ export { StatsAccumulator, StatsName } from './typings/common';
|
|
|
17
17
|
export { normalizeTypes } from './types';
|
|
18
18
|
export { Stats } from './rules/other/stats';
|
|
19
19
|
|
|
20
|
-
export { Config, LintConfig, RawConfig, IGNORE_FILE } from './config/config';
|
|
20
|
+
export { Config, LintConfig, RawConfig, IGNORE_FILE, Region } from './config/config';
|
|
21
21
|
export { loadConfig } from './config/load';
|
|
22
22
|
export { RedoclyClient } from './redocly';
|
|
23
23
|
export {
|
|
@@ -46,11 +46,5 @@ export {
|
|
|
46
46
|
|
|
47
47
|
export { getAstNodeByPointer, getLineColLocation } from './format/codeframes';
|
|
48
48
|
export { formatProblems, OutputFormat, getTotals, Totals } from './format/format';
|
|
49
|
-
export {
|
|
50
|
-
lint,
|
|
51
|
-
lint as validate,
|
|
52
|
-
lintDocument,
|
|
53
|
-
lintFromString,
|
|
54
|
-
lintConfig,
|
|
55
|
-
} from './lint';
|
|
49
|
+
export { lint, lint as validate, lintDocument, lintFromString, lintConfig } from './lint';
|
|
56
50
|
export { bundle, bundleDocument } from './bundle';
|
package/src/lint.ts
CHANGED
|
@@ -72,6 +72,7 @@ export async function lintDocument(opts: {
|
|
|
72
72
|
const ctx: WalkContext = {
|
|
73
73
|
problems: [],
|
|
74
74
|
oasVersion: oasVersion,
|
|
75
|
+
visitorsData: {},
|
|
75
76
|
};
|
|
76
77
|
|
|
77
78
|
const preprocessors = initRules(rules as any, config, 'preprocessors', oasVersion);
|
|
@@ -101,6 +102,7 @@ export async function lintConfig(opts: {
|
|
|
101
102
|
const ctx: WalkContext = {
|
|
102
103
|
problems: [],
|
|
103
104
|
oasVersion: OasVersion.Version3_0,
|
|
105
|
+
visitorsData: {},
|
|
104
106
|
};
|
|
105
107
|
const config = new LintConfig({
|
|
106
108
|
plugins: [defaultPlugin],
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { RedoclyClient } from '../index';
|
|
2
|
+
|
|
3
|
+
describe('RedoclyClient', () => {
|
|
4
|
+
const REDOCLY_DOMAIN_US = 'redoc.ly';
|
|
5
|
+
const REDOCLY_DOMAIN_EU = 'eu.redocly.com';
|
|
6
|
+
const REDOCLY_AUTHORIZATION_TOKEN = 'redocly-auth-token';
|
|
7
|
+
const testRedoclyDomain = 'redoclyDomain.com';
|
|
8
|
+
const testToken = 'test-token';
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
delete process.env.REDOCLY_DOMAIN;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should resolve the US domain by default', () => {
|
|
15
|
+
const client = new RedoclyClient();
|
|
16
|
+
expect(client.domain).toBe(REDOCLY_DOMAIN_US);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should resolve domain from RedoclyDomain env', () => {
|
|
20
|
+
process.env.REDOCLY_DOMAIN = testRedoclyDomain;
|
|
21
|
+
const client = new RedoclyClient();
|
|
22
|
+
expect(client.domain).toBe(testRedoclyDomain);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should resolve a domain by US region', () => {
|
|
26
|
+
const client = new RedoclyClient('us');
|
|
27
|
+
expect(client.domain).toBe(REDOCLY_DOMAIN_US);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should resolve a domain by EU region', () => {
|
|
31
|
+
const client = new RedoclyClient('eu');
|
|
32
|
+
expect(client.domain).toBe(REDOCLY_DOMAIN_EU);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should resolve domain by EU region prioritizing flag over env variable', () => {
|
|
36
|
+
process.env.REDOCLY_DOMAIN = testRedoclyDomain;
|
|
37
|
+
const client = new RedoclyClient('eu');
|
|
38
|
+
expect(client.domain).toBe(REDOCLY_DOMAIN_EU);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should resolve domain by US region prioritizing flag over env variable', () => {
|
|
42
|
+
process.env.REDOCLY_DOMAIN = testRedoclyDomain;
|
|
43
|
+
const client = new RedoclyClient('us');
|
|
44
|
+
expect(client.domain).toBe(REDOCLY_DOMAIN_US);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should resolve domain by US region when REDOCLY_DOMAIN consists EU domain', () => {
|
|
48
|
+
process.env.REDOCLY_DOMAIN = REDOCLY_DOMAIN_EU;
|
|
49
|
+
const client = new RedoclyClient();
|
|
50
|
+
expect(client.getRegion()).toBe('eu');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should resolve valid tokens data', async () => {
|
|
54
|
+
let spy = jest.spyOn(RedoclyClient.prototype, 'readCredentialsFile').mockImplementation(() => {
|
|
55
|
+
return { us: "accessToken", eu: "eu-accessToken" }
|
|
56
|
+
});
|
|
57
|
+
const client = new RedoclyClient();
|
|
58
|
+
const tokens = await client.getValidTokens();
|
|
59
|
+
expect(tokens).toStrictEqual([
|
|
60
|
+
{ region: 'us', token: 'accessToken', valid: true },
|
|
61
|
+
{ region: 'eu', token: 'eu-accessToken', valid: true }
|
|
62
|
+
]);
|
|
63
|
+
spy.mockRestore();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should not call setAccessTokens by default', () => {
|
|
67
|
+
let spy = jest.spyOn(RedoclyClient.prototype, 'readCredentialsFile').mockImplementation(() => ({}));
|
|
68
|
+
jest.spyOn(RedoclyClient.prototype, 'setAccessTokens').mockImplementation();
|
|
69
|
+
const client = new RedoclyClient();
|
|
70
|
+
expect(client.setAccessTokens).not.toHaveBeenCalled()
|
|
71
|
+
spy.mockRestore();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should set correct accessTokens - backward compatibility: default US region', () => {
|
|
75
|
+
let spy = jest.spyOn(RedoclyClient.prototype, 'readCredentialsFile').mockImplementation(() => ({ token: testToken }));
|
|
76
|
+
jest.spyOn(RedoclyClient.prototype, 'setAccessTokens').mockImplementation();
|
|
77
|
+
const client = new RedoclyClient();
|
|
78
|
+
expect(client.setAccessTokens).toBeCalledWith(
|
|
79
|
+
expect.objectContaining({ us: testToken })
|
|
80
|
+
);
|
|
81
|
+
spy.mockRestore();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should set correct accessTokens - backward compatibility: EU region', () => {
|
|
85
|
+
let spy = jest.spyOn(RedoclyClient.prototype, 'readCredentialsFile').mockImplementation(() => ({ token: testToken }));
|
|
86
|
+
jest.spyOn(RedoclyClient.prototype, 'setAccessTokens').mockImplementation();
|
|
87
|
+
const client = new RedoclyClient('eu');
|
|
88
|
+
expect(client.setAccessTokens).toBeCalledWith(
|
|
89
|
+
expect.objectContaining({ eu: testToken })
|
|
90
|
+
);
|
|
91
|
+
spy.mockRestore();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should set correct accessTokens - REDOCLY_AUTHORIZATION env', () => {
|
|
95
|
+
process.env.REDOCLY_AUTHORIZATION = REDOCLY_AUTHORIZATION_TOKEN;
|
|
96
|
+
let spy = jest.spyOn(RedoclyClient.prototype, 'readCredentialsFile').mockImplementation();
|
|
97
|
+
jest.spyOn(RedoclyClient.prototype, 'setAccessTokens').mockImplementation();
|
|
98
|
+
const client = new RedoclyClient();
|
|
99
|
+
expect(client.setAccessTokens).toHaveBeenNthCalledWith(1, { "us": REDOCLY_AUTHORIZATION_TOKEN });
|
|
100
|
+
spy.mockRestore();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should set correct accessTokens prioritizing REDOCLY_AUTHORIZATION env over token in file', () => {
|
|
104
|
+
process.env.REDOCLY_AUTHORIZATION = REDOCLY_AUTHORIZATION_TOKEN;
|
|
105
|
+
let spy = jest.spyOn(RedoclyClient.prototype, 'readCredentialsFile').mockImplementation(() => ({ token: testToken }));
|
|
106
|
+
jest.spyOn(RedoclyClient.prototype, 'setAccessTokens').mockImplementation();
|
|
107
|
+
const client = new RedoclyClient();
|
|
108
|
+
expect(client.setAccessTokens).toHaveBeenNthCalledWith(2, { "us": REDOCLY_AUTHORIZATION_TOKEN });
|
|
109
|
+
spy.mockRestore();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should set correct accessTokens prioritizing REDOCLY_AUTHORIZATION env over EU token', () => {
|
|
113
|
+
process.env.REDOCLY_AUTHORIZATION = REDOCLY_AUTHORIZATION_TOKEN;
|
|
114
|
+
let spy = jest.spyOn(RedoclyClient.prototype, 'readCredentialsFile').mockImplementation(() => ({ us: testToken }));
|
|
115
|
+
jest.spyOn(RedoclyClient.prototype, 'setAccessTokens').mockImplementation();
|
|
116
|
+
const client = new RedoclyClient('eu');
|
|
117
|
+
expect(client.setAccessTokens).toHaveBeenNthCalledWith(2, { "eu": REDOCLY_AUTHORIZATION_TOKEN });
|
|
118
|
+
spy.mockRestore();
|
|
119
|
+
});
|
|
120
|
+
});
|