@redocly/openapi-core 1.0.0-beta.65 → 1.0.0-beta.69

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.
Files changed (90) hide show
  1. package/__tests__/__snapshots__/bundle.test.ts.snap +126 -0
  2. package/__tests__/bundle.test.ts +53 -1
  3. package/__tests__/fixtures/refs/definitions.yaml +3 -0
  4. package/__tests__/fixtures/refs/external-request-body.yaml +13 -0
  5. package/__tests__/fixtures/refs/externalref.yaml +35 -0
  6. package/__tests__/fixtures/refs/hosted.yaml +35 -0
  7. package/__tests__/fixtures/refs/rename.yaml +1 -0
  8. package/__tests__/fixtures/refs/requestBody.yaml +9 -0
  9. package/__tests__/fixtures/refs/simple.yaml +1 -0
  10. package/__tests__/fixtures/refs/vendor.schema.yaml +20 -0
  11. package/lib/bundle.d.ts +4 -0
  12. package/lib/bundle.js +25 -7
  13. package/lib/config/all.js +9 -1
  14. package/lib/config/config.js +1 -1
  15. package/lib/config/minimal.js +1 -0
  16. package/lib/config/recommended.js +1 -0
  17. package/lib/index.d.ts +1 -1
  18. package/lib/index.js +2 -1
  19. package/lib/lint.js +2 -0
  20. package/lib/redocly/index.d.ts +3 -14
  21. package/lib/redocly/index.js +19 -186
  22. package/lib/redocly/registry-api-types.d.ts +28 -0
  23. package/lib/redocly/registry-api-types.js +2 -0
  24. package/lib/redocly/registry-api.d.ts +11 -0
  25. package/lib/redocly/registry-api.js +94 -0
  26. package/lib/ref-utils.js +1 -2
  27. package/lib/rules/common/info-license-url.js +1 -0
  28. package/lib/rules/common/no-http-verbs-in-paths.d.ts +2 -0
  29. package/lib/rules/common/no-http-verbs-in-paths.js +33 -0
  30. package/lib/rules/common/operation-4xx-response.d.ts +2 -0
  31. package/lib/rules/common/operation-4xx-response.js +17 -0
  32. package/lib/rules/common/path-excludes-patterns.d.ts +2 -0
  33. package/lib/rules/common/path-excludes-patterns.js +22 -0
  34. package/lib/rules/common/path-segment-plural.d.ts +2 -0
  35. package/lib/rules/common/path-segment-plural.js +32 -0
  36. package/lib/rules/common/registry-dependencies.js +4 -7
  37. package/lib/rules/oas2/index.d.ts +6 -0
  38. package/lib/rules/oas2/index.js +12 -0
  39. package/lib/rules/oas2/request-mime-type.d.ts +2 -0
  40. package/lib/rules/oas2/request-mime-type.js +17 -0
  41. package/lib/rules/oas2/response-mime-type.d.ts +2 -0
  42. package/lib/rules/oas2/response-mime-type.js +17 -0
  43. package/lib/rules/oas3/index.js +12 -0
  44. package/lib/rules/oas3/no-server-trailing-slash.js +1 -1
  45. package/lib/rules/oas3/request-mime-type.d.ts +2 -0
  46. package/lib/rules/oas3/request-mime-type.js +31 -0
  47. package/lib/rules/oas3/response-mime-type.d.ts +2 -0
  48. package/lib/rules/oas3/response-mime-type.js +31 -0
  49. package/lib/types/oas3_1.js +6 -0
  50. package/lib/utils.d.ts +10 -0
  51. package/lib/utils.js +65 -1
  52. package/lib/walk.d.ts +2 -0
  53. package/lib/walk.js +7 -0
  54. package/package.json +5 -3
  55. package/src/__tests__/utils.test.ts +19 -1
  56. package/src/bundle.ts +51 -9
  57. package/src/config/all.ts +9 -1
  58. package/src/config/config.ts +2 -2
  59. package/src/config/minimal.ts +1 -0
  60. package/src/config/recommended.ts +1 -0
  61. package/src/index.ts +1 -1
  62. package/src/lint.ts +2 -0
  63. package/src/redocly/index.ts +17 -194
  64. package/src/redocly/registry-api-types.ts +31 -0
  65. package/src/redocly/registry-api.ts +106 -0
  66. package/src/ref-utils.ts +1 -3
  67. package/src/rules/common/__tests__/info-license.test.ts +1 -1
  68. package/src/rules/common/__tests__/operation-4xx-response.test.ts +108 -0
  69. package/src/rules/common/info-license-url.ts +1 -0
  70. package/src/rules/common/no-http-verbs-in-paths.ts +36 -0
  71. package/src/rules/common/operation-4xx-response.ts +17 -0
  72. package/src/rules/common/path-excludes-patterns.ts +23 -0
  73. package/src/rules/common/path-segment-plural.ts +31 -0
  74. package/src/rules/common/registry-dependencies.ts +6 -8
  75. package/src/rules/oas2/index.ts +12 -0
  76. package/src/rules/oas2/request-mime-type.ts +17 -0
  77. package/src/rules/oas2/response-mime-type.ts +17 -0
  78. package/src/rules/oas3/__tests__/no-server-trailing-slash.test.ts +19 -0
  79. package/src/rules/oas3/index.ts +12 -0
  80. package/src/rules/oas3/no-server-trailing-slash.ts +1 -1
  81. package/src/rules/oas3/request-mime-type.ts +31 -0
  82. package/src/rules/oas3/response-mime-type.ts +31 -0
  83. package/src/rules/utils.ts +1 -1
  84. package/src/types/oas3_1.ts +7 -0
  85. package/src/utils.ts +75 -0
  86. package/src/walk.ts +10 -0
  87. package/tsconfig.tsbuildinfo +1 -1
  88. package/lib/redocly/query.d.ts +0 -4
  89. package/lib/redocly/query.js +0 -44
  90. package/src/redocly/query.ts +0 -38
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.decorators = exports.preprocessors = exports.rules = void 0;
4
4
  const spec_1 = require("../common/spec");
5
5
  const operation_2xx_response_1 = require("../common/operation-2xx-response");
6
+ const operation_4xx_response_1 = require("../common/operation-4xx-response");
6
7
  const operation_operationId_unique_1 = require("../common/operation-operationId-unique");
7
8
  const operation_parameters_unique_1 = require("../common/operation-parameters-unique");
8
9
  const path_params_defined_1 = require("../common/path-params-defined");
@@ -39,9 +40,14 @@ const operation_operationId_1 = require("../common/operation-operationId");
39
40
  const operation_summary_1 = require("../common/operation-summary");
40
41
  const no_ambiguous_paths_1 = require("../common/no-ambiguous-paths");
41
42
  const no_servers_empty_enum_1 = require("./no-servers-empty-enum");
43
+ const no_http_verbs_in_paths_1 = require("../common/no-http-verbs-in-paths");
44
+ const request_mime_type_1 = require("./request-mime-type");
45
+ const response_mime_type_1 = require("./response-mime-type");
46
+ const path_segment_plural_1 = require("../common/path-segment-plural");
42
47
  const operation_description_override_1 = require("../common/operation-description-override");
43
48
  const tag_description_override_1 = require("../common/tag-description-override");
44
49
  const info_description_override_1 = require("../common/info-description-override");
50
+ const path_excludes_patterns_1 = require("../common/path-excludes-patterns");
45
51
  exports.rules = {
46
52
  spec: spec_1.OasSpec,
47
53
  'info-description': info_description_1.InfoDescription,
@@ -49,6 +55,7 @@ exports.rules = {
49
55
  'info-license': info_license_url_1.InfoLicense,
50
56
  'info-license-url': license_url_1.InfoLicenseUrl,
51
57
  'operation-2xx-response': operation_2xx_response_1.Operation2xxResponse,
58
+ 'operation-4xx-response': operation_4xx_response_1.Operation4xxResponse,
52
59
  'operation-operationId-unique': operation_operationId_unique_1.OperationIdUnique,
53
60
  'operation-parameters-unique': operation_parameters_unique_1.OperationParametersUnique,
54
61
  'path-parameters-defined': path_params_defined_1.PathParamsDefined,
@@ -81,6 +88,11 @@ exports.rules = {
81
88
  'no-ambiguous-paths': no_ambiguous_paths_1.NoAmbiguousPaths,
82
89
  'no-undefined-server-variable': no_undefined_server_variable_1.NoUndefinedServerVariable,
83
90
  'no-servers-empty-enum': no_servers_empty_enum_1.NoEmptyEnumServers,
91
+ 'no-http-verbs-in-paths': no_http_verbs_in_paths_1.NoHttpVerbsInPaths,
92
+ 'path-excludes-patterns': path_excludes_patterns_1.PathExcludesPatterns,
93
+ 'request-mime-type': request_mime_type_1.RequestMimeType,
94
+ 'response-mime-type': response_mime_type_1.ResponseMimeType,
95
+ 'path-segment-plural': path_segment_plural_1.PathSegmentPlural,
84
96
  };
85
97
  exports.preprocessors = {};
86
98
  exports.decorators = {
@@ -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']),
@@ -0,0 +1,2 @@
1
+ import { Oas3Rule } from '../../visitors';
2
+ export declare const RequestMimeType: Oas3Rule;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RequestMimeType = void 0;
4
+ const utils_1 = require("../../utils");
5
+ const RequestMimeType = ({ allowedValues }) => {
6
+ return {
7
+ PathMap: {
8
+ RequestBody: {
9
+ leave(requestBody, ctx) {
10
+ utils_1.validateMimeTypeOAS3({ type: 'consumes', value: requestBody }, ctx, allowedValues);
11
+ },
12
+ },
13
+ Callback: {
14
+ RequestBody() { },
15
+ Response: {
16
+ leave(response, ctx) {
17
+ utils_1.validateMimeTypeOAS3({ type: 'consumes', value: response }, ctx, allowedValues);
18
+ },
19
+ },
20
+ },
21
+ },
22
+ WebhooksMap: {
23
+ Response: {
24
+ leave(response, ctx) {
25
+ utils_1.validateMimeTypeOAS3({ type: 'consumes', value: response }, ctx, allowedValues);
26
+ },
27
+ },
28
+ },
29
+ };
30
+ };
31
+ exports.RequestMimeType = RequestMimeType;
@@ -0,0 +1,2 @@
1
+ import { Oas3Rule } from '../../visitors';
2
+ export declare const ResponseMimeType: Oas3Rule;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ResponseMimeType = void 0;
4
+ const utils_1 = require("../../utils");
5
+ const ResponseMimeType = ({ allowedValues }) => {
6
+ return {
7
+ PathMap: {
8
+ Response: {
9
+ leave(response, ctx) {
10
+ utils_1.validateMimeTypeOAS3({ type: 'produces', value: response }, ctx, allowedValues);
11
+ },
12
+ },
13
+ Callback: {
14
+ Response() { },
15
+ RequestBody: {
16
+ leave(requestBody, ctx) {
17
+ utils_1.validateMimeTypeOAS3({ type: 'produces', value: requestBody }, ctx, allowedValues);
18
+ },
19
+ },
20
+ },
21
+ },
22
+ WebhooksMap: {
23
+ RequestBody: {
24
+ leave(requestBody, ctx) {
25
+ utils_1.validateMimeTypeOAS3({ type: 'produces', value: requestBody }, ctx, allowedValues);
26
+ },
27
+ },
28
+ },
29
+ };
30
+ };
31
+ exports.ResponseMimeType = ResponseMimeType;
@@ -94,6 +94,12 @@ const Operation = {
94
94
  };
95
95
  const Schema = {
96
96
  properties: {
97
+ $id: { type: 'string' },
98
+ id: { type: 'string' },
99
+ $schema: { type: 'string' },
100
+ definitions: 'NamedSchemas',
101
+ $defs: 'NamedSchemas',
102
+ $vocabulary: { type: 'string' },
97
103
  externalDocs: 'ExternalDocs',
98
104
  discriminator: 'Discriminator',
99
105
  myArbitraryKeyword: { type: 'boolean' },
package/lib/utils.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { HttpResolveConfig } from './config/config';
2
+ import { UserContext } from './walk';
2
3
  export { parseYaml, stringifyYaml } from './js-yaml';
3
4
  export declare type StackFrame<T> = {
4
5
  prev: StackFrame<T> | null;
@@ -22,4 +23,13 @@ export declare function readFileFromUrl(url: string, config: HttpResolveConfig):
22
23
  export declare function match(url: string, pattern: string): boolean;
23
24
  export declare function pickObjectProps<T extends Record<string, unknown>>(object: T, keys: Array<string>): T;
24
25
  export declare function omitObjectProps<T extends Record<string, unknown>>(object: T, keys: Array<string>): T;
26
+ export declare function splitCamelCaseIntoWords(str: string): Set<string>;
27
+ export declare function validateMimeType({ type, value }: any, { report, location }: UserContext, allowedValues: string[]): void;
28
+ export declare function validateMimeTypeOAS3({ type, value }: any, { report, location }: UserContext, allowedValues: string[]): void;
29
+ export declare function isSingular(path: string): boolean;
25
30
  export declare function readFileAsStringSync(filePath: string): string;
31
+ export declare function isPathParameter(pathSegment: string): boolean;
32
+ /**
33
+ * Convert Windows backslash paths to slash paths: foo\\bar ➔ foo/bar
34
+ */
35
+ export declare function slash(path: string): string;
package/lib/utils.js CHANGED
@@ -9,10 +9,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.readFileAsStringSync = 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.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");
16
+ const pluralize = require("pluralize");
16
17
  const js_yaml_1 = require("./js-yaml");
17
18
  var js_yaml_2 = require("./js-yaml");
18
19
  Object.defineProperty(exports, "parseYaml", { enumerable: true, get: function () { return js_yaml_2.parseYaml; } });
@@ -76,7 +77,70 @@ function omitObjectProps(object, keys) {
76
77
  return Object.fromEntries(Object.entries(object).filter(([key]) => !keys.includes(key)));
77
78
  }
78
79
  exports.omitObjectProps = omitObjectProps;
80
+ function splitCamelCaseIntoWords(str) {
81
+ const camel = str
82
+ .split(/(?:[-._])|([A-Z][a-z]+)/)
83
+ .filter(Boolean)
84
+ .map((item) => item.toLocaleLowerCase());
85
+ const caps = str
86
+ .split(/([A-Z]{2,})/)
87
+ .filter((e) => e && e === e.toUpperCase())
88
+ .map((item) => item.toLocaleLowerCase());
89
+ return new Set([...camel, ...caps]);
90
+ }
91
+ exports.splitCamelCaseIntoWords = splitCamelCaseIntoWords;
92
+ function validateMimeType({ type, value }, { report, location }, allowedValues) {
93
+ const ruleType = type === 'consumes' ? 'request' : 'response';
94
+ if (!allowedValues)
95
+ throw new Error(`Parameter "allowedValues" is not provided for "${ruleType}-mime-type" rule`);
96
+ if (!value[type])
97
+ return;
98
+ for (const mime of value[type]) {
99
+ if (!allowedValues.includes(mime)) {
100
+ report({
101
+ message: `Mime type "${mime}" is not allowed`,
102
+ location: location.child(value[type].indexOf(mime)).key(),
103
+ });
104
+ }
105
+ }
106
+ }
107
+ exports.validateMimeType = validateMimeType;
108
+ function validateMimeTypeOAS3({ type, value }, { report, location }, allowedValues) {
109
+ const ruleType = type === 'consumes' ? 'request' : 'response';
110
+ if (!allowedValues)
111
+ throw new Error(`Parameter "allowedValues" is not provided for "${ruleType}-mime-type" rule`);
112
+ if (!value.content)
113
+ return;
114
+ for (const mime of Object.keys(value.content)) {
115
+ if (!allowedValues.includes(mime)) {
116
+ report({
117
+ message: `Mime type "${mime}" is not allowed`,
118
+ location: location.child('content').child(mime).key(),
119
+ });
120
+ }
121
+ }
122
+ }
123
+ exports.validateMimeTypeOAS3 = validateMimeTypeOAS3;
124
+ function isSingular(path) {
125
+ return pluralize.isSingular(path);
126
+ }
127
+ exports.isSingular = isSingular;
79
128
  function readFileAsStringSync(filePath) {
80
129
  return fs.readFileSync(filePath, 'utf-8');
81
130
  }
82
131
  exports.readFileAsStringSync = readFileAsStringSync;
132
+ function isPathParameter(pathSegment) {
133
+ return pathSegment.startsWith('{') && pathSegment.endsWith('{');
134
+ }
135
+ exports.isPathParameter = isPathParameter;
136
+ /**
137
+ * Convert Windows backslash paths to slash paths: foo\\bar ➔ foo/bar
138
+ */
139
+ function slash(path) {
140
+ const isExtendedLengthPath = /^\\\\\?\\/.test(path);
141
+ if (isExtendedLengthPath) {
142
+ return path;
143
+ }
144
+ return path.replace(/\\/g, '/');
145
+ }
146
+ exports.slash = slash;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/openapi-core",
3
- "version": "1.0.0-beta.65",
3
+ "version": "1.0.0-beta.69",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "engines": {
@@ -30,7 +30,7 @@
30
30
  "Andriy Leliv <andriy@redoc.ly> (https://redoc.ly/)"
31
31
  ],
32
32
  "dependencies": {
33
- "@redocly/ajv": "^8.6.2",
33
+ "@redocly/ajv": "^8.6.4",
34
34
  "@types/node": "^14.11.8",
35
35
  "colorette": "^1.2.0",
36
36
  "js-levenshtein": "^1.1.6",
@@ -38,6 +38,7 @@
38
38
  "lodash.isequal": "^4.5.0",
39
39
  "minimatch": "^3.0.4",
40
40
  "node-fetch": "^2.6.1",
41
+ "pluralize": "^8.0.0",
41
42
  "yaml-ast-parser": "0.0.43"
42
43
  },
43
44
  "devDependencies": {
@@ -46,6 +47,7 @@
46
47
  "@types/lodash.isequal": "^4.5.5",
47
48
  "@types/minimatch": "^3.0.3",
48
49
  "@types/node-fetch": "^2.5.7",
50
+ "@types/pluralize": "^0.0.29",
49
51
  "typescript": "^4.0.5"
50
52
  }
51
- }
53
+ }
@@ -1,4 +1,4 @@
1
- import { pickObjectProps, omitObjectProps } from '../utils';
1
+ import { pickObjectProps, omitObjectProps, slash } from '../utils';
2
2
 
3
3
  describe('utils', () => {
4
4
  const testObject = {
@@ -53,4 +53,22 @@ describe('utils', () => {
53
53
  expect(omitObjectProps({}, ['d', 'e'])).toStrictEqual({});
54
54
  });
55
55
  });
56
+
57
+ describe('slash path', () => {
58
+ it('can correctly slash path', () => {
59
+ [
60
+ ['foo\\bar', 'foo/bar'],
61
+ ['foo/bar', 'foo/bar'],
62
+ ['foo\\中文', 'foo/中文'],
63
+ ['foo/中文', 'foo/中文'],
64
+ ].forEach(([path, expectRes]) => {
65
+ expect(slash(path)).toBe(expectRes);
66
+ });
67
+ });
68
+
69
+ it('does not modify extended length paths', () => {
70
+ const extended = '\\\\?\\some\\path';
71
+ expect(slash(extended)).toBe(extended);
72
+ });
73
+ });
56
74
  });
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 = doc !== undefined ? doc : await externalRefResolver.resolveDocument(base, ref!, true);
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 { document, config, customTypes, externalRefResolver, dereference = false } = opts;
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 ? (oasVersion === OasVersion.Version3_1 ? Oas3_1Types : Oas3Types) : Oas2Types,
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(version: OasMajorVersion, dereference: boolean, rootDocument: Document) {
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
- isEqual(componentsGroup[name], target.node)
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] || isEqual(componentsGroup[name], target.node)) {
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] && !isEqual(componentsGroup[name], target.node)) {
323
+ while (componentsGroup[name] && !isEqualOrEqualRef(componentsGroup[name], target, ctx)) {
282
324
  name = `${prevName}-${serialId}`;
283
325
  serialId++;
284
326
  }
package/src/config/all.ts CHANGED
@@ -12,11 +12,13 @@ export default {
12
12
  'no-identical-paths': 'error',
13
13
  'no-ambiguous-paths': 'error',
14
14
  'no-path-trailing-slash': 'error',
15
+ 'path-segment-plural': 'error',
15
16
  'path-declaration-must-exist': 'error',
16
17
  'path-not-include-query': 'error',
17
18
  'path-parameters-defined': 'error',
18
19
  'operation-description': 'error',
19
20
  'operation-2xx-response': 'error',
21
+ 'operation-4xx-response': 'error',
20
22
  'operation-operationId': 'error',
21
23
  'operation-summary': 'error',
22
24
  'operation-operationId-unique': 'error',
@@ -29,6 +31,12 @@ export default {
29
31
  'no-enum-type-mismatch': 'error',
30
32
  'boolean-parameter-prefixes': 'error',
31
33
  'paths-kebab-case': 'error',
34
+ 'no-http-verbs-in-paths': 'error',
35
+ 'path-excludes-patterns': {
36
+ severity: 'error',
37
+ patterns: [],
38
+ },
39
+ 'request-mime-type': 'error',
32
40
  spec: 'error',
33
41
  },
34
42
  oas3_0Rules: {
@@ -49,5 +57,5 @@ export default {
49
57
  'no-unused-components': 'error',
50
58
  'no-undefined-server-variable': 'error',
51
59
  'no-servers-empty-enum': 'error',
52
- }
60
+ },
53
61
  } as LintRawConfig;
@@ -4,7 +4,7 @@ import { dirname } from 'path';
4
4
  import { red, blue } from 'colorette';
5
5
 
6
6
  import { parseYaml, stringifyYaml } from '../js-yaml';
7
- import { notUndefined } from '../utils';
7
+ import { notUndefined, slash } from '../utils';
8
8
 
9
9
  import {
10
10
  OasVersion,
@@ -211,7 +211,7 @@ export class LintConfig {
211
211
  const ignoreFile = path.join(dir, IGNORE_FILE);
212
212
  const mapped: Record<string, any> = {};
213
213
  for (const absFileName of Object.keys(this.ignore)) {
214
- const ignoredRules = (mapped[path.relative(dir, absFileName)] = this.ignore[absFileName]);
214
+ const ignoredRules = (mapped[slash(path.relative(dir, absFileName))] = this.ignore[absFileName]);
215
215
  for (const ruleId of Object.keys(ignoredRules)) {
216
216
  ignoredRules[ruleId] = Array.from(ignoredRules[ruleId]) as any;
217
217
  }
@@ -17,6 +17,7 @@ export default {
17
17
  'path-parameters-defined': 'warn',
18
18
  'operation-description': 'off',
19
19
  'operation-2xx-response': 'warn',
20
+ 'operation-4xx-response': 'off',
20
21
  'operation-operationId': 'warn',
21
22
  'operation-summary': 'warn',
22
23
  'operation-operationId-unique': 'warn',
@@ -17,6 +17,7 @@ export default {
17
17
  'path-parameters-defined': 'error',
18
18
  'operation-description': 'off',
19
19
  'operation-2xx-response': 'warn',
20
+ 'operation-4xx-response': 'warn',
20
21
  'operation-operationId': 'warn',
21
22
  'operation-summary': 'error',
22
23
  'operation-operationId-unique': 'error',
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { BundleOutputFormat, readFileFromUrl } from './utils';
1
+ export { BundleOutputFormat, readFileFromUrl, slash } from './utils';
2
2
  export { Oas3_1Types } from './types/oas3_1';
3
3
  export { Oas3Types } from './types/oas3';
4
4
  export { Oas2Types } from './types/oas2';
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],