@redocly/openapi-core 1.0.0-beta.64 → 1.0.0-beta.68

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 (81) 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.js +16 -4
  12. package/lib/config/all.js +9 -1
  13. package/lib/config/config.js +1 -1
  14. package/lib/config/minimal.js +1 -0
  15. package/lib/config/recommended.js +1 -0
  16. package/lib/index.d.ts +1 -1
  17. package/lib/index.js +2 -1
  18. package/lib/ref-utils.js +1 -2
  19. package/lib/rules/builtin.d.ts +6 -0
  20. package/lib/rules/common/info-description-override.d.ts +2 -0
  21. package/lib/rules/common/info-description-override.js +24 -0
  22. package/lib/rules/common/info-license-url.js +1 -0
  23. package/lib/rules/common/no-http-verbs-in-paths.d.ts +2 -0
  24. package/lib/rules/common/no-http-verbs-in-paths.js +33 -0
  25. package/lib/rules/common/operation-4xx-response.d.ts +2 -0
  26. package/lib/rules/common/operation-4xx-response.js +17 -0
  27. package/lib/rules/common/operation-description-override.d.ts +2 -0
  28. package/lib/rules/common/operation-description-override.js +29 -0
  29. package/lib/rules/common/path-excludes-patterns.d.ts +2 -0
  30. package/lib/rules/common/path-excludes-patterns.js +22 -0
  31. package/lib/rules/common/path-segment-plural.d.ts +2 -0
  32. package/lib/rules/common/path-segment-plural.js +32 -0
  33. package/lib/rules/common/tag-description-override.d.ts +2 -0
  34. package/lib/rules/common/tag-description-override.js +25 -0
  35. package/lib/rules/oas2/index.d.ts +9 -0
  36. package/lib/rules/oas2/index.js +18 -0
  37. package/lib/rules/oas2/request-mime-type.d.ts +2 -0
  38. package/lib/rules/oas2/request-mime-type.js +17 -0
  39. package/lib/rules/oas2/response-mime-type.d.ts +2 -0
  40. package/lib/rules/oas2/response-mime-type.js +17 -0
  41. package/lib/rules/oas3/index.d.ts +3 -0
  42. package/lib/rules/oas3/index.js +19 -1
  43. package/lib/rules/oas3/no-server-trailing-slash.js +1 -1
  44. package/lib/rules/oas3/request-mime-type.d.ts +2 -0
  45. package/lib/rules/oas3/request-mime-type.js +31 -0
  46. package/lib/rules/oas3/response-mime-type.d.ts +2 -0
  47. package/lib/rules/oas3/response-mime-type.js +31 -0
  48. package/lib/types/oas3_1.js +6 -0
  49. package/lib/utils.d.ts +11 -0
  50. package/lib/utils.js +69 -1
  51. package/package.json +4 -2
  52. package/src/__tests__/utils.test.ts +19 -1
  53. package/src/bundle.ts +26 -6
  54. package/src/config/all.ts +9 -1
  55. package/src/config/config.ts +2 -2
  56. package/src/config/minimal.ts +1 -0
  57. package/src/config/recommended.ts +1 -0
  58. package/src/index.ts +1 -1
  59. package/src/ref-utils.ts +1 -3
  60. package/src/rules/common/__tests__/info-license.test.ts +1 -1
  61. package/src/rules/common/__tests__/operation-4xx-response.test.ts +108 -0
  62. package/src/rules/common/info-description-override.ts +24 -0
  63. package/src/rules/common/info-license-url.ts +1 -0
  64. package/src/rules/common/no-http-verbs-in-paths.ts +36 -0
  65. package/src/rules/common/operation-4xx-response.ts +17 -0
  66. package/src/rules/common/operation-description-override.ts +30 -0
  67. package/src/rules/common/path-excludes-patterns.ts +23 -0
  68. package/src/rules/common/path-segment-plural.ts +31 -0
  69. package/src/rules/common/tag-description-override.ts +25 -0
  70. package/src/rules/oas2/index.ts +18 -0
  71. package/src/rules/oas2/request-mime-type.ts +17 -0
  72. package/src/rules/oas2/response-mime-type.ts +17 -0
  73. package/src/rules/oas3/__tests__/no-server-trailing-slash.test.ts +19 -0
  74. package/src/rules/oas3/index.ts +20 -3
  75. package/src/rules/oas3/no-server-trailing-slash.ts +1 -1
  76. package/src/rules/oas3/request-mime-type.ts +31 -0
  77. package/src/rules/oas3/response-mime-type.ts +31 -0
  78. package/src/rules/utils.ts +1 -1
  79. package/src/types/oas3_1.ts +7 -0
  80. package/src/utils.ts +79 -0
  81. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PathSegmentPlural = void 0;
4
+ const utils_1 = require("../../utils");
5
+ const PathSegmentPlural = (opts) => {
6
+ const { ignoreLastPathSegment, exceptions } = opts;
7
+ return {
8
+ PathItem: {
9
+ leave(_path, { report, key, location }) {
10
+ const pathKey = key.toString();
11
+ if (pathKey.startsWith('/')) {
12
+ const pathSegments = pathKey.split('/');
13
+ pathSegments.shift();
14
+ if (ignoreLastPathSegment && pathSegments.length > 1) {
15
+ pathSegments.pop();
16
+ }
17
+ for (const pathSegment of pathSegments) {
18
+ if (exceptions && exceptions.includes(pathSegment))
19
+ continue;
20
+ if (!utils_1.isPathParameter(pathSegment) && utils_1.isSingular(pathSegment)) {
21
+ report({
22
+ message: `path segment \`${pathSegment}\` should be plural.`,
23
+ location: location.key(),
24
+ });
25
+ }
26
+ }
27
+ }
28
+ },
29
+ },
30
+ };
31
+ };
32
+ exports.PathSegmentPlural = PathSegmentPlural;
@@ -0,0 +1,2 @@
1
+ import { Oas3Decorator, Oas2Decorator } from '../../visitors';
2
+ export declare const TagDescriptionOverride: Oas3Decorator | Oas2Decorator;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TagDescriptionOverride = void 0;
4
+ const utils_1 = require("../../utils");
5
+ const TagDescriptionOverride = ({ tagNames }) => {
6
+ return {
7
+ Tag: {
8
+ leave(tag, { report }) {
9
+ if (!tagNames)
10
+ throw new Error(`Parameter "tagNames" is not provided for "tag-description-override" rule`);
11
+ if (tagNames[tag.name]) {
12
+ try {
13
+ tag.description = utils_1.readFileAsStringSync(tagNames[tag.name]);
14
+ }
15
+ catch (e) {
16
+ report({
17
+ message: `Failed to read markdown override file for tag "${tag.name}".\n${e.message}`,
18
+ });
19
+ }
20
+ }
21
+ },
22
+ },
23
+ };
24
+ };
25
+ exports.TagDescriptionOverride = TagDescriptionOverride;
@@ -12,6 +12,7 @@ export declare const rules: {
12
12
  'boolean-parameter-prefixes': Oas2Rule;
13
13
  'no-path-trailing-slash': Oas2Rule;
14
14
  'operation-2xx-response': Oas2Rule;
15
+ 'operation-4xx-response': Oas2Rule;
15
16
  'operation-operationId-unique': Oas2Rule;
16
17
  'operation-parameters-unique': Oas2Rule;
17
18
  'path-parameters-defined': Oas2Rule;
@@ -30,8 +31,16 @@ export declare const rules: {
30
31
  'no-identical-paths': Oas2Rule;
31
32
  'no-ambiguous-paths': Oas2Rule;
32
33
  'path-http-verbs-order': Oas2Rule;
34
+ 'no-http-verbs-in-paths': Oas2Rule;
35
+ 'path-excludes-patterns': Oas2Rule;
36
+ 'request-mime-type': Oas2Rule;
37
+ 'response-mime-type': Oas2Rule;
38
+ 'path-segment-plural': Oas2Rule;
33
39
  };
34
40
  export declare const preprocessors: {};
35
41
  export declare const decorators: {
36
42
  'registry-dependencies': Oas2Decorator;
43
+ 'operation-description-override': Oas2Decorator;
44
+ 'tag-description-override': Oas2Decorator;
45
+ 'info-description-override': Oas2Decorator;
37
46
  };
@@ -13,6 +13,7 @@ const paths_kebab_case_1 = require("../common/paths-kebab-case");
13
13
  const no_enum_type_mismatch_1 = require("../common/no-enum-type-mismatch");
14
14
  const no_path_trailing_slash_1 = require("../common/no-path-trailing-slash");
15
15
  const operation_2xx_response_1 = require("../common/operation-2xx-response");
16
+ const operation_4xx_response_1 = require("../common/operation-4xx-response");
16
17
  const operation_operationId_unique_1 = require("../common/operation-operationId-unique");
17
18
  const operation_parameters_unique_1 = require("../common/operation-parameters-unique");
18
19
  const path_params_defined_1 = require("../common/path-params-defined");
@@ -31,6 +32,14 @@ const no_identical_paths_1 = require("../common/no-identical-paths");
31
32
  const operation_operationId_1 = require("../common/operation-operationId");
32
33
  const operation_summary_1 = require("../common/operation-summary");
33
34
  const no_ambiguous_paths_1 = require("../common/no-ambiguous-paths");
35
+ const no_http_verbs_in_paths_1 = require("../common/no-http-verbs-in-paths");
36
+ const path_excludes_patterns_1 = require("../common/path-excludes-patterns");
37
+ const request_mime_type_1 = require("./request-mime-type");
38
+ const response_mime_type_1 = require("./response-mime-type");
39
+ const path_segment_plural_1 = require("../common/path-segment-plural");
40
+ const operation_description_override_1 = require("../common/operation-description-override");
41
+ const tag_description_override_1 = require("../common/tag-description-override");
42
+ const info_description_override_1 = require("../common/info-description-override");
34
43
  exports.rules = {
35
44
  spec: spec_1.OasSpec,
36
45
  'info-description': info_description_1.InfoDescription,
@@ -44,6 +53,7 @@ exports.rules = {
44
53
  'boolean-parameter-prefixes': boolean_parameter_prefixes_1.BooleanParameterPrefixes,
45
54
  'no-path-trailing-slash': no_path_trailing_slash_1.NoPathTrailingSlash,
46
55
  'operation-2xx-response': operation_2xx_response_1.Operation2xxResponse,
56
+ 'operation-4xx-response': operation_4xx_response_1.Operation4xxResponse,
47
57
  'operation-operationId-unique': operation_operationId_unique_1.OperationIdUnique,
48
58
  'operation-parameters-unique': operation_parameters_unique_1.OperationParametersUnique,
49
59
  'path-parameters-defined': path_params_defined_1.PathParamsDefined,
@@ -62,8 +72,16 @@ exports.rules = {
62
72
  'no-identical-paths': no_identical_paths_1.NoIdenticalPaths,
63
73
  'no-ambiguous-paths': no_ambiguous_paths_1.NoAmbiguousPaths,
64
74
  'path-http-verbs-order': path_http_verbs_order_1.PathHttpVerbsOrder,
75
+ 'no-http-verbs-in-paths': no_http_verbs_in_paths_1.NoHttpVerbsInPaths,
76
+ 'path-excludes-patterns': path_excludes_patterns_1.PathExcludesPatterns,
77
+ 'request-mime-type': request_mime_type_1.RequestMimeType,
78
+ 'response-mime-type': response_mime_type_1.ResponseMimeType,
79
+ 'path-segment-plural': path_segment_plural_1.PathSegmentPlural,
65
80
  };
66
81
  exports.preprocessors = {};
67
82
  exports.decorators = {
68
83
  'registry-dependencies': registry_dependencies_1.RegistryDependencies,
84
+ 'operation-description-override': operation_description_override_1.OperationDescriptionOverride,
85
+ 'tag-description-override': tag_description_override_1.TagDescriptionOverride,
86
+ 'info-description-override': info_description_override_1.InfoDescriptionOverride,
69
87
  };
@@ -0,0 +1,2 @@
1
+ import { Oas2Rule } from '../../visitors';
2
+ export declare const RequestMimeType: Oas2Rule;
@@ -0,0 +1,17 @@
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
+ DefinitionRoot(root, ctx) {
8
+ utils_1.validateMimeType({ type: 'consumes', value: root }, ctx, allowedValues);
9
+ },
10
+ Operation: {
11
+ leave(operation, ctx) {
12
+ utils_1.validateMimeType({ type: 'consumes', value: operation }, ctx, allowedValues);
13
+ },
14
+ },
15
+ };
16
+ };
17
+ exports.RequestMimeType = RequestMimeType;
@@ -0,0 +1,2 @@
1
+ import { Oas2Rule } from '../../visitors';
2
+ export declare const ResponseMimeType: Oas2Rule;
@@ -0,0 +1,17 @@
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
+ DefinitionRoot(root, ctx) {
8
+ utils_1.validateMimeType({ type: 'produces', value: root }, ctx, allowedValues);
9
+ },
10
+ Operation: {
11
+ leave(operation, ctx) {
12
+ utils_1.validateMimeType({ type: 'produces', value: operation }, ctx, allowedValues);
13
+ },
14
+ },
15
+ };
16
+ };
17
+ exports.ResponseMimeType = ResponseMimeType;
@@ -4,4 +4,7 @@ export declare const rules: Oas3RuleSet;
4
4
  export declare const preprocessors: {};
5
5
  export declare const decorators: {
6
6
  'registry-dependencies': Oas3Decorator;
7
+ 'operation-description-override': Oas3Decorator;
8
+ 'tag-description-override': Oas3Decorator;
9
+ 'info-description-override': Oas3Decorator;
7
10
  };
@@ -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,6 +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");
47
+ const operation_description_override_1 = require("../common/operation-description-override");
48
+ const tag_description_override_1 = require("../common/tag-description-override");
49
+ const info_description_override_1 = require("../common/info-description-override");
50
+ const path_excludes_patterns_1 = require("../common/path-excludes-patterns");
42
51
  exports.rules = {
43
52
  spec: spec_1.OasSpec,
44
53
  'info-description': info_description_1.InfoDescription,
@@ -46,6 +55,7 @@ exports.rules = {
46
55
  'info-license': info_license_url_1.InfoLicense,
47
56
  'info-license-url': license_url_1.InfoLicenseUrl,
48
57
  'operation-2xx-response': operation_2xx_response_1.Operation2xxResponse,
58
+ 'operation-4xx-response': operation_4xx_response_1.Operation4xxResponse,
49
59
  'operation-operationId-unique': operation_operationId_unique_1.OperationIdUnique,
50
60
  'operation-parameters-unique': operation_parameters_unique_1.OperationParametersUnique,
51
61
  'path-parameters-defined': path_params_defined_1.PathParamsDefined,
@@ -77,9 +87,17 @@ exports.rules = {
77
87
  'no-identical-paths': no_identical_paths_1.NoIdenticalPaths,
78
88
  'no-ambiguous-paths': no_ambiguous_paths_1.NoAmbiguousPaths,
79
89
  'no-undefined-server-variable': no_undefined_server_variable_1.NoUndefinedServerVariable,
80
- 'no-servers-empty-enum': no_servers_empty_enum_1.NoEmptyEnumServers
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,
81
96
  };
82
97
  exports.preprocessors = {};
83
98
  exports.decorators = {
84
99
  'registry-dependencies': registry_dependencies_1.RegistryDependencies,
100
+ 'operation-description-override': operation_description_override_1.OperationDescriptionOverride,
101
+ 'tag-description-override': tag_description_override_1.TagDescriptionOverride,
102
+ 'info-description-override': info_description_override_1.InfoDescriptionOverride,
85
103
  };
@@ -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,3 +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;
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.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,3 +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;
128
+ function readFileAsStringSync(filePath) {
129
+ return fs.readFileSync(filePath, 'utf-8');
130
+ }
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/openapi-core",
3
- "version": "1.0.0-beta.64",
3
+ "version": "1.0.0-beta.68",
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,7 +7,7 @@ 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';
@@ -40,7 +40,8 @@ export async function bundle(opts: {
40
40
  throw new Error('Document or reference is required.\n');
41
41
  }
42
42
 
43
- const document = doc !== undefined ? doc : await externalRefResolver.resolveDocument(base, ref!, true);
43
+ const document =
44
+ doc !== undefined ? doc : await externalRefResolver.resolveDocument(base, ref!, true);
44
45
 
45
46
  if (document instanceof Error) {
46
47
  throw document;
@@ -69,7 +70,11 @@ export async function bundleDocument(opts: {
69
70
  const rules = config.getRulesForOasVersion(oasMajorVersion);
70
71
  const types = normalizeTypes(
71
72
  config.extendTypes(
72
- customTypes ?? oasMajorVersion === OasMajorVersion.Version3 ? (oasVersion === OasVersion.Version3_1 ? Oas3_1Types : Oas3Types) : Oas2Types,
73
+ customTypes ?? oasMajorVersion === OasMajorVersion.Version3
74
+ ? oasVersion === OasVersion.Version3_1
75
+ ? Oas3_1Types
76
+ : Oas3Types
77
+ : Oas2Types,
73
78
  oasVersion,
74
79
  ),
75
80
  config,
@@ -249,6 +254,21 @@ function makeBundleVisitor(version: OasMajorVersion, dereference: boolean, rootD
249
254
  }
250
255
  }
251
256
 
257
+ function isEqualOrEqualRef(
258
+ node: any,
259
+ target: { node: any; location: Location },
260
+ ctx: UserContext,
261
+ ) {
262
+ if (
263
+ isRef(node) &&
264
+ ctx.resolve(node).location?.absolutePointer === target.location.absolutePointer
265
+ ) {
266
+ return true;
267
+ }
268
+
269
+ return isEqual(node, target.node);
270
+ }
271
+
252
272
  function getComponentName(
253
273
  target: { node: any; location: Location },
254
274
  componentType: string,
@@ -265,20 +285,20 @@ function makeBundleVisitor(version: OasMajorVersion, dereference: boolean, rootD
265
285
  if (
266
286
  !componentsGroup ||
267
287
  !componentsGroup[name] ||
268
- isEqual(componentsGroup[name], target.node)
288
+ isEqualOrEqualRef(componentsGroup[name], target, ctx)
269
289
  ) {
270
290
  return name;
271
291
  }
272
292
  }
273
293
 
274
294
  name = refBaseName(fileRef) + (name ? `_${name}` : '');
275
- if (!componentsGroup[name] || isEqual(componentsGroup[name], target.node)) {
295
+ if (!componentsGroup[name] || isEqualOrEqualRef(componentsGroup[name], target, ctx)) {
276
296
  return name;
277
297
  }
278
298
 
279
299
  const prevName = name;
280
300
  let serialId = 2;
281
- while (componentsGroup[name] && !isEqual(componentsGroup[name], target.node)) {
301
+ while (componentsGroup[name] && !isEqualOrEqualRef(componentsGroup[name], target, ctx)) {
282
302
  name = `${prevName}-${serialId}`;
283
303
  serialId++;
284
304
  }
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',