@redocly/openapi-core 1.27.2 → 1.28.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.
@@ -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 { Oas3Components, Oas3Definition } from '../../typings/openapi';
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
- { usedIn: Location[]; componentType?: keyof Oas3Components; name: string }
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(root: Oas3Definition, removedPaths: string[]): number {
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
- jest.mock('node-fetch', () => ({
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 response = await fetch(
47
- `${this.getBaseUrl()}${path}`,
48
- Object.assign({}, options, { headers, agent: getProxyAgent() })
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(root: Oas2Definition | Oas3Definition, { report, location }: UserContext) {
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: Oas3Parameter, ctx) {
15
+ leave(node, ctx) {
16
16
  if (!node.schema) {
17
17
  return;
18
18
  }
19
- const schema = isRef(node.schema)
20
- ? ctx.resolve<Oas3_1Schema>(node.schema).node
21
- : (node.schema as Oas3_1Schema);
19
+ const schema = (
20
+ isRef(node.schema) ? ctx.resolve(node.schema).node : node.schema
21
+ ) as Oas3_1Schema;
22
22
 
23
- if (schema && shouldReportMissingStyleAndExplode(node, schema, options)) {
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
  };
@@ -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', 'body'] },
87
+ in: { type: 'string', enum: ['header', 'query', 'path', 'cookie'] },
88
88
  name: { type: 'string' },
89
89
  value: {}, // any
90
90
  },
@@ -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
  };
@@ -392,10 +392,18 @@ const Rules: NodeType = {
392
392
  properties: {},
393
393
  additionalProperties: (value: unknown, key: string) => {
394
394
  if (key.startsWith('rule/')) {
395
- return 'Assert';
395
+ if (typeof value === 'string') {
396
+ return { enum: ['error', 'warn', 'off'] };
397
+ } else {
398
+ return 'Assert';
399
+ }
396
400
  } else if (key.startsWith('assert/')) {
397
401
  // keep the old assert/ prefix as an alias
398
- return 'Assert';
402
+ if (typeof value === 'string') {
403
+ return { enum: ['error', 'warn', 'off'] };
404
+ } else {
405
+ return 'Assert';
406
+ }
399
407
  } else if (builtInRules.includes(key as BuiltInRuleId) || isCustomRuleId(key)) {
400
408
  if (typeof value === 'string') {
401
409
  return { enum: ['error', 'warn', 'off'] };
@@ -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' | 'body';
24
+ in?: 'header' | 'query' | 'path' | 'cookie';
25
25
  name: string;
26
26
  value: string | number | boolean;
27
27
  reference?: string;