@redocly/openapi-core 1.2.0 → 1.2.1
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/CHANGELOG.md +10 -0
- package/README.md +250 -7
- package/lib/bundle.d.ts +10 -4
- package/lib/bundle.js +10 -2
- package/lib/config/load.d.ts +3 -2
- package/lib/config/load.js +2 -2
- package/lib/config/types.d.ts +1 -0
- package/lib/index.d.ts +3 -2
- package/lib/index.js +5 -2
- package/lib/rules/common/assertions/asserts.js +12 -4
- package/lib/rules/oas3/no-server-example.com.js +3 -2
- package/lib/types/asyncapi.js +8 -1
- package/lib/types/oas3_1.js +8 -1
- package/package.json +3 -2
- package/src/__tests__/__snapshots__/bundle.test.ts.snap +1 -1
- package/src/__tests__/bundle.test.ts +65 -25
- package/src/__tests__/fixtures/lint/openapi.yaml +10 -0
- package/src/__tests__/fixtures/redocly.yaml +2 -0
- package/src/__tests__/lint.test.ts +31 -2
- package/src/bundle.ts +35 -7
- package/src/config/__tests__/load.test.ts +35 -2
- package/src/config/load.ts +11 -4
- package/src/config/types.ts +3 -0
- package/src/index.ts +3 -1
- package/src/rules/common/assertions/asserts.ts +16 -4
- package/src/rules/oas3/__tests__/no-server-example.com.test.ts +36 -1
- package/src/rules/oas3/__tests__/spec/spec.test.ts +1 -1
- package/src/rules/oas3/no-server-example.com.ts +3 -2
- package/src/types/asyncapi.ts +7 -1
- package/src/types/oas3_1.ts +7 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
2
|
import { outdent } from 'outdent';
|
|
3
3
|
|
|
4
|
-
import { lintFromString, lintConfig, lintDocument } from '../lint';
|
|
4
|
+
import { lintFromString, lintConfig, lintDocument, lint } from '../lint';
|
|
5
5
|
import { BaseResolver } from '../resolve';
|
|
6
6
|
import { loadConfig } from '../config/load';
|
|
7
7
|
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../__tests__/utils';
|
|
@@ -20,7 +20,7 @@ describe('lint', () => {
|
|
|
20
20
|
license: Fail
|
|
21
21
|
|
|
22
22
|
servers:
|
|
23
|
-
- url: http://example.com
|
|
23
|
+
- url: http://redocly-example.com
|
|
24
24
|
paths: {}
|
|
25
25
|
`,
|
|
26
26
|
config: await loadConfig(),
|
|
@@ -46,6 +46,35 @@ describe('lint', () => {
|
|
|
46
46
|
`);
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
it('lint should work', async () => {
|
|
50
|
+
const results = await lint({
|
|
51
|
+
ref: path.join(__dirname, 'fixtures/lint/openapi.yaml'),
|
|
52
|
+
config: await loadConfig({
|
|
53
|
+
configPath: path.join(__dirname, 'fixtures/redocly.yaml'),
|
|
54
|
+
}),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(replaceSourceWithRef(results, path.join(__dirname, 'fixtures/lint/')))
|
|
58
|
+
.toMatchInlineSnapshot(`
|
|
59
|
+
Array [
|
|
60
|
+
Object {
|
|
61
|
+
"from": undefined,
|
|
62
|
+
"location": Array [
|
|
63
|
+
Object {
|
|
64
|
+
"pointer": "#/info/license",
|
|
65
|
+
"reportOnKey": false,
|
|
66
|
+
"source": "openapi.yaml",
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
"message": "Expected type \`License\` (object) but got \`string\`",
|
|
70
|
+
"ruleId": "spec",
|
|
71
|
+
"severity": "error",
|
|
72
|
+
"suggest": Array [],
|
|
73
|
+
},
|
|
74
|
+
]
|
|
75
|
+
`);
|
|
76
|
+
});
|
|
77
|
+
|
|
49
78
|
it('lintConfig should work', async () => {
|
|
50
79
|
const document = parseYamlToDocument(
|
|
51
80
|
outdent`
|
package/src/bundle.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import isEqual = require('lodash.isequal');
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
BaseResolver,
|
|
4
|
+
resolveDocument,
|
|
5
|
+
Document,
|
|
6
|
+
ResolvedRefMap,
|
|
7
|
+
makeRefId,
|
|
8
|
+
makeDocumentFromString,
|
|
9
|
+
} from './resolve';
|
|
3
10
|
import { Oas3Rule, normalizeVisitors, Oas3Visitor, Oas2Visitor } from './visitors';
|
|
4
11
|
import { NormalizedNodeType, normalizeTypes, NodeType } from './types';
|
|
5
12
|
import { WalkContext, walkDocument, UserContext, ResolveResult, NormalizedProblem } from './walk';
|
|
@@ -22,10 +29,7 @@ export enum OasVersion {
|
|
|
22
29
|
Version3_0 = 'oas3_0',
|
|
23
30
|
Version3_1 = 'oas3_1',
|
|
24
31
|
}
|
|
25
|
-
|
|
26
|
-
export async function bundle(opts: {
|
|
27
|
-
ref?: string;
|
|
28
|
-
doc?: Document;
|
|
32
|
+
export type BundleOptions = {
|
|
29
33
|
externalRefResolver?: BaseResolver;
|
|
30
34
|
config: Config;
|
|
31
35
|
dereference?: boolean;
|
|
@@ -33,7 +37,14 @@ export async function bundle(opts: {
|
|
|
33
37
|
skipRedoclyRegistryRefs?: boolean;
|
|
34
38
|
removeUnusedComponents?: boolean;
|
|
35
39
|
keepUrlRefs?: boolean;
|
|
36
|
-
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export async function bundle(
|
|
43
|
+
opts: {
|
|
44
|
+
ref?: string;
|
|
45
|
+
doc?: Document;
|
|
46
|
+
} & BundleOptions
|
|
47
|
+
) {
|
|
37
48
|
const {
|
|
38
49
|
ref,
|
|
39
50
|
doc,
|
|
@@ -45,7 +56,7 @@ export async function bundle(opts: {
|
|
|
45
56
|
}
|
|
46
57
|
|
|
47
58
|
const document =
|
|
48
|
-
doc
|
|
59
|
+
doc === undefined ? await externalRefResolver.resolveDocument(base, ref!, true) : doc;
|
|
49
60
|
|
|
50
61
|
if (document instanceof Error) {
|
|
51
62
|
throw document;
|
|
@@ -59,6 +70,23 @@ export async function bundle(opts: {
|
|
|
59
70
|
});
|
|
60
71
|
}
|
|
61
72
|
|
|
73
|
+
export async function bundleFromString(
|
|
74
|
+
opts: {
|
|
75
|
+
source: string;
|
|
76
|
+
absoluteRef?: string;
|
|
77
|
+
} & BundleOptions
|
|
78
|
+
) {
|
|
79
|
+
const { source, absoluteRef, externalRefResolver = new BaseResolver(opts.config.resolve) } = opts;
|
|
80
|
+
const document = makeDocumentFromString(source, absoluteRef || '/');
|
|
81
|
+
|
|
82
|
+
return bundleDocument({
|
|
83
|
+
document,
|
|
84
|
+
...opts,
|
|
85
|
+
externalRefResolver,
|
|
86
|
+
config: opts.config.styleguide,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
62
90
|
type BundleContext = WalkContext;
|
|
63
91
|
|
|
64
92
|
export type BundleResult = {
|
|
@@ -73,8 +73,8 @@ describe('findConfig', () => {
|
|
|
73
73
|
.spyOn(fs, 'existsSync')
|
|
74
74
|
.mockImplementation((name) => name === 'redocly.yaml' || name === '.redocly.yaml');
|
|
75
75
|
expect(findConfig).toThrow(`
|
|
76
|
-
Multiple configuration files are not allowed.
|
|
77
|
-
Found the following files: redocly.yaml, .redocly.yaml.
|
|
76
|
+
Multiple configuration files are not allowed.
|
|
77
|
+
Found the following files: redocly.yaml, .redocly.yaml.
|
|
78
78
|
Please use 'redocly.yaml' instead.
|
|
79
79
|
`);
|
|
80
80
|
});
|
|
@@ -124,6 +124,39 @@ describe('createConfig', () => {
|
|
|
124
124
|
overridesRules: rawConfig.rules as Record<string, RuleConfig>,
|
|
125
125
|
});
|
|
126
126
|
});
|
|
127
|
+
|
|
128
|
+
it('should create config from object with a custom plugin', async () => {
|
|
129
|
+
const testCustomRule = jest.fn();
|
|
130
|
+
const rawConfig: FlatRawConfig = {
|
|
131
|
+
extends: [],
|
|
132
|
+
plugins: [
|
|
133
|
+
{
|
|
134
|
+
id: 'my-plugin',
|
|
135
|
+
rules: {
|
|
136
|
+
oas3: {
|
|
137
|
+
'test-rule': testCustomRule,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
rules: {
|
|
143
|
+
'my-plugin/test-rule': 'error',
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
const config = await createConfig(rawConfig);
|
|
147
|
+
|
|
148
|
+
expect(config.styleguide.plugins[0]).toEqual({
|
|
149
|
+
id: 'my-plugin',
|
|
150
|
+
rules: {
|
|
151
|
+
oas3: {
|
|
152
|
+
'my-plugin/test-rule': testCustomRule,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
expect(config.styleguide.rules.oas3_0).toEqual({
|
|
157
|
+
'my-plugin/test-rule': 'error',
|
|
158
|
+
});
|
|
159
|
+
});
|
|
127
160
|
});
|
|
128
161
|
|
|
129
162
|
function verifyExtendedConfig(
|
package/src/config/load.ts
CHANGED
|
@@ -7,7 +7,13 @@ import { Config, DOMAINS } from './config';
|
|
|
7
7
|
import { transformConfig } from './utils';
|
|
8
8
|
import { resolveConfig } from './config-resolvers';
|
|
9
9
|
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
DeprecatedInRawConfig,
|
|
12
|
+
FlatRawConfig,
|
|
13
|
+
RawConfig,
|
|
14
|
+
RawUniversalConfig,
|
|
15
|
+
Region,
|
|
16
|
+
} from './types';
|
|
11
17
|
import { RegionalTokenWithValidity } from '../redocly/redocly-client-types';
|
|
12
18
|
|
|
13
19
|
async function addConfigMetadata({
|
|
@@ -101,8 +107,8 @@ export function findConfig(dir?: string): string | undefined {
|
|
|
101
107
|
).filter(fs.existsSync);
|
|
102
108
|
if (existingConfigFiles.length > 1) {
|
|
103
109
|
throw new Error(`
|
|
104
|
-
Multiple configuration files are not allowed.
|
|
105
|
-
Found the following files: ${existingConfigFiles.join(', ')}.
|
|
110
|
+
Multiple configuration files are not allowed.
|
|
111
|
+
Found the following files: ${existingConfigFiles.join(', ')}.
|
|
106
112
|
Please use 'redocly.yaml' instead.
|
|
107
113
|
`);
|
|
108
114
|
}
|
|
@@ -129,10 +135,11 @@ export async function getConfig(
|
|
|
129
135
|
type CreateConfigOptions = {
|
|
130
136
|
extends?: string[];
|
|
131
137
|
tokens?: RegionalTokenWithValidity[];
|
|
138
|
+
configPath?: string;
|
|
132
139
|
};
|
|
133
140
|
|
|
134
141
|
export async function createConfig(
|
|
135
|
-
config: string |
|
|
142
|
+
config: string | RawUniversalConfig,
|
|
136
143
|
options?: CreateConfigOptions
|
|
137
144
|
): Promise<Config> {
|
|
138
145
|
return addConfigMetadata({
|
package/src/config/types.ts
CHANGED
|
@@ -185,6 +185,9 @@ export type RawConfig = {
|
|
|
185
185
|
telemetry?: Telemetry;
|
|
186
186
|
} & ThemeConfig;
|
|
187
187
|
|
|
188
|
+
// RawConfig is legacy, use RawUniversalConfig in public APIs
|
|
189
|
+
export type RawUniversalConfig = Omit<RawConfig, 'styleguide'> & StyleguideRawConfig;
|
|
190
|
+
|
|
188
191
|
export type FlatApi = Omit<Api, 'styleguide'> &
|
|
189
192
|
Omit<ApiStyleguideRawConfig, 'doNotResolveExamples'>;
|
|
190
193
|
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { BundleOutputFormat, readFileFromUrl, slash, doesYamlFileExist, isTruthy
|
|
|
2
2
|
export { Oas3_1Types } from './types/oas3_1';
|
|
3
3
|
export { Oas3Types } from './types/oas3';
|
|
4
4
|
export { Oas2Types } from './types/oas2';
|
|
5
|
+
export { AsyncApi2Types } from './types/asyncapi';
|
|
5
6
|
export { ConfigTypes } from './types/redocly-yaml';
|
|
6
7
|
export type {
|
|
7
8
|
Oas3Definition,
|
|
@@ -26,6 +27,7 @@ export {
|
|
|
26
27
|
Config,
|
|
27
28
|
StyleguideConfig,
|
|
28
29
|
RawConfig,
|
|
30
|
+
RawUniversalConfig,
|
|
29
31
|
IGNORE_FILE,
|
|
30
32
|
Region,
|
|
31
33
|
getMergedConfig,
|
|
@@ -74,4 +76,4 @@ export {
|
|
|
74
76
|
export { getAstNodeByPointer, getLineColLocation } from './format/codeframes';
|
|
75
77
|
export { formatProblems, OutputFormat, getTotals, Totals } from './format/format';
|
|
76
78
|
export { lint, lint as validate, lintDocument, lintFromString, lintConfig } from './lint';
|
|
77
|
-
export { bundle, bundleDocument, mapTypeToComponent } from './bundle';
|
|
79
|
+
export { bundle, bundleDocument, mapTypeToComponent, bundleFromString } from './bundle';
|
|
@@ -66,7 +66,11 @@ export const runOnValuesSet = new Set<keyof Asserts>([
|
|
|
66
66
|
]);
|
|
67
67
|
|
|
68
68
|
export const asserts: Asserts = {
|
|
69
|
-
pattern: (
|
|
69
|
+
pattern: (
|
|
70
|
+
value: string | string[],
|
|
71
|
+
condition: string,
|
|
72
|
+
{ baseLocation, rawValue }: AssertionFnContext
|
|
73
|
+
) => {
|
|
70
74
|
if (typeof value === 'undefined' || isPlainObject(value)) return []; // property doesn't exist or is an object, no need to lint it with this assert
|
|
71
75
|
const values = Array.isArray(value) ? value : [value];
|
|
72
76
|
const regex = regexFromString(condition);
|
|
@@ -76,7 +80,11 @@ export const asserts: Asserts = {
|
|
|
76
80
|
(_val) =>
|
|
77
81
|
!regex?.test(_val) && {
|
|
78
82
|
message: `"${_val}" should match a regex ${condition}`,
|
|
79
|
-
location: runOnValue(value)
|
|
83
|
+
location: runOnValue(value)
|
|
84
|
+
? baseLocation
|
|
85
|
+
: isPlainObject(rawValue)
|
|
86
|
+
? baseLocation.child(_val).key()
|
|
87
|
+
: baseLocation.key(),
|
|
80
88
|
}
|
|
81
89
|
)
|
|
82
90
|
.filter(isTruthy);
|
|
@@ -84,7 +92,7 @@ export const asserts: Asserts = {
|
|
|
84
92
|
notPattern: (
|
|
85
93
|
value: string | string[],
|
|
86
94
|
condition: string,
|
|
87
|
-
{ baseLocation }: AssertionFnContext
|
|
95
|
+
{ baseLocation, rawValue }: AssertionFnContext
|
|
88
96
|
) => {
|
|
89
97
|
if (typeof value === 'undefined' || isPlainObject(value)) return []; // property doesn't exist or is an object, no need to lint it with this assert
|
|
90
98
|
const values = Array.isArray(value) ? value : [value];
|
|
@@ -95,7 +103,11 @@ export const asserts: Asserts = {
|
|
|
95
103
|
(_val) =>
|
|
96
104
|
regex?.test(_val) && {
|
|
97
105
|
message: `"${_val}" should not match a regex ${condition}`,
|
|
98
|
-
location: runOnValue(value)
|
|
106
|
+
location: runOnValue(value)
|
|
107
|
+
? baseLocation
|
|
108
|
+
: isPlainObject(rawValue)
|
|
109
|
+
? baseLocation.child(_val).key()
|
|
110
|
+
: baseLocation.key(),
|
|
99
111
|
}
|
|
100
112
|
)
|
|
101
113
|
.filter(isTruthy);
|
|
@@ -30,7 +30,7 @@ describe('Oas3 oas3-no-server-example.com', () => {
|
|
|
30
30
|
"source": "foobar.yaml",
|
|
31
31
|
},
|
|
32
32
|
],
|
|
33
|
-
"message": "Server \`url\` should not point
|
|
33
|
+
"message": "Server \`url\` should not point to example.com or localhost.",
|
|
34
34
|
"ruleId": "no-server-example.com",
|
|
35
35
|
"severity": "error",
|
|
36
36
|
"suggest": Array [],
|
|
@@ -57,4 +57,39 @@ describe('Oas3 oas3-no-server-example.com', () => {
|
|
|
57
57
|
|
|
58
58
|
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
|
|
59
59
|
});
|
|
60
|
+
|
|
61
|
+
it('oas3-no-server-example.com: should report on server object with "foo.example.com" url', async () => {
|
|
62
|
+
const document = parseYamlToDocument(
|
|
63
|
+
outdent`
|
|
64
|
+
openapi: 3.0.0
|
|
65
|
+
servers:
|
|
66
|
+
- url: foo.example.com
|
|
67
|
+
`,
|
|
68
|
+
'foobar.yaml'
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const results = await lintDocument({
|
|
72
|
+
externalRefResolver: new BaseResolver(),
|
|
73
|
+
document,
|
|
74
|
+
config: await makeConfig({ 'no-server-example.com': 'error' }),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
78
|
+
Array [
|
|
79
|
+
Object {
|
|
80
|
+
"location": Array [
|
|
81
|
+
Object {
|
|
82
|
+
"pointer": "#/servers/0/url",
|
|
83
|
+
"reportOnKey": false,
|
|
84
|
+
"source": "foobar.yaml",
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
"message": "Server \`url\` should not point to example.com or localhost.",
|
|
88
|
+
"ruleId": "no-server-example.com",
|
|
89
|
+
"severity": "error",
|
|
90
|
+
"suggest": Array [],
|
|
91
|
+
},
|
|
92
|
+
]
|
|
93
|
+
`);
|
|
94
|
+
});
|
|
60
95
|
});
|
|
@@ -3,9 +3,10 @@ import { Oas3Rule } from '../../visitors';
|
|
|
3
3
|
export const NoServerExample: Oas3Rule = () => {
|
|
4
4
|
return {
|
|
5
5
|
Server(server, { report, location }) {
|
|
6
|
-
|
|
6
|
+
const pattern = /^(.*[\/.])?(example\.com|localhost)([\/:?].*|$)/;
|
|
7
|
+
if (server.url && pattern.test(server.url)) {
|
|
7
8
|
report({
|
|
8
|
-
message: 'Server `url` should not point
|
|
9
|
+
message: 'Server `url` should not point to example.com or localhost.',
|
|
9
10
|
location: location.child(['url']),
|
|
10
11
|
});
|
|
11
12
|
}
|
package/src/types/asyncapi.ts
CHANGED
|
@@ -390,7 +390,13 @@ const Schema: NodeType = {
|
|
|
390
390
|
maxContains: { type: 'integer', minimum: 0 },
|
|
391
391
|
patternProperties: { type: 'object' },
|
|
392
392
|
propertyNames: 'Schema',
|
|
393
|
-
unevaluatedItems:
|
|
393
|
+
unevaluatedItems: (value: unknown) => {
|
|
394
|
+
if (typeof value === 'boolean') {
|
|
395
|
+
return { type: 'boolean' };
|
|
396
|
+
} else {
|
|
397
|
+
return 'Schema';
|
|
398
|
+
}
|
|
399
|
+
},
|
|
394
400
|
unevaluatedProperties: (value: unknown) => {
|
|
395
401
|
if (typeof value === 'boolean') {
|
|
396
402
|
return { type: 'boolean' };
|
package/src/types/oas3_1.ts
CHANGED
|
@@ -137,7 +137,13 @@ const Schema: NodeType = {
|
|
|
137
137
|
maxContains: { type: 'integer', minimum: 0 },
|
|
138
138
|
patternProperties: { type: 'object' },
|
|
139
139
|
propertyNames: 'Schema',
|
|
140
|
-
unevaluatedItems:
|
|
140
|
+
unevaluatedItems: (value: unknown) => {
|
|
141
|
+
if (typeof value === 'boolean') {
|
|
142
|
+
return { type: 'boolean' };
|
|
143
|
+
} else {
|
|
144
|
+
return 'Schema';
|
|
145
|
+
}
|
|
146
|
+
},
|
|
141
147
|
unevaluatedProperties: (value: unknown) => {
|
|
142
148
|
if (typeof value === 'boolean') {
|
|
143
149
|
return { type: 'boolean' };
|