@redocly/openapi-core 1.0.0-beta.107 → 1.0.0-beta.108
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/benchmark/benches/lint-with-top-level-rule-report.bench.js +0 -1
- package/lib/bundle.js +5 -2
- package/lib/config/all.js +2 -2
- package/lib/config/config-resolvers.js +31 -13
- package/lib/config/config.d.ts +3 -5
- package/lib/config/config.js +7 -4
- package/lib/config/load.d.ts +7 -0
- package/lib/config/load.js +14 -6
- package/lib/config/minimal.js +2 -2
- package/lib/config/recommended.js +2 -2
- package/lib/config/rules.d.ts +1 -1
- package/lib/config/utils.js +5 -5
- package/lib/decorators/common/registry-dependencies.js +1 -1
- package/lib/env.d.ts +3 -0
- package/lib/env.js +8 -0
- package/lib/format/codeframes.js +16 -10
- package/lib/format/format.js +28 -26
- package/lib/index.d.ts +5 -5
- package/lib/index.js +3 -1
- package/lib/js-yaml/index.js +1 -0
- package/lib/logger.d.ts +10 -0
- package/lib/logger.js +31 -0
- package/lib/output.d.ts +3 -0
- package/lib/output.js +9 -0
- package/lib/redocly/index.js +10 -9
- package/lib/redocly/registry-api-types.d.ts +28 -30
- package/lib/redocly/registry-api.d.ts +4 -3
- package/lib/redocly/registry-api.js +7 -2
- package/lib/ref-utils.js +2 -1
- package/lib/resolve.d.ts +1 -1
- package/lib/resolve.js +1 -1
- package/lib/rules/ajv.js +1 -1
- package/lib/rules/common/assertions/asserts.js +4 -4
- package/lib/rules/common/assertions/index.js +1 -1
- package/lib/rules/common/operation-security-defined.js +1 -1
- package/lib/rules/common/spec.js +2 -2
- package/lib/rules/oas2/remove-unused-components.js +2 -2
- package/lib/rules/oas3/index.js +2 -2
- package/lib/rules/oas3/no-server-variables-empty-enum.d.ts +2 -0
- package/lib/rules/oas3/{no-servers-empty-enum.js → no-server-variables-empty-enum.js} +4 -4
- package/lib/rules/oas3/no-unused-components.js +1 -1
- package/lib/rules/oas3/remove-unused-components.js +3 -3
- package/lib/rules/utils.d.ts +1 -1
- package/lib/rules/utils.js +1 -1
- package/lib/types/redocly-yaml.js +1 -1
- package/lib/utils.d.ts +3 -0
- package/lib/utils.js +15 -7
- package/lib/visitors.d.ts +1 -1
- package/lib/visitors.js +2 -2
- package/package.json +1 -1
- package/src/__tests__/logger-browser.test.ts +53 -0
- package/src/__tests__/logger.test.ts +47 -0
- package/src/__tests__/output-browser.test.ts +18 -0
- package/src/__tests__/output.test.ts +15 -0
- package/src/__tests__/utils-browser.test.ts +11 -0
- package/src/__tests__/utils.test.ts +7 -0
- package/src/benchmark/benches/lint-with-top-level-rule-report.bench.ts +0 -1
- package/src/bundle.ts +6 -3
- package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +4 -4
- package/src/config/__tests__/config.test.ts +35 -0
- package/src/config/__tests__/load.test.ts +79 -1
- package/src/config/all.ts +2 -2
- package/src/config/config-resolvers.ts +43 -17
- package/src/config/config.ts +10 -8
- package/src/config/load.ts +28 -5
- package/src/config/minimal.ts +2 -2
- package/src/config/recommended.ts +2 -2
- package/src/config/utils.ts +6 -5
- package/src/decorators/common/registry-dependencies.ts +1 -1
- package/src/env.ts +5 -0
- package/src/format/codeframes.ts +15 -9
- package/src/format/format.ts +28 -33
- package/src/index.ts +6 -4
- package/src/js-yaml/index.ts +1 -0
- package/src/logger.ts +34 -0
- package/src/output.ts +7 -0
- package/src/redocly/index.ts +7 -4
- package/src/redocly/registry-api-types.ts +27 -29
- package/src/redocly/registry-api.ts +16 -6
- package/src/ref-utils.ts +2 -1
- package/src/resolve.ts +4 -4
- package/src/rules/ajv.ts +1 -1
- package/src/rules/common/assertions/asserts.ts +4 -4
- package/src/rules/common/assertions/index.ts +1 -1
- package/src/rules/common/operation-security-defined.ts +1 -1
- package/src/rules/common/spec.ts +2 -2
- package/src/rules/oas2/remove-unused-components.ts +2 -2
- package/src/rules/oas3/__tests__/no-empty-enum-servers.com.test.ts +16 -16
- package/src/rules/oas3/index.ts +2 -2
- package/src/rules/oas3/{no-servers-empty-enum.ts → no-server-variables-empty-enum.ts} +2 -2
- package/src/rules/oas3/no-unused-components.ts +1 -1
- package/src/rules/oas3/remove-unused-components.ts +4 -4
- package/src/rules/utils.ts +1 -1
- package/src/types/redocly-yaml.ts +1 -1
- package/src/utils.ts +16 -6
- package/src/visitors.ts +5 -5
- package/tsconfig.tsbuildinfo +1 -1
- package/lib/rules/oas3/no-servers-empty-enum.d.ts +0 -2
package/src/logger.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as colorette from 'colorette';
|
|
2
|
+
export { options as colorOptions } from 'colorette';
|
|
3
|
+
|
|
4
|
+
import { isBrowser } from './env';
|
|
5
|
+
import { identity } from './utils';
|
|
6
|
+
|
|
7
|
+
export const colorize = new Proxy(colorette, {
|
|
8
|
+
get(target: typeof colorette, prop: string): typeof identity {
|
|
9
|
+
if (isBrowser) {
|
|
10
|
+
return identity;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return (target as any)[prop];
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
class Logger {
|
|
17
|
+
protected stderr(str: string) {
|
|
18
|
+
return process.stderr.write(str);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
info(str: string) {
|
|
22
|
+
return isBrowser ? console.log(str) : this.stderr(str);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
warn(str: string) {
|
|
26
|
+
return isBrowser ? console.warn(str) : this.stderr(colorize.yellow(str));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
error(str: string) {
|
|
30
|
+
return isBrowser ? console.error(str) : this.stderr(colorize.red(str));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const logger = new Logger();
|
package/src/output.ts
ADDED
package/src/redocly/index.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
|
|
2
2
|
import { resolve } from 'path';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
|
-
import { green } from 'colorette';
|
|
5
4
|
import { RegistryApi } from './registry-api';
|
|
6
|
-
import { DEFAULT_REGION, DOMAINS, AVAILABLE_REGIONS
|
|
5
|
+
import { DEFAULT_REGION, DOMAINS, AVAILABLE_REGIONS } from '../config/config';
|
|
6
|
+
import { env } from '../env';
|
|
7
7
|
import { RegionalToken, RegionalTokenWithValidity } from './redocly-client-types';
|
|
8
8
|
import { isNotEmptyObject } from '../utils';
|
|
9
|
+
import { colorize } from '../logger';
|
|
9
10
|
|
|
10
11
|
import type { AccessTokens, Region } from '../config/types';
|
|
11
12
|
|
|
@@ -29,7 +30,9 @@ export class RedoclyClient {
|
|
|
29
30
|
loadRegion(region?: Region) {
|
|
30
31
|
if (region && !DOMAINS[region]) {
|
|
31
32
|
throw new Error(
|
|
32
|
-
`Invalid argument: region in config file.\nGiven: ${green(
|
|
33
|
+
`Invalid argument: region in config file.\nGiven: ${colorize.green(
|
|
34
|
+
region
|
|
35
|
+
)}, choices: "us", "eu".`
|
|
33
36
|
);
|
|
34
37
|
}
|
|
35
38
|
|
|
@@ -152,7 +155,7 @@ export class RedoclyClient {
|
|
|
152
155
|
|
|
153
156
|
const credentials = {
|
|
154
157
|
...this.readCredentialsFile(credentialsPath),
|
|
155
|
-
[this.region
|
|
158
|
+
[this.region]: accessToken,
|
|
156
159
|
token: accessToken, // FIXME: backward compatibility, remove on 1.0.0
|
|
157
160
|
};
|
|
158
161
|
this.accessTokens = credentials;
|
|
@@ -1,34 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
1
|
+
interface VersionParams {
|
|
2
|
+
organizationId: string;
|
|
3
|
+
name: string;
|
|
4
|
+
version: string;
|
|
5
|
+
}
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
export interface PrepareFileuploadParams extends VersionParams {
|
|
8
|
+
filesHash: string;
|
|
9
|
+
filename: string;
|
|
10
|
+
isUpsert?: boolean;
|
|
11
|
+
}
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
export interface PushApiParams extends VersionParams {
|
|
14
|
+
rootFilePath: string;
|
|
15
|
+
filePaths: string[];
|
|
16
|
+
branch?: string;
|
|
17
|
+
isUpsert?: boolean;
|
|
18
|
+
isPublic?: boolean;
|
|
19
|
+
batchId?: string;
|
|
20
|
+
batchSize?: number;
|
|
21
|
+
}
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
export interface PrepareFileuploadOKResponse {
|
|
24
|
+
filePath: string;
|
|
25
|
+
signedUploadUrl: string;
|
|
26
|
+
}
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
28
|
+
export interface NotFoundProblemResponse {
|
|
29
|
+
status: 404;
|
|
30
|
+
title: 'Not Found';
|
|
31
|
+
code: 'ORGANIZATION_NOT_FOUND' | 'API_VERSION_NOT_FOUND';
|
|
34
32
|
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import fetch, { RequestInit, HeadersInit } from 'node-fetch';
|
|
2
|
-
import {
|
|
2
|
+
import type {
|
|
3
|
+
NotFoundProblemResponse,
|
|
4
|
+
PrepareFileuploadOKResponse,
|
|
5
|
+
PrepareFileuploadParams,
|
|
6
|
+
PushApiParams,
|
|
7
|
+
} from './registry-api-types';
|
|
8
|
+
import type { AccessTokens, Region } from '../config/types';
|
|
3
9
|
import { DEFAULT_REGION, DOMAINS } from '../config/config';
|
|
4
10
|
import { isNotEmptyObject } from '../utils';
|
|
5
11
|
const version = require('../../package.json').version;
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+
export const currentCommand =
|
|
14
|
+
typeof process !== 'undefined' ? process.env?.REDOCLY_CLI_COMMAND || '' : '';
|
|
8
15
|
|
|
9
16
|
export class RegistryApi {
|
|
10
17
|
constructor(private accessTokens: AccessTokens, private region: Region) {}
|
|
@@ -23,7 +30,10 @@ export class RegistryApi {
|
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
private async request(path = '', options: RequestInit = {}, region?: Region) {
|
|
26
|
-
const headers = Object.assign({}, options.headers || {}, {
|
|
33
|
+
const headers = Object.assign({}, options.headers || {}, {
|
|
34
|
+
'x-redocly-cli-version': version,
|
|
35
|
+
'user-agent': `redocly-cli / ${version} ${currentCommand}`,
|
|
36
|
+
});
|
|
27
37
|
|
|
28
38
|
if (!headers.hasOwnProperty('authorization')) {
|
|
29
39
|
throw new Error('Unauthorized');
|
|
@@ -39,7 +49,7 @@ export class RegistryApi {
|
|
|
39
49
|
}
|
|
40
50
|
|
|
41
51
|
if (response.status === 404) {
|
|
42
|
-
const body:
|
|
52
|
+
const body: NotFoundProblemResponse = await response.json();
|
|
43
53
|
throw new Error(body.code);
|
|
44
54
|
}
|
|
45
55
|
|
|
@@ -71,7 +81,7 @@ export class RegistryApi {
|
|
|
71
81
|
filesHash,
|
|
72
82
|
filename,
|
|
73
83
|
isUpsert,
|
|
74
|
-
}:
|
|
84
|
+
}: PrepareFileuploadParams): Promise<PrepareFileuploadOKResponse> {
|
|
75
85
|
const response = await this.request(
|
|
76
86
|
`/${organizationId}/${name}/${version}/prepare-file-upload`,
|
|
77
87
|
{
|
|
@@ -107,7 +117,7 @@ export class RegistryApi {
|
|
|
107
117
|
isPublic,
|
|
108
118
|
batchId,
|
|
109
119
|
batchSize,
|
|
110
|
-
}:
|
|
120
|
+
}: PushApiParams) {
|
|
111
121
|
const response = await this.request(
|
|
112
122
|
`/${organizationId}/${name}/${version}`,
|
|
113
123
|
{
|
package/src/ref-utils.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Source } from './resolve';
|
|
2
2
|
import { OasRef } from './typings/openapi';
|
|
3
|
+
import { isTruthy } from './utils';
|
|
3
4
|
|
|
4
5
|
export function joinPointer(base: string, key: string | number) {
|
|
5
6
|
if (base === '') base = '#/';
|
|
@@ -45,7 +46,7 @@ export function parseRef(ref: string): { uri: string | null; pointer: string[] }
|
|
|
45
46
|
const [uri, pointer] = ref.split('#/');
|
|
46
47
|
return {
|
|
47
48
|
uri: uri || null,
|
|
48
|
-
pointer: pointer ? pointer.split('/').map(unescapePointer).filter(
|
|
49
|
+
pointer: pointer ? pointer.split('/').map(unescapePointer).filter(isTruthy) : [],
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
52
|
|
package/src/resolve.ts
CHANGED
|
@@ -91,7 +91,7 @@ export function makeDocumentFromString(sourceString: string, absoluteRef: string
|
|
|
91
91
|
export class BaseResolver {
|
|
92
92
|
cache: Map<string, Promise<Document | ResolveError>> = new Map();
|
|
93
93
|
|
|
94
|
-
constructor(
|
|
94
|
+
constructor(protected config: ResolveConfig = { http: { headers: [] } }) {}
|
|
95
95
|
|
|
96
96
|
getFiles() {
|
|
97
97
|
return new Set(Array.from(this.cache.keys()));
|
|
@@ -323,7 +323,7 @@ export async function resolveDocument(opts: {
|
|
|
323
323
|
: document;
|
|
324
324
|
} catch (error) {
|
|
325
325
|
const resolvedRef = {
|
|
326
|
-
resolved: false as
|
|
326
|
+
resolved: false as const,
|
|
327
327
|
isRemote,
|
|
328
328
|
document: undefined,
|
|
329
329
|
error: error,
|
|
@@ -334,7 +334,7 @@ export async function resolveDocument(opts: {
|
|
|
334
334
|
}
|
|
335
335
|
|
|
336
336
|
let resolvedRef: ResolvedRef = {
|
|
337
|
-
resolved: true as
|
|
337
|
+
resolved: true as const,
|
|
338
338
|
document: targetDoc,
|
|
339
339
|
isRemote,
|
|
340
340
|
node: document.parsed,
|
|
@@ -344,7 +344,7 @@ export async function resolveDocument(opts: {
|
|
|
344
344
|
let target = targetDoc.parsed as any;
|
|
345
345
|
|
|
346
346
|
const segments = pointer;
|
|
347
|
-
for (
|
|
347
|
+
for (const segment of segments) {
|
|
348
348
|
if (typeof target !== 'object') {
|
|
349
349
|
target = undefined;
|
|
350
350
|
break;
|
package/src/rules/ajv.ts
CHANGED
|
@@ -73,7 +73,7 @@ export function validateJsonSchema(
|
|
|
73
73
|
|
|
74
74
|
function beatifyErrorMessage(error: ErrorObject) {
|
|
75
75
|
let message = error.message;
|
|
76
|
-
|
|
76
|
+
const suggest = error.keyword === 'enum' ? error.params.allowedValues : undefined;
|
|
77
77
|
if (suggest) {
|
|
78
78
|
message += ` ${suggest.map((e: any) => `"${e}"`).join(', ')}`;
|
|
79
79
|
}
|
|
@@ -46,7 +46,7 @@ export const asserts: Asserts = {
|
|
|
46
46
|
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
|
47
47
|
const values = runOnValue(value) ? [value] : value;
|
|
48
48
|
const regx = regexFromString(condition);
|
|
49
|
-
for (
|
|
49
|
+
for (const _val of values) {
|
|
50
50
|
if (!regx?.test(_val)) {
|
|
51
51
|
return { isValid: false, location: runOnValue(value) ? baseLocation : baseLocation.key() };
|
|
52
52
|
}
|
|
@@ -56,7 +56,7 @@ export const asserts: Asserts = {
|
|
|
56
56
|
enum: (value: string | string[], condition: string[], baseLocation: Location) => {
|
|
57
57
|
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
|
58
58
|
const values = runOnValue(value) ? [value] : value;
|
|
59
|
-
for (
|
|
59
|
+
for (const _val of values) {
|
|
60
60
|
if (!condition.includes(_val)) {
|
|
61
61
|
return {
|
|
62
62
|
isValid: false,
|
|
@@ -81,7 +81,7 @@ export const asserts: Asserts = {
|
|
|
81
81
|
disallowed: (value: string | string[], condition: string[], baseLocation: Location) => {
|
|
82
82
|
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
|
83
83
|
const values = runOnValue(value) ? [value] : value;
|
|
84
|
-
for (
|
|
84
|
+
for (const _val of values) {
|
|
85
85
|
if (condition.includes(_val)) {
|
|
86
86
|
return {
|
|
87
87
|
isValid: false,
|
|
@@ -114,7 +114,7 @@ export const asserts: Asserts = {
|
|
|
114
114
|
casing: (value: string | string[], condition: string, baseLocation: Location) => {
|
|
115
115
|
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
|
116
116
|
const values: string[] = runOnValue(value) ? [value] : value;
|
|
117
|
-
for (
|
|
117
|
+
for (const _val of values) {
|
|
118
118
|
let matchCase = false;
|
|
119
119
|
switch (condition) {
|
|
120
120
|
case 'camelCase':
|
|
@@ -3,7 +3,7 @@ import { AssertToApply, buildSubjectVisitor, buildVisitorObject } from './utils'
|
|
|
3
3
|
import { Oas2Rule, Oas3Rule } from '../../../visitors';
|
|
4
4
|
|
|
5
5
|
export const Assertions: Oas3Rule | Oas2Rule = (opts: object) => {
|
|
6
|
-
|
|
6
|
+
const visitors: any[] = [];
|
|
7
7
|
|
|
8
8
|
// As 'Assertions' has an array of asserts,
|
|
9
9
|
// that array spreads into an 'opts' object on init rules phase here
|
|
@@ -5,7 +5,7 @@ import { Oas2SecurityScheme } from '../../typings/swagger';
|
|
|
5
5
|
import { Oas3SecurityScheme } from '../../typings/openapi';
|
|
6
6
|
|
|
7
7
|
export const OperationSecurityDefined: Oas3Rule | Oas2Rule = () => {
|
|
8
|
-
|
|
8
|
+
const referencedSchemes = new Map<
|
|
9
9
|
string,
|
|
10
10
|
{
|
|
11
11
|
defined?: boolean;
|
package/src/rules/common/spec.ts
CHANGED
|
@@ -28,7 +28,7 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
|
|
|
28
28
|
const required =
|
|
29
29
|
typeof type.required === 'function' ? type.required(node, key) : type.required;
|
|
30
30
|
|
|
31
|
-
for (
|
|
31
|
+
for (const propName of required || []) {
|
|
32
32
|
if (!(node as object).hasOwnProperty(propName)) {
|
|
33
33
|
report({
|
|
34
34
|
message: `The field \`${propName}\` must be present on this level.`,
|
|
@@ -57,7 +57,7 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
|
|
|
57
57
|
const requiredOneOf = type.requiredOneOf || null;
|
|
58
58
|
if (requiredOneOf) {
|
|
59
59
|
let hasProperty = false;
|
|
60
|
-
for (
|
|
60
|
+
for (const propName of requiredOneOf || []) {
|
|
61
61
|
if ((node as object).hasOwnProperty(propName)) {
|
|
62
62
|
hasProperty = true;
|
|
63
63
|
}
|
|
@@ -4,7 +4,7 @@ import { Oas2Components } from '../../typings/swagger';
|
|
|
4
4
|
import { isEmptyObject } from '../../utils';
|
|
5
5
|
|
|
6
6
|
export const RemoveUnusedComponents: Oas2Rule = () => {
|
|
7
|
-
|
|
7
|
+
const components = new Map<
|
|
8
8
|
string,
|
|
9
9
|
{ used: boolean; componentType?: keyof Oas2Components; name: string }
|
|
10
10
|
>();
|
|
@@ -39,7 +39,7 @@ export const RemoveUnusedComponents: Oas2Rule = () => {
|
|
|
39
39
|
const data = ctx.getVisitorData() as { removedCount: number };
|
|
40
40
|
data.removedCount = 0;
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
const rootComponents = new Set<keyof Oas2Components>();
|
|
43
43
|
components.forEach((usageInfo) => {
|
|
44
44
|
const { used, name, componentType } = usageInfo;
|
|
45
45
|
if (!used && componentType) {
|
|
@@ -3,8 +3,8 @@ import { lintDocument } from '../../../lint';
|
|
|
3
3
|
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
|
|
4
4
|
import { BaseResolver } from '../../../resolve';
|
|
5
5
|
|
|
6
|
-
describe('Oas3 as3-no-
|
|
7
|
-
it('oas3-no-
|
|
6
|
+
describe('Oas3 as3-no-server-variables-empty-enum', () => {
|
|
7
|
+
it('oas3-no-server-variables-empty-enum: should report on server object with empty enum and unknown enum value', async () => {
|
|
8
8
|
const document = parseYamlToDocument(
|
|
9
9
|
outdent`
|
|
10
10
|
openapi: 3.0.0
|
|
@@ -24,7 +24,7 @@ describe('Oas3 as3-no-servers-empty-enum', () => {
|
|
|
24
24
|
const results = await lintDocument({
|
|
25
25
|
externalRefResolver: new BaseResolver(),
|
|
26
26
|
document,
|
|
27
|
-
config: await makeConfig({ 'no-
|
|
27
|
+
config: await makeConfig({ 'no-server-variables-empty-enum': 'error' }),
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
@@ -38,7 +38,7 @@ describe('Oas3 as3-no-servers-empty-enum', () => {
|
|
|
38
38
|
},
|
|
39
39
|
],
|
|
40
40
|
"message": "Server variable with \`enum\` must be a non-empty array.",
|
|
41
|
-
"ruleId": "no-
|
|
41
|
+
"ruleId": "no-server-variables-empty-enum",
|
|
42
42
|
"severity": "error",
|
|
43
43
|
"suggest": Array [],
|
|
44
44
|
},
|
|
@@ -51,7 +51,7 @@ describe('Oas3 as3-no-servers-empty-enum', () => {
|
|
|
51
51
|
},
|
|
52
52
|
],
|
|
53
53
|
"message": "Server variable define \`enum\` and \`default\`. \`enum\` must include default value",
|
|
54
|
-
"ruleId": "no-
|
|
54
|
+
"ruleId": "no-server-variables-empty-enum",
|
|
55
55
|
"severity": "error",
|
|
56
56
|
"suggest": Array [],
|
|
57
57
|
},
|
|
@@ -59,7 +59,7 @@ describe('Oas3 as3-no-servers-empty-enum', () => {
|
|
|
59
59
|
`);
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
-
it('oas3-no-
|
|
62
|
+
it('oas3-no-server-variables-empty-enum: should report on server object with empty enum', async () => {
|
|
63
63
|
const document = parseYamlToDocument(
|
|
64
64
|
outdent`
|
|
65
65
|
openapi: 3.0.0
|
|
@@ -78,7 +78,7 @@ describe('Oas3 as3-no-servers-empty-enum', () => {
|
|
|
78
78
|
const results = await lintDocument({
|
|
79
79
|
externalRefResolver: new BaseResolver(),
|
|
80
80
|
document,
|
|
81
|
-
config: await makeConfig({ 'no-
|
|
81
|
+
config: await makeConfig({ 'no-server-variables-empty-enum': 'error' }),
|
|
82
82
|
});
|
|
83
83
|
|
|
84
84
|
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
@@ -92,7 +92,7 @@ describe('Oas3 as3-no-servers-empty-enum', () => {
|
|
|
92
92
|
},
|
|
93
93
|
],
|
|
94
94
|
"message": "Server variable with \`enum\` must be a non-empty array.",
|
|
95
|
-
"ruleId": "no-
|
|
95
|
+
"ruleId": "no-server-variables-empty-enum",
|
|
96
96
|
"severity": "error",
|
|
97
97
|
"suggest": Array [],
|
|
98
98
|
},
|
|
@@ -100,7 +100,7 @@ describe('Oas3 as3-no-servers-empty-enum', () => {
|
|
|
100
100
|
`);
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
-
it('oas3-no-
|
|
103
|
+
it('oas3-no-server-variables-empty-enum: should be success because variables is empty object', async () => {
|
|
104
104
|
const document = parseYamlToDocument(
|
|
105
105
|
outdent`
|
|
106
106
|
openapi: 3.0.0
|
|
@@ -117,13 +117,13 @@ describe('Oas3 as3-no-servers-empty-enum', () => {
|
|
|
117
117
|
const results = await lintDocument({
|
|
118
118
|
externalRefResolver: new BaseResolver(),
|
|
119
119
|
document,
|
|
120
|
-
config: await makeConfig({ 'no-
|
|
120
|
+
config: await makeConfig({ 'no-server-variables-empty-enum': 'error' }),
|
|
121
121
|
});
|
|
122
122
|
|
|
123
123
|
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
|
|
124
124
|
});
|
|
125
125
|
|
|
126
|
-
it('oas3-no-
|
|
126
|
+
it('oas3-no-server-variables-empty-enum: should be success because variable is empty object', async () => {
|
|
127
127
|
const document = parseYamlToDocument(
|
|
128
128
|
outdent`
|
|
129
129
|
openapi: 3.0.0
|
|
@@ -141,13 +141,13 @@ describe('Oas3 as3-no-servers-empty-enum', () => {
|
|
|
141
141
|
const results = await lintDocument({
|
|
142
142
|
externalRefResolver: new BaseResolver(),
|
|
143
143
|
document,
|
|
144
|
-
config: await makeConfig({ 'no-
|
|
144
|
+
config: await makeConfig({ 'no-server-variables-empty-enum': 'error' }),
|
|
145
145
|
});
|
|
146
146
|
|
|
147
147
|
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
-
it('oas3-no-
|
|
150
|
+
it('oas3-no-server-variables-empty-enum: should be success because enum contains default value', async () => {
|
|
151
151
|
const document = parseYamlToDocument(
|
|
152
152
|
outdent`
|
|
153
153
|
openapi: 3.0.0
|
|
@@ -168,13 +168,13 @@ describe('Oas3 as3-no-servers-empty-enum', () => {
|
|
|
168
168
|
const results = await lintDocument({
|
|
169
169
|
externalRefResolver: new BaseResolver(),
|
|
170
170
|
document,
|
|
171
|
-
config: await makeConfig({ 'no-
|
|
171
|
+
config: await makeConfig({ 'no-server-variables-empty-enum': 'error' }),
|
|
172
172
|
});
|
|
173
173
|
|
|
174
174
|
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
|
|
175
175
|
});
|
|
176
176
|
|
|
177
|
-
it('oas3-no-
|
|
177
|
+
it('oas3-no-server-variables-empty-enum: should be success because enum contains default value', async () => {
|
|
178
178
|
const document = parseYamlToDocument(
|
|
179
179
|
outdent`
|
|
180
180
|
openapi: 3.0.0
|
|
@@ -197,7 +197,7 @@ describe('Oas3 as3-no-servers-empty-enum', () => {
|
|
|
197
197
|
const results = await lintDocument({
|
|
198
198
|
externalRefResolver: new BaseResolver(),
|
|
199
199
|
document,
|
|
200
|
-
config: await makeConfig({ 'no-
|
|
200
|
+
config: await makeConfig({ 'no-server-variables-empty-enum': 'error' }),
|
|
201
201
|
});
|
|
202
202
|
|
|
203
203
|
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
|
package/src/rules/oas3/index.ts
CHANGED
|
@@ -37,7 +37,7 @@ import { NoUndefinedServerVariable } from './no-undefined-server-variable';
|
|
|
37
37
|
import { OperationOperationId } from '../common/operation-operationId';
|
|
38
38
|
import { OperationSummary } from '../common/operation-summary';
|
|
39
39
|
import { NoAmbiguousPaths } from '../common/no-ambiguous-paths';
|
|
40
|
-
import {
|
|
40
|
+
import { NoServerVariablesEmptyEnum } from './no-server-variables-empty-enum';
|
|
41
41
|
import { NoHttpVerbsInPaths } from '../common/no-http-verbs-in-paths';
|
|
42
42
|
import { RequestMimeType } from './request-mime-type';
|
|
43
43
|
import { ResponseMimeType } from './response-mime-type';
|
|
@@ -89,7 +89,7 @@ export const rules = {
|
|
|
89
89
|
'no-identical-paths': NoIdenticalPaths,
|
|
90
90
|
'no-ambiguous-paths': NoAmbiguousPaths,
|
|
91
91
|
'no-undefined-server-variable': NoUndefinedServerVariable,
|
|
92
|
-
'no-
|
|
92
|
+
'no-server-variables-empty-enum': NoServerVariablesEmptyEnum,
|
|
93
93
|
'no-http-verbs-in-paths': NoHttpVerbsInPaths,
|
|
94
94
|
'path-excludes-patterns': PathExcludesPatterns,
|
|
95
95
|
'request-mime-type': RequestMimeType,
|
|
@@ -6,7 +6,7 @@ enum enumError {
|
|
|
6
6
|
invalidDefaultValue = 'invalidDefaultValue',
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export const
|
|
9
|
+
export const NoServerVariablesEmptyEnum: Oas3Rule = () => {
|
|
10
10
|
return {
|
|
11
11
|
DefinitionRoot(root, { report, location }) {
|
|
12
12
|
if (!root.servers || root.servers.length === 0) return;
|
|
@@ -48,7 +48,7 @@ function checkEnumVariables(server: Oas3Server): enumError[] | undefined {
|
|
|
48
48
|
if (server.variables && Object.keys(server.variables).length === 0) return;
|
|
49
49
|
|
|
50
50
|
const errors: enumError[] = [];
|
|
51
|
-
for (
|
|
51
|
+
for (const variable in server.variables) {
|
|
52
52
|
const serverVariable = server.variables[variable];
|
|
53
53
|
if (!serverVariable.enum) continue;
|
|
54
54
|
|
|
@@ -2,7 +2,7 @@ import { Oas3Rule } from '../../visitors';
|
|
|
2
2
|
import { Location } from '../../ref-utils';
|
|
3
3
|
|
|
4
4
|
export const NoUnusedComponents: Oas3Rule = () => {
|
|
5
|
-
|
|
5
|
+
const components = new Map<string, { used: boolean; location: Location; name: string }>();
|
|
6
6
|
|
|
7
7
|
function registerComponent(location: Location, name: string): void {
|
|
8
8
|
components.set(location.absolutePointer, {
|
|
@@ -4,7 +4,7 @@ import { Oas3Components } from '../../typings/openapi';
|
|
|
4
4
|
import { isEmptyObject } from '../../utils';
|
|
5
5
|
|
|
6
6
|
export const RemoveUnusedComponents: Oas3Rule = () => {
|
|
7
|
-
|
|
7
|
+
const components = new Map<
|
|
8
8
|
string,
|
|
9
9
|
{ used: boolean; componentType?: keyof Oas3Components; name: string }
|
|
10
10
|
>();
|
|
@@ -45,12 +45,12 @@ export const RemoveUnusedComponents: Oas3Rule = () => {
|
|
|
45
45
|
|
|
46
46
|
components.forEach((usageInfo) => {
|
|
47
47
|
const { used, componentType, name } = usageInfo;
|
|
48
|
-
if (!used && componentType) {
|
|
49
|
-
|
|
48
|
+
if (!used && componentType && root.components) {
|
|
49
|
+
const componentChild = root.components[componentType];
|
|
50
50
|
delete componentChild![name];
|
|
51
51
|
data.removedCount++;
|
|
52
52
|
if (isEmptyObject(componentChild)) {
|
|
53
|
-
delete root.components
|
|
53
|
+
delete root.components[componentType];
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
});
|
package/src/rules/utils.ts
CHANGED
|
@@ -104,7 +104,7 @@ export function validateExample(
|
|
|
104
104
|
allowAdditionalProperties
|
|
105
105
|
);
|
|
106
106
|
if (!valid) {
|
|
107
|
-
for (
|
|
107
|
+
for (const error of errors) {
|
|
108
108
|
report({
|
|
109
109
|
message: `Example value must conform to the schema: ${error.message}.`,
|
|
110
110
|
location: {
|
package/src/utils.ts
CHANGED
|
@@ -6,8 +6,8 @@ import * as pluralize from 'pluralize';
|
|
|
6
6
|
import { parseYaml } from './js-yaml';
|
|
7
7
|
import { UserContext } from './walk';
|
|
8
8
|
import { HttpResolveConfig } from './config';
|
|
9
|
-
import { env } from './
|
|
10
|
-
import {
|
|
9
|
+
import { env } from './env';
|
|
10
|
+
import { logger, colorize } from './logger';
|
|
11
11
|
|
|
12
12
|
export { parseYaml, stringifyYaml } from './js-yaml';
|
|
13
13
|
|
|
@@ -96,7 +96,7 @@ export function omitObjectProps<T extends Record<string, unknown>>(
|
|
|
96
96
|
export function splitCamelCaseIntoWords(str: string) {
|
|
97
97
|
const camel = str
|
|
98
98
|
.split(/(?:[-._])|([A-Z][a-z]+)/)
|
|
99
|
-
.filter(
|
|
99
|
+
.filter(isTruthy)
|
|
100
100
|
.map((item) => item.toLocaleLowerCase());
|
|
101
101
|
const caps = str
|
|
102
102
|
.split(/([A-Z]{2,})/)
|
|
@@ -183,7 +183,7 @@ export function isNotString<T>(value: string | T): value is T {
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
export function assignExisting<T>(target: Record<string, T>, obj: Record<string, T>) {
|
|
186
|
-
for (
|
|
186
|
+
for (const k of Object.keys(obj)) {
|
|
187
187
|
if (target.hasOwnProperty(k)) {
|
|
188
188
|
target[k] = obj[k];
|
|
189
189
|
}
|
|
@@ -207,8 +207,8 @@ export function doesYamlFileExist(filePath: string): boolean {
|
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
export function showWarningForDeprecatedField(deprecatedField: string, updatedField: string) {
|
|
210
|
-
|
|
211
|
-
`The ${
|
|
210
|
+
logger.warn(
|
|
211
|
+
`The ${colorize.red(deprecatedField)} field is deprecated. Use ${colorize.green(
|
|
212
212
|
updatedField
|
|
213
213
|
)} instead. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties\n`
|
|
214
214
|
);
|
|
@@ -217,3 +217,13 @@ export function showWarningForDeprecatedField(deprecatedField: string, updatedFi
|
|
|
217
217
|
export function showErrorForDeprecatedField(deprecatedField: string, updatedField: string) {
|
|
218
218
|
throw new Error(`Do not use '${deprecatedField}' field. Use '${updatedField}' instead.\n`);
|
|
219
219
|
}
|
|
220
|
+
|
|
221
|
+
export type Falsy = undefined | null | false | '' | 0;
|
|
222
|
+
|
|
223
|
+
export function isTruthy<Truthy>(value: Truthy | Falsy): value is Truthy {
|
|
224
|
+
return !!value;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function identity<T>(value: T): T {
|
|
228
|
+
return value;
|
|
229
|
+
}
|