@redocly/openapi-core 1.27.2 → 1.28.0
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 +11 -0
- package/lib/index.d.ts +1 -1
- package/lib/redocly/registry-api.js +6 -2
- package/lib/rules/oas3/array-parameter-serialization.js +3 -4
- package/lib/types/arazzo.js +1 -1
- package/lib/types/oas3_1.js +2 -0
- package/lib/typings/arazzo.d.ts +1 -1
- package/lib/typings/openapi.d.ts +118 -66
- package/lib/utils.d.ts +3 -0
- package/lib/utils.js +1 -2
- package/lib/visitors.d.ts +22 -22
- package/package.json +4 -8
- package/src/__tests__/lint.test.ts +95 -0
- package/src/decorators/oas3/remove-unused-components.ts +16 -4
- package/src/index.ts +1 -1
- package/src/redocly/__tests__/redocly-client.test.ts +14 -6
- package/src/redocly/registry-api.ts +7 -6
- package/src/rules/common/operation-tag-defined.ts +2 -2
- package/src/rules/common/security-defined.ts +2 -1
- package/src/rules/common/tags-alphabetical.ts +5 -2
- package/src/rules/oas3/array-parameter-serialization.ts +9 -6
- package/src/rules/oas3/component-name-unique.ts +4 -2
- package/src/types/arazzo.ts +1 -1
- package/src/types/oas3_1.ts +2 -0
- package/src/typings/arazzo.ts +1 -1
- package/src/typings/openapi.ts +115 -71
- package/src/utils.ts +2 -1
- package/src/visitors.ts +26 -21
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1674,4 +1674,99 @@ describe('lint', () => {
|
|
|
1674
1674
|
|
|
1675
1675
|
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
|
1676
1676
|
});
|
|
1677
|
+
|
|
1678
|
+
it('should throw an error for $schema not expected here - OAS 3.0.x', async () => {
|
|
1679
|
+
const document = parseYamlToDocument(
|
|
1680
|
+
outdent`
|
|
1681
|
+
openapi: 3.0.4
|
|
1682
|
+
info:
|
|
1683
|
+
title: test json schema validation keyword $schema - should use an OAS Schema, not JSON Schema
|
|
1684
|
+
version: 1.0.0
|
|
1685
|
+
paths:
|
|
1686
|
+
'/thing':
|
|
1687
|
+
get:
|
|
1688
|
+
summary: a sample api
|
|
1689
|
+
responses:
|
|
1690
|
+
'200':
|
|
1691
|
+
description: OK
|
|
1692
|
+
content:
|
|
1693
|
+
'application/json':
|
|
1694
|
+
schema:
|
|
1695
|
+
$schema: http://json-schema.org/draft-04/schema#
|
|
1696
|
+
type: object
|
|
1697
|
+
properties: {}
|
|
1698
|
+
`,
|
|
1699
|
+
''
|
|
1700
|
+
);
|
|
1701
|
+
|
|
1702
|
+
const configFilePath = path.join(__dirname, '..', '..', '..', 'redocly.yaml');
|
|
1703
|
+
|
|
1704
|
+
const results = await lintDocument({
|
|
1705
|
+
externalRefResolver: new BaseResolver(),
|
|
1706
|
+
document,
|
|
1707
|
+
config: await makeConfig({
|
|
1708
|
+
rules: { spec: 'error' },
|
|
1709
|
+
decorators: undefined,
|
|
1710
|
+
configPath: configFilePath,
|
|
1711
|
+
}),
|
|
1712
|
+
});
|
|
1713
|
+
|
|
1714
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
1715
|
+
[
|
|
1716
|
+
{
|
|
1717
|
+
"from": undefined,
|
|
1718
|
+
"location": [
|
|
1719
|
+
{
|
|
1720
|
+
"pointer": "#/paths/~1thing/get/responses/200/content/application~1json/schema/$schema",
|
|
1721
|
+
"reportOnKey": true,
|
|
1722
|
+
"source": "",
|
|
1723
|
+
},
|
|
1724
|
+
],
|
|
1725
|
+
"message": "Property \`$schema\` is not expected here.",
|
|
1726
|
+
"ruleId": "struct",
|
|
1727
|
+
"severity": "error",
|
|
1728
|
+
"suggest": [],
|
|
1729
|
+
},
|
|
1730
|
+
]
|
|
1731
|
+
`);
|
|
1732
|
+
});
|
|
1733
|
+
|
|
1734
|
+
it('should allow for $schema to be defined - OAS 3.1.x', async () => {
|
|
1735
|
+
const document = parseYamlToDocument(
|
|
1736
|
+
outdent`
|
|
1737
|
+
openapi: 3.1.1
|
|
1738
|
+
info:
|
|
1739
|
+
title: test json schema validation keyword $schema - should allow a JSON Schema
|
|
1740
|
+
version: 1.0.0
|
|
1741
|
+
paths:
|
|
1742
|
+
'/thing':
|
|
1743
|
+
get:
|
|
1744
|
+
summary: a sample api
|
|
1745
|
+
responses:
|
|
1746
|
+
'200':
|
|
1747
|
+
description: OK
|
|
1748
|
+
content:
|
|
1749
|
+
'application/json':
|
|
1750
|
+
schema:
|
|
1751
|
+
$schema: http://json-schema.org/draft-04/schema#
|
|
1752
|
+
type: object
|
|
1753
|
+
properties: {}
|
|
1754
|
+
`,
|
|
1755
|
+
''
|
|
1756
|
+
);
|
|
1757
|
+
|
|
1758
|
+
const configFilePath = path.join(__dirname, '..', '..', '..', 'redocly.yaml');
|
|
1759
|
+
|
|
1760
|
+
const results = await lintDocument({
|
|
1761
|
+
externalRefResolver: new BaseResolver(),
|
|
1762
|
+
document,
|
|
1763
|
+
config: await makeConfig({
|
|
1764
|
+
rules: { spec: 'error' },
|
|
1765
|
+
decorators: undefined,
|
|
1766
|
+
configPath: configFilePath,
|
|
1767
|
+
}),
|
|
1768
|
+
});
|
|
1769
|
+
|
|
1770
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
|
1771
|
+
});
|
|
1677
1772
|
});
|
|
@@ -2,17 +2,26 @@ import { isEmptyObject } from '../../utils';
|
|
|
2
2
|
|
|
3
3
|
import type { Location } from '../../ref-utils';
|
|
4
4
|
import type { Oas3Decorator } from '../../visitors';
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
Oas3Definition,
|
|
7
|
+
Oas3_1Definition,
|
|
8
|
+
Oas3Components,
|
|
9
|
+
Oas3_1Components,
|
|
10
|
+
} from '../../typings/openapi';
|
|
6
11
|
|
|
7
12
|
export const RemoveUnusedComponents: Oas3Decorator = () => {
|
|
8
13
|
const components = new Map<
|
|
9
14
|
string,
|
|
10
|
-
{
|
|
15
|
+
{
|
|
16
|
+
usedIn: Location[];
|
|
17
|
+
componentType?: keyof (Oas3Components | Oas3_1Components);
|
|
18
|
+
name: string;
|
|
19
|
+
}
|
|
11
20
|
>();
|
|
12
21
|
|
|
13
22
|
function registerComponent(
|
|
14
23
|
location: Location,
|
|
15
|
-
componentType: keyof Oas3Components,
|
|
24
|
+
componentType: keyof (Oas3Components | Oas3_1Components),
|
|
16
25
|
name: string
|
|
17
26
|
): void {
|
|
18
27
|
components.set(location.absolutePointer, {
|
|
@@ -22,7 +31,10 @@ export const RemoveUnusedComponents: Oas3Decorator = () => {
|
|
|
22
31
|
});
|
|
23
32
|
}
|
|
24
33
|
|
|
25
|
-
function removeUnusedComponents(
|
|
34
|
+
function removeUnusedComponents(
|
|
35
|
+
root: Oas3Definition | Oas3_1Definition,
|
|
36
|
+
removedPaths: string[]
|
|
37
|
+
): number {
|
|
26
38
|
const removedLengthStart = removedPaths.length;
|
|
27
39
|
|
|
28
40
|
for (const [path, { usedIn, name, componentType }] of components) {
|
package/src/index.ts
CHANGED
|
@@ -18,13 +18,13 @@ export type {
|
|
|
18
18
|
Oas3Definition,
|
|
19
19
|
Oas3_1Definition,
|
|
20
20
|
Oas3Components,
|
|
21
|
+
Oas3_1Components,
|
|
21
22
|
Oas3PathItem,
|
|
22
23
|
Oas3Paths,
|
|
23
24
|
Oas3ComponentName,
|
|
24
25
|
Oas3Schema,
|
|
25
26
|
Oas3_1Schema,
|
|
26
27
|
Oas3Tag,
|
|
27
|
-
Oas3_1Webhooks,
|
|
28
28
|
Referenced,
|
|
29
29
|
OasRef,
|
|
30
30
|
} from './typings/openapi';
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { setRedoclyDomain } from '../domains';
|
|
2
2
|
import { RedoclyClient } from '../index';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
default: jest.fn(() => ({
|
|
6
|
-
ok: true,
|
|
7
|
-
json: jest.fn().mockResolvedValue({}),
|
|
8
|
-
})),
|
|
9
|
-
}));
|
|
4
|
+
const originalFetch = global.fetch;
|
|
10
5
|
|
|
11
6
|
describe('RedoclyClient', () => {
|
|
12
7
|
const REDOCLY_DOMAIN_US = 'redocly.com';
|
|
@@ -15,6 +10,19 @@ describe('RedoclyClient', () => {
|
|
|
15
10
|
const testRedoclyDomain = 'redoclyDomain.com';
|
|
16
11
|
const testToken = 'test-token';
|
|
17
12
|
|
|
13
|
+
beforeAll(() => {
|
|
14
|
+
global.fetch = jest.fn(() =>
|
|
15
|
+
Promise.resolve({
|
|
16
|
+
ok: true,
|
|
17
|
+
json: jest.fn().mockResolvedValue({}),
|
|
18
|
+
} as any)
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterAll(() => {
|
|
23
|
+
global.fetch = originalFetch;
|
|
24
|
+
});
|
|
25
|
+
|
|
18
26
|
afterEach(() => {
|
|
19
27
|
delete process.env.REDOCLY_DOMAIN;
|
|
20
28
|
setRedoclyDomain('');
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import fetch from 'node-fetch';
|
|
2
1
|
import { getProxyAgent, isNotEmptyObject } from '../utils';
|
|
3
2
|
import { getRedoclyDomain } from './domains';
|
|
4
3
|
|
|
5
|
-
import type { RequestInit, HeadersInit } from 'node-fetch';
|
|
6
4
|
import type {
|
|
7
5
|
NotFoundProblemResponse,
|
|
8
6
|
PrepareFileuploadOKResponse,
|
|
@@ -43,10 +41,13 @@ export class RegistryApi {
|
|
|
43
41
|
throw new Error('Unauthorized');
|
|
44
42
|
}
|
|
45
43
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
const requestOptions = {
|
|
45
|
+
...options,
|
|
46
|
+
headers,
|
|
47
|
+
agent: getProxyAgent(),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const response = await fetch(`${this.getBaseUrl()}${path}`, requestOptions);
|
|
50
51
|
|
|
51
52
|
if (response.status === 401) {
|
|
52
53
|
throw new Error('Unauthorized');
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { Oas3Rule, Oas2Rule } from '../../visitors';
|
|
2
2
|
import type { Oas2Definition, Oas2Operation } from '../../typings/swagger';
|
|
3
|
-
import type { Oas3Definition, Oas3Operation } from '../../typings/openapi';
|
|
3
|
+
import type { Oas3Definition, Oas3_1Definition, Oas3Operation } from '../../typings/openapi';
|
|
4
4
|
import type { UserContext } from '../../walk';
|
|
5
5
|
|
|
6
6
|
export const OperationTagDefined: Oas3Rule | Oas2Rule = () => {
|
|
7
7
|
let definedTags: Set<string>;
|
|
8
8
|
|
|
9
9
|
return {
|
|
10
|
-
Root(root: Oas2Definition | Oas3Definition) {
|
|
10
|
+
Root(root: Oas2Definition | Oas3Definition | Oas3_1Definition) {
|
|
11
11
|
definedTags = new Set((root.tags ?? []).map((t) => t.name));
|
|
12
12
|
},
|
|
13
13
|
Operation(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) {
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
} from '../../typings/swagger';
|
|
10
10
|
import type {
|
|
11
11
|
Oas3Definition,
|
|
12
|
+
Oas3_1Definition,
|
|
12
13
|
Oas3Operation,
|
|
13
14
|
Oas3PathItem,
|
|
14
15
|
Oas3SecurityScheme,
|
|
@@ -31,7 +32,7 @@ export const SecurityDefined: Oas3Rule | Oas2Rule = (opts: {
|
|
|
31
32
|
|
|
32
33
|
return {
|
|
33
34
|
Root: {
|
|
34
|
-
leave(root: Oas2Definition | Oas3Definition, { report }: UserContext) {
|
|
35
|
+
leave(root: Oas2Definition | Oas3Definition | Oas3_1Definition, { report }: UserContext) {
|
|
35
36
|
for (const [name, scheme] of referencedSchemes.entries()) {
|
|
36
37
|
if (scheme.defined) continue;
|
|
37
38
|
for (const reportedFromLocation of scheme.from) {
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { Oas3Rule, Oas2Rule } from '../../visitors';
|
|
2
2
|
import type { Oas2Definition, Oas2Tag } from '../../typings/swagger';
|
|
3
|
-
import type { Oas3Definition, Oas3Tag } from '../../typings/openapi';
|
|
3
|
+
import type { Oas3Definition, Oas3Tag, Oas3_1Definition } from '../../typings/openapi';
|
|
4
4
|
import type { UserContext } from '../../walk';
|
|
5
5
|
|
|
6
6
|
export const TagsAlphabetical: Oas3Rule | Oas2Rule = ({ ignoreCase = false }) => {
|
|
7
7
|
return {
|
|
8
|
-
Root(
|
|
8
|
+
Root(
|
|
9
|
+
root: Oas2Definition | Oas3Definition | Oas3_1Definition,
|
|
10
|
+
{ report, location }: UserContext
|
|
11
|
+
) {
|
|
9
12
|
if (!root.tags) return;
|
|
10
13
|
for (let i = 0; i < root.tags.length - 1; i++) {
|
|
11
14
|
if (getTagName(root.tags[i], ignoreCase) > getTagName(root.tags[i + 1], ignoreCase)) {
|
|
@@ -12,15 +12,18 @@ export const ArrayParameterSerialization: Oas3Rule = (
|
|
|
12
12
|
): Oas3Visitor => {
|
|
13
13
|
return {
|
|
14
14
|
Parameter: {
|
|
15
|
-
leave(node
|
|
15
|
+
leave(node, ctx) {
|
|
16
16
|
if (!node.schema) {
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
19
|
-
const schema =
|
|
20
|
-
? ctx.resolve
|
|
21
|
-
|
|
19
|
+
const schema = (
|
|
20
|
+
isRef(node.schema) ? ctx.resolve(node.schema).node : node.schema
|
|
21
|
+
) as Oas3_1Schema;
|
|
22
22
|
|
|
23
|
-
if (
|
|
23
|
+
if (
|
|
24
|
+
schema &&
|
|
25
|
+
shouldReportMissingStyleAndExplode(node as Oas3Parameter<Oas3_1Schema>, schema, options)
|
|
26
|
+
) {
|
|
24
27
|
ctx.report({
|
|
25
28
|
message: `Parameter \`${node.name}\` should have \`style\` and \`explode \` fields`,
|
|
26
29
|
location: ctx.location,
|
|
@@ -32,7 +35,7 @@ export const ArrayParameterSerialization: Oas3Rule = (
|
|
|
32
35
|
};
|
|
33
36
|
|
|
34
37
|
function shouldReportMissingStyleAndExplode(
|
|
35
|
-
node: Oas3Parameter
|
|
38
|
+
node: Oas3Parameter<Oas3_1Schema>,
|
|
36
39
|
schema: Oas3_1Schema,
|
|
37
40
|
options: ArrayParameterSerializationOptions
|
|
38
41
|
) {
|
|
@@ -3,10 +3,12 @@ import type { Problem, UserContext } from '../../walk';
|
|
|
3
3
|
import type { Oas2Rule, Oas3Rule, Oas3Visitor } from '../../visitors';
|
|
4
4
|
import type {
|
|
5
5
|
Oas3Definition,
|
|
6
|
+
Oas3_1Definition,
|
|
6
7
|
Oas3Parameter,
|
|
7
8
|
Oas3RequestBody,
|
|
8
9
|
Oas3Response,
|
|
9
10
|
Oas3Schema,
|
|
11
|
+
Oas3_1Schema,
|
|
10
12
|
OasRef,
|
|
11
13
|
} from '../../typings/openapi';
|
|
12
14
|
|
|
@@ -54,7 +56,7 @@ export const ComponentNameUnique: Oas3Rule | Oas2Rule = (options) => {
|
|
|
54
56
|
},
|
|
55
57
|
},
|
|
56
58
|
Root: {
|
|
57
|
-
leave(root: Oas3Definition, ctx: UserContext) {
|
|
59
|
+
leave(root: Oas3Definition | Oas3_1Definition, ctx: UserContext) {
|
|
58
60
|
components.forEach((value, key, _) => {
|
|
59
61
|
if (value.absolutePointers.size > 1) {
|
|
60
62
|
const component = getComponentFromKey(key);
|
|
@@ -82,7 +84,7 @@ export const ComponentNameUnique: Oas3Rule | Oas2Rule = (options) => {
|
|
|
82
84
|
|
|
83
85
|
if (options.schemas != 'off') {
|
|
84
86
|
rule.NamedSchemas = {
|
|
85
|
-
Schema(_: Oas3Schema, { location }: UserContext) {
|
|
87
|
+
Schema(_: Oas3Schema | Oas3_1Schema, { location }: UserContext) {
|
|
86
88
|
addComponentFromAbsoluteLocation(TYPE_NAME_SCHEMA, location);
|
|
87
89
|
},
|
|
88
90
|
};
|
package/src/types/arazzo.ts
CHANGED
|
@@ -84,7 +84,7 @@ const ReusableObject: NodeType = {
|
|
|
84
84
|
};
|
|
85
85
|
const Parameter: NodeType = {
|
|
86
86
|
properties: {
|
|
87
|
-
in: { type: 'string', enum: ['header', 'query', 'path', 'cookie'
|
|
87
|
+
in: { type: 'string', enum: ['header', 'query', 'path', 'cookie'] },
|
|
88
88
|
name: { type: 'string' },
|
|
89
89
|
value: {}, // any
|
|
90
90
|
},
|
package/src/types/oas3_1.ts
CHANGED
|
@@ -182,6 +182,8 @@ export const Schema: NodeType = {
|
|
|
182
182
|
const: null,
|
|
183
183
|
$comment: { type: 'string' },
|
|
184
184
|
'x-tags': { type: 'array', items: { type: 'string' } },
|
|
185
|
+
$dynamicAnchor: { type: 'string' },
|
|
186
|
+
$dynamicRef: { type: 'string' },
|
|
185
187
|
},
|
|
186
188
|
extensionsPrefix: 'x-',
|
|
187
189
|
};
|
package/src/typings/arazzo.ts
CHANGED
|
@@ -21,7 +21,7 @@ export interface ArazzoSourceDescription {
|
|
|
21
21
|
export type SourceDescription = OpenAPISourceDescription | ArazzoSourceDescription;
|
|
22
22
|
|
|
23
23
|
export interface Parameter {
|
|
24
|
-
in?: 'header' | 'query' | 'path' | 'cookie'
|
|
24
|
+
in?: 'header' | 'query' | 'path' | 'cookie';
|
|
25
25
|
name: string;
|
|
26
26
|
value: string | number | boolean;
|
|
27
27
|
reference?: string;
|