@redocly/openapi-core 1.0.0-beta.110 → 1.0.0-beta.111

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 (57) hide show
  1. package/lib/config/config-resolvers.js +23 -22
  2. package/lib/rules/common/no-ambiguous-paths.js +1 -1
  3. package/lib/rules/common/no-identical-paths.js +4 -4
  4. package/lib/rules/common/operation-2xx-response.js +2 -2
  5. package/lib/rules/common/operation-4xx-response.js +2 -2
  6. package/lib/rules/common/path-not-include-query.js +1 -1
  7. package/lib/rules/common/path-params-defined.js +7 -2
  8. package/lib/rules/common/response-contains-header.js +2 -2
  9. package/lib/rules/common/security-defined.js +10 -5
  10. package/lib/rules/common/spec.js +14 -12
  11. package/lib/rules/oas3/request-mime-type.js +1 -1
  12. package/lib/rules/oas3/response-mime-type.js +1 -1
  13. package/lib/rules/other/stats.d.ts +1 -1
  14. package/lib/rules/other/stats.js +1 -1
  15. package/lib/rules/utils.d.ts +1 -0
  16. package/lib/rules/utils.js +17 -1
  17. package/lib/types/oas2.js +6 -6
  18. package/lib/types/oas3.js +11 -11
  19. package/lib/types/oas3_1.js +3 -3
  20. package/lib/types/redocly-yaml.js +14 -4
  21. package/lib/utils.d.ts +1 -0
  22. package/lib/utils.js +13 -1
  23. package/lib/visitors.d.ts +7 -6
  24. package/lib/visitors.js +11 -3
  25. package/package.json +1 -1
  26. package/src/__tests__/__snapshots__/bundle.test.ts.snap +1 -1
  27. package/src/__tests__/utils.test.ts +11 -0
  28. package/src/__tests__/walk.test.ts +2 -2
  29. package/src/config/__tests__/config-resolvers.test.ts +25 -0
  30. package/src/config/config-resolvers.ts +2 -1
  31. package/src/rules/common/__tests__/operation-2xx-response.test.ts +37 -0
  32. package/src/rules/common/__tests__/operation-4xx-response.test.ts +37 -0
  33. package/src/rules/common/__tests__/path-params-defined.test.ts +69 -0
  34. package/src/rules/common/__tests__/security-defined.test.ts +6 -6
  35. package/src/rules/common/__tests__/spec.test.ts +125 -0
  36. package/src/rules/common/assertions/__tests__/utils.test.ts +2 -2
  37. package/src/rules/common/no-ambiguous-paths.ts +1 -1
  38. package/src/rules/common/no-identical-paths.ts +4 -4
  39. package/src/rules/common/operation-2xx-response.ts +2 -2
  40. package/src/rules/common/operation-4xx-response.ts +2 -2
  41. package/src/rules/common/path-not-include-query.ts +1 -1
  42. package/src/rules/common/path-params-defined.ts +9 -2
  43. package/src/rules/common/response-contains-header.ts +6 -1
  44. package/src/rules/common/security-defined.ts +10 -5
  45. package/src/rules/common/spec.ts +15 -11
  46. package/src/rules/oas3/__tests__/response-contains-header.test.ts +116 -0
  47. package/src/rules/oas3/request-mime-type.ts +1 -1
  48. package/src/rules/oas3/response-mime-type.ts +1 -1
  49. package/src/rules/other/stats.ts +1 -1
  50. package/src/rules/utils.ts +22 -0
  51. package/src/types/oas2.ts +6 -6
  52. package/src/types/oas3.ts +11 -11
  53. package/src/types/oas3_1.ts +3 -3
  54. package/src/types/redocly-yaml.ts +14 -4
  55. package/src/utils.ts +13 -0
  56. package/src/visitors.ts +25 -10
  57. package/tsconfig.tsbuildinfo +1 -1
@@ -68,7 +68,7 @@ const nodeTypesList = [
68
68
  'Info',
69
69
  'Contact',
70
70
  'License',
71
- 'PathsMap',
71
+ 'Paths',
72
72
  'PathItem',
73
73
  'Parameter',
74
74
  'Operation',
@@ -80,10 +80,10 @@ const nodeTypesList = [
80
80
  'Example',
81
81
  'ExamplesMap',
82
82
  'Encoding',
83
- 'EncodingsMap',
83
+ 'EncodingMap',
84
84
  'Header',
85
85
  'HeadersMap',
86
- 'ResponsesMap',
86
+ 'Responses',
87
87
  'Response',
88
88
  'Link',
89
89
  'LinksMap',
@@ -106,7 +106,7 @@ const nodeTypesList = [
106
106
  'PasswordFlow',
107
107
  'ClientCredentials',
108
108
  'AuthorizationCode',
109
- 'SecuritySchemeFlows',
109
+ 'OAuth2Flows',
110
110
  'SecurityScheme',
111
111
  'XCodeSample',
112
112
  'WebhooksMap',
@@ -758,6 +758,16 @@ const ConfigReferenceDocs = {
758
758
  unstable_externalDescription: { type: 'boolean' },
759
759
  unstable_ignoreMimeParameters: { type: 'boolean' },
760
760
  untrustedDefinition: { type: 'boolean' },
761
+ mockServer: {
762
+ properties: {
763
+ url: { type: 'string' },
764
+ position: { enum: ['first', 'last', 'replace', 'off'] },
765
+ description: { type: 'string' },
766
+ },
767
+ },
768
+ showAccessMode: { type: 'boolean' },
769
+ preserveOriginalExtensionsName: { type: 'boolean' },
770
+ markdownHeadingsAnchorLevel: { type: 'number' },
761
771
  },
762
772
  additionalProperties: { type: 'string' },
763
773
  };
package/lib/utils.d.ts CHANGED
@@ -46,3 +46,4 @@ export declare function showErrorForDeprecatedField(deprecatedField: string, upd
46
46
  export declare type Falsy = undefined | null | false | '' | 0;
47
47
  export declare function isTruthy<Truthy>(value: Truthy | Falsy): value is Truthy;
48
48
  export declare function identity<T>(value: T): T;
49
+ export declare function pickDefined<T extends Record<string, unknown>>(obj?: T): Record<string, unknown> | undefined;
package/lib/utils.js CHANGED
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.identity = exports.isTruthy = exports.showErrorForDeprecatedField = exports.showWarningForDeprecatedField = exports.doesYamlFileExist = exports.isCustomRuleId = exports.getMatchingStatusCodeRange = exports.assignExisting = exports.isNotString = exports.isString = exports.isNotEmptyObject = exports.slash = exports.isPathParameter = exports.readFileAsStringSync = exports.isSingular = exports.validateMimeTypeOAS3 = exports.validateMimeType = exports.splitCamelCaseIntoWords = exports.omitObjectProps = exports.pickObjectProps = exports.readFileFromUrl = exports.isEmptyArray = exports.isEmptyObject = exports.isPlainObject = exports.isDefined = exports.loadYaml = exports.popStack = exports.pushStack = exports.stringifyYaml = exports.parseYaml = void 0;
12
+ exports.pickDefined = exports.identity = exports.isTruthy = exports.showErrorForDeprecatedField = exports.showWarningForDeprecatedField = exports.doesYamlFileExist = exports.isCustomRuleId = exports.getMatchingStatusCodeRange = exports.assignExisting = exports.isNotString = exports.isString = exports.isNotEmptyObject = exports.slash = exports.isPathParameter = exports.readFileAsStringSync = exports.isSingular = exports.validateMimeTypeOAS3 = exports.validateMimeType = exports.splitCamelCaseIntoWords = exports.omitObjectProps = exports.pickObjectProps = exports.readFileFromUrl = exports.isEmptyArray = exports.isEmptyObject = exports.isPlainObject = exports.isDefined = exports.loadYaml = exports.popStack = exports.pushStack = exports.stringifyYaml = exports.parseYaml = void 0;
13
13
  const fs = require("fs");
14
14
  const path_1 = require("path");
15
15
  const minimatch = require("minimatch");
@@ -205,3 +205,15 @@ function identity(value) {
205
205
  return value;
206
206
  }
207
207
  exports.identity = identity;
208
+ function pickDefined(obj) {
209
+ if (!obj)
210
+ return undefined;
211
+ const res = {};
212
+ for (const key in obj) {
213
+ if (obj[key] !== undefined) {
214
+ res[key] = obj[key];
215
+ }
216
+ }
217
+ return res;
218
+ }
219
+ exports.pickDefined = pickDefined;
package/lib/visitors.d.ts CHANGED
@@ -70,9 +70,10 @@ declare type Oas3FlatVisitor = {
70
70
  Info?: VisitFunctionOrObject<Oas3Info>;
71
71
  Contact?: VisitFunctionOrObject<Oas3Contact>;
72
72
  License?: VisitFunctionOrObject<Oas3License>;
73
- PathsMap?: VisitFunctionOrObject<Record<string, Oas3PathItem>>;
73
+ Paths?: VisitFunctionOrObject<Record<string, Oas3PathItem>>;
74
74
  PathItem?: VisitFunctionOrObject<Oas3PathItem>;
75
- Callback?: VisitFunctionOrObject<Record<string, Oas3PathItem>>;
75
+ Callback?: VisitFunctionOrObject<Oas3Callback>;
76
+ CallbacksMap?: VisitFunctionOrObject<Record<string, Oas3Callback>>;
76
77
  Parameter?: VisitFunctionOrObject<Oas3Parameter>;
77
78
  Operation?: VisitFunctionOrObject<Oas3Operation>;
78
79
  RequestBody?: VisitFunctionOrObject<Oas3RequestBody>;
@@ -81,7 +82,7 @@ declare type Oas3FlatVisitor = {
81
82
  Example?: VisitFunctionOrObject<Oas3Example>;
82
83
  Encoding?: VisitFunctionOrObject<Oas3Encoding>;
83
84
  Header?: VisitFunctionOrObject<Oas3Header>;
84
- ResponsesMap?: VisitFunctionOrObject<Record<string, Oas3Response>>;
85
+ Responses?: VisitFunctionOrObject<Record<string, Oas3Response>>;
85
86
  Response?: VisitFunctionOrObject<Oas3Response>;
86
87
  Link?: VisitFunctionOrObject<Oas3Link>;
87
88
  Schema?: VisitFunctionOrObject<Oas3Schema>;
@@ -103,7 +104,7 @@ declare type Oas3FlatVisitor = {
103
104
  PasswordFlow?: VisitFunctionOrObject<Oas3SecurityScheme['flows']['password']>;
104
105
  ClientCredentials?: VisitFunctionOrObject<Oas3SecurityScheme['flows']['clientCredentials']>;
105
106
  AuthorizationCode?: VisitFunctionOrObject<Oas3SecurityScheme['flows']['authorizationCode']>;
106
- SecuritySchemeFlows?: VisitFunctionOrObject<Oas3SecurityScheme['flows']>;
107
+ OAuth2Flows?: VisitFunctionOrObject<Oas3SecurityScheme['flows']>;
107
108
  SecurityScheme?: VisitFunctionOrObject<Oas3SecurityScheme>;
108
109
  };
109
110
  declare type Oas2FlatVisitor = {
@@ -114,13 +115,13 @@ declare type Oas2FlatVisitor = {
114
115
  Info?: VisitFunctionOrObject<Oas2Info>;
115
116
  Contact?: VisitFunctionOrObject<Oas2Contact>;
116
117
  License?: VisitFunctionOrObject<Oas2License>;
117
- PathsMap?: VisitFunctionOrObject<Record<string, Oas2PathItem>>;
118
+ Paths?: VisitFunctionOrObject<Record<string, Oas2PathItem>>;
118
119
  PathItem?: VisitFunctionOrObject<Oas2PathItem>;
119
120
  Parameter?: VisitFunctionOrObject<any>;
120
121
  Operation?: VisitFunctionOrObject<Oas2Operation>;
121
122
  Examples?: VisitFunctionOrObject<Record<string, any>>;
122
123
  Header?: VisitFunctionOrObject<Oas2Header>;
123
- ResponsesMap?: VisitFunctionOrObject<Record<string, Oas2Response>>;
124
+ Responses?: VisitFunctionOrObject<Record<string, Oas2Response>>;
124
125
  Response?: VisitFunctionOrObject<Oas2Response>;
125
126
  Schema?: VisitFunctionOrObject<Oas2Schema>;
126
127
  Xml?: VisitFunctionOrObject<Oas2Xml>;
package/lib/visitors.js CHANGED
@@ -4,13 +4,14 @@ exports.normalizeVisitors = void 0;
4
4
  const legacyTypesMap = {
5
5
  Root: 'DefinitionRoot',
6
6
  ServerVariablesMap: 'ServerVariableMap',
7
- PathsMap: 'PathMap',
7
+ Paths: ['PathMap', 'PathsMap'],
8
8
  CallbacksMap: 'CallbackMap',
9
9
  MediaTypesMap: 'MediaTypeMap',
10
10
  ExamplesMap: 'ExampleMap',
11
- EncodingsMap: 'EncodingMap',
11
+ EncodingMap: 'EncodingsMap',
12
12
  HeadersMap: 'HeaderMap',
13
13
  LinksMap: 'LinkMap',
14
+ OAuth2Flows: 'SecuritySchemeFlows',
14
15
  };
15
16
  function normalizeVisitors(visitorsConfig, types) {
16
17
  const normalizedVisitors = {};
@@ -85,6 +86,13 @@ function normalizeVisitors(visitorsConfig, types) {
85
86
  }
86
87
  }
87
88
  }
89
+ function findLegacyVisitorNode(visitor, typeName) {
90
+ if (Array.isArray(typeName)) {
91
+ const name = typeName.find((name) => visitor[name]) || undefined;
92
+ return name && visitor[name];
93
+ }
94
+ return visitor[typeName];
95
+ }
88
96
  function normalizeVisitorLevel(ruleConf, visitor, parentContext, depth = 0) {
89
97
  const visitorKeys = Object.keys(types);
90
98
  if (depth === 0) {
@@ -101,7 +109,7 @@ function normalizeVisitors(visitorsConfig, types) {
101
109
  }
102
110
  for (const typeName of visitorKeys) {
103
111
  const typeVisitor = (visitor[typeName] ||
104
- visitor[legacyTypesMap[typeName]]);
112
+ findLegacyVisitorNode(visitor, legacyTypesMap[typeName]));
105
113
  const normalizedTypeVisitor = normalizedVisitors[typeName];
106
114
  if (!typeVisitor)
107
115
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/openapi-core",
3
- "version": "1.0.0-beta.110",
3
+ "version": "1.0.0-beta.111",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "engines": {
@@ -94,7 +94,7 @@ rootType:
94
94
  name: ExternalDocs
95
95
  paths:
96
96
  properties: {}
97
- name: PathsMap
97
+ name: Paths
98
98
  components:
99
99
  properties:
100
100
  parameters:
@@ -4,6 +4,7 @@ import {
4
4
  slash,
5
5
  getMatchingStatusCodeRange,
6
6
  doesYamlFileExist,
7
+ pickDefined,
7
8
  } from '../utils';
8
9
  import { isBrowser } from '../env';
9
10
  import * as fs from 'fs';
@@ -81,6 +82,16 @@ describe('utils', () => {
81
82
  });
82
83
  });
83
84
 
85
+ describe('pickDefined', () => {
86
+ it('returns undefined for undefined', () => {
87
+ expect(pickDefined(undefined)).toBeUndefined();
88
+ });
89
+
90
+ it('picks only defined values', () => {
91
+ expect(pickDefined({ a: 1, b: undefined, c: 3 })).toStrictEqual({ a: 1, c: 3 });
92
+ });
93
+ });
94
+
84
95
  describe('getMatchingStatusCodeRange', () => {
85
96
  it('should get the generalized form of status codes', () => {
86
97
  expect(getMatchingStatusCodeRange('202')).toEqual('2XX');
@@ -1115,7 +1115,7 @@ describe('walk order', () => {
1115
1115
  expect(calls).toMatchInlineSnapshot(`
1116
1116
  Array [
1117
1117
  "enter Root",
1118
- "enter PathsMap",
1118
+ "enter Paths",
1119
1119
  "enter PathItem",
1120
1120
  "enter ParameterList",
1121
1121
  "enter Parameter",
@@ -1134,7 +1134,7 @@ describe('walk order', () => {
1134
1134
  "leave ParameterList",
1135
1135
  "leave Operation",
1136
1136
  "leave PathItem",
1137
- "leave PathsMap",
1137
+ "leave Paths",
1138
1138
  "enter Components",
1139
1139
  "enter NamedParameters",
1140
1140
  "leave NamedParameters",
@@ -464,4 +464,29 @@ describe('resolveConfig', () => {
464
464
  delete apis['petstore'].styleguide.pluginPaths;
465
465
  expect(apis['petstore'].styleguide).toMatchSnapshot();
466
466
  });
467
+
468
+ it('should default to the extends from the main config if no extends defined', async () => {
469
+ const rawConfig: RawConfig = {
470
+ apis: {
471
+ petstore: {
472
+ root: 'some/path',
473
+ styleguide: {
474
+ rules: {
475
+ 'operation-4xx-response': 'error',
476
+ },
477
+ },
478
+ },
479
+ },
480
+ styleguide: {
481
+ extends: ['minimal'],
482
+ rules: {
483
+ 'operation-2xx-response': 'warn',
484
+ },
485
+ },
486
+ };
487
+
488
+ const { apis } = await resolveConfig(rawConfig, configPath);
489
+ expect(apis['petstore'].styleguide.rules).toBeDefined();
490
+ expect(apis['petstore'].styleguide.rules?.['operation-2xx-response']).toEqual('warn'); // from minimal ruleset
491
+ });
467
492
  });
@@ -1,5 +1,6 @@
1
1
  import * as path from 'path';
2
2
  import { isAbsoluteUrl } from '../ref-utils';
3
+ import { pickDefined } from '../utils';
3
4
  import { BaseResolver } from '../resolve';
4
5
  import { defaultPlugin } from './builtIn';
5
6
  import {
@@ -355,7 +356,7 @@ function getMergedRawStyleguideConfig(
355
356
  ) {
356
357
  const resultLint = {
357
358
  ...rootStyleguideConfig,
358
- ...apiStyleguideConfig,
359
+ ...pickDefined(apiStyleguideConfig),
359
360
  rules: { ...rootStyleguideConfig?.rules, ...apiStyleguideConfig?.rules },
360
361
  oas2Rules: { ...rootStyleguideConfig?.oas2Rules, ...apiStyleguideConfig?.oas2Rules },
361
362
  oas3_0Rules: { ...rootStyleguideConfig?.oas3_0Rules, ...apiStyleguideConfig?.oas3_0Rules },
@@ -88,4 +88,41 @@ describe('Oas3 operation-2xx-response', () => {
88
88
 
89
89
  expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
90
90
  });
91
+
92
+ it('should report even if the responses are null', async () => {
93
+ const document = parseYamlToDocument(
94
+ outdent`
95
+ openapi: 3.0.0
96
+ paths:
97
+ '/test/':
98
+ put:
99
+ responses: null
100
+ `,
101
+ 'foobar.yaml'
102
+ );
103
+
104
+ const results = await lintDocument({
105
+ externalRefResolver: new BaseResolver(),
106
+ document,
107
+ config: await makeConfig({ 'operation-2xx-response': 'error' }),
108
+ });
109
+
110
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
111
+ Array [
112
+ Object {
113
+ "location": Array [
114
+ Object {
115
+ "pointer": "#/paths/~1test~1/put/responses",
116
+ "reportOnKey": true,
117
+ "source": "foobar.yaml",
118
+ },
119
+ ],
120
+ "message": "Operation must have at least one \`2XX\` response.",
121
+ "ruleId": "operation-2xx-response",
122
+ "severity": "error",
123
+ "suggest": Array [],
124
+ },
125
+ ]
126
+ `);
127
+ });
91
128
  });
@@ -127,4 +127,41 @@ describe('Oas3 operation-4xx-response', () => {
127
127
  ]
128
128
  `);
129
129
  });
130
+
131
+ it('should report even if the responses are null', async () => {
132
+ const document = parseYamlToDocument(
133
+ outdent`
134
+ openapi: 3.0.0
135
+ paths:
136
+ '/test/':
137
+ put:
138
+ responses: null
139
+ `,
140
+ 'foobar.yaml'
141
+ );
142
+
143
+ const results = await lintDocument({
144
+ externalRefResolver: new BaseResolver(),
145
+ document,
146
+ config: await makeConfig({ 'operation-2xx-response': 'error' }),
147
+ });
148
+
149
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
150
+ Array [
151
+ Object {
152
+ "location": Array [
153
+ Object {
154
+ "pointer": "#/paths/~1test~1/put/responses",
155
+ "reportOnKey": true,
156
+ "source": "foobar.yaml",
157
+ },
158
+ ],
159
+ "message": "Operation must have at least one \`2XX\` response.",
160
+ "ruleId": "operation-2xx-response",
161
+ "severity": "error",
162
+ "suggest": Array [],
163
+ },
164
+ ]
165
+ `);
166
+ });
130
167
  });
@@ -130,4 +130,73 @@ describe('Oas3 path-params-defined', () => {
130
130
  ]
131
131
  `);
132
132
  });
133
+
134
+ it('should fail cause POST has no parameters', async () => {
135
+ const document = parseYamlToDocument(
136
+ outdent`
137
+ openapi: 3.0.0
138
+ paths:
139
+ /pets/{a}:
140
+ get:
141
+ parameters:
142
+ - name: a
143
+ in: path
144
+ post:
145
+ description: without parameters
146
+ `,
147
+ 'foobar.yaml'
148
+ );
149
+
150
+ const results = await lintDocument({
151
+ externalRefResolver: new BaseResolver(),
152
+ document,
153
+ config: await makeConfig({ 'path-params-defined': 'error' }),
154
+ });
155
+
156
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
157
+ Array [
158
+ Object {
159
+ "location": Array [
160
+ Object {
161
+ "pointer": "#/paths/~1pets~1{a}/post/parameters",
162
+ "reportOnKey": true,
163
+ "source": "foobar.yaml",
164
+ },
165
+ ],
166
+ "message": "The operation does not define the path parameter \`{a}\` expected by path \`/pets/{a}\`.",
167
+ "ruleId": "path-params-defined",
168
+ "severity": "error",
169
+ "suggest": Array [],
170
+ },
171
+ ]
172
+ `);
173
+ });
174
+
175
+ it('should apply parameters for POST operation from path parameters', async () => {
176
+ const document = parseYamlToDocument(
177
+ outdent`
178
+ openapi: 3.0.0
179
+ paths:
180
+ /pets/{a}:
181
+ parameters:
182
+ - name: a
183
+ in: path
184
+ get:
185
+ parameters:
186
+ - name: a
187
+ in: path
188
+ post:
189
+ description: without parameters
190
+ `,
191
+ 'foobar.yaml'
192
+ );
193
+
194
+ const results = await lintDocument({
195
+ externalRefResolver: new BaseResolver(),
196
+ document,
197
+ config: await makeConfig({ 'path-params-defined': 'error' }),
198
+ });
199
+
200
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
201
+ });
133
202
  });
@@ -81,12 +81,12 @@ describe('Oas3 security-defined', () => {
81
81
  Object {
82
82
  "location": Array [
83
83
  Object {
84
- "pointer": "#/",
85
- "reportOnKey": false,
84
+ "pointer": "#/paths/~1pets/get",
85
+ "reportOnKey": true,
86
86
  "source": "foobar.yaml",
87
87
  },
88
88
  ],
89
- "message": "Every API should have security defined on the root level or for each operation.",
89
+ "message": "Every operation should have security defined on it or on the root level.",
90
90
  "ruleId": "security-defined",
91
91
  "severity": "error",
92
92
  "suggest": Array [],
@@ -133,12 +133,12 @@ describe('Oas3 security-defined', () => {
133
133
  Object {
134
134
  "location": Array [
135
135
  Object {
136
- "pointer": "#/",
137
- "reportOnKey": false,
136
+ "pointer": "#/paths/~1cats/get",
137
+ "reportOnKey": true,
138
138
  "source": "foobar.yaml",
139
139
  },
140
140
  ],
141
- "message": "Every API should have security defined on the root level or for each operation.",
141
+ "message": "Every operation should have security defined on it or on the root level.",
142
142
  "ruleId": "security-defined",
143
143
  "severity": "error",
144
144
  "suggest": Array [],
@@ -139,3 +139,128 @@ describe('Oas3 spec', () => {
139
139
  `);
140
140
  });
141
141
  });
142
+
143
+ describe('Oas3.1 spec', () => {
144
+ it('should report with "type can be one of the following only"', async () => {
145
+ const document = parseYamlToDocument(
146
+ outdent`
147
+ openapi: 3.1.0
148
+ info:
149
+ version: 1.0.0
150
+ title: Example.com
151
+ description: info,
152
+ license:
153
+ name: Apache 2.0
154
+ url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
155
+ components:
156
+ schemas:
157
+ TestSchema:
158
+ title: TestSchema
159
+ description: Property name's description
160
+ type: test
161
+ `
162
+ );
163
+
164
+ const results = await lintDocument({
165
+ externalRefResolver: new BaseResolver(),
166
+ document,
167
+ config: await makeConfig({ spec: 'error' }),
168
+ });
169
+
170
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
171
+ Array [
172
+ Object {
173
+ "from": undefined,
174
+ "location": Array [
175
+ Object {
176
+ "pointer": "#/components/schemas/TestSchema/type",
177
+ "reportOnKey": false,
178
+ "source": "",
179
+ },
180
+ ],
181
+ "message": "\`type\` can be one of the following only: \\"object\\", \\"array\\", \\"string\\", \\"number\\", \\"integer\\", \\"boolean\\", \\"null\\".",
182
+ "ruleId": "spec",
183
+ "severity": "error",
184
+ "suggest": Array [],
185
+ },
186
+ ]
187
+ `);
188
+ });
189
+
190
+ it('should report with unknown type in type`s list', async () => {
191
+ const document = parseYamlToDocument(
192
+ outdent`
193
+ openapi: 3.1.0
194
+ info:
195
+ version: 1.0.0
196
+ title: Example.com
197
+ description: info,
198
+ license:
199
+ name: Apache 2.0
200
+ url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
201
+ components:
202
+ schemas:
203
+ TestSchema:
204
+ title: TestSchema
205
+ description: Property name's description
206
+ type:
207
+ - string
208
+ - foo
209
+ `
210
+ );
211
+
212
+ const results = await lintDocument({
213
+ externalRefResolver: new BaseResolver(),
214
+ document,
215
+ config: await makeConfig({ spec: 'error' }),
216
+ });
217
+
218
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
219
+ Array [
220
+ Object {
221
+ "from": undefined,
222
+ "location": Array [
223
+ Object {
224
+ "pointer": "#/components/schemas/TestSchema/type/1",
225
+ "reportOnKey": false,
226
+ "source": "",
227
+ },
228
+ ],
229
+ "message": "\`type\` can be one of the following only: \\"object\\", \\"array\\", \\"string\\", \\"number\\", \\"integer\\", \\"boolean\\", \\"null\\".",
230
+ "ruleId": "spec",
231
+ "severity": "error",
232
+ "suggest": Array [],
233
+ },
234
+ ]
235
+ `);
236
+ });
237
+
238
+ it('should not report about unknown type', async () => {
239
+ const document = parseYamlToDocument(
240
+ outdent`
241
+ openapi: 3.1.0
242
+ info:
243
+ version: 1.0.0
244
+ title: Example.com
245
+ description: info,
246
+ license:
247
+ name: Apache 2.0
248
+ url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
249
+ components:
250
+ schemas:
251
+ TestSchema:
252
+ title: TestSchema
253
+ description: Property name's description
254
+ type: null
255
+ `
256
+ );
257
+
258
+ const results = await lintDocument({
259
+ externalRefResolver: new BaseResolver(),
260
+ document,
261
+ config: await makeConfig({ spec: 'error' }),
262
+ });
263
+
264
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
265
+ });
266
+ });
@@ -65,7 +65,7 @@ describe('Oas3 assertions', () => {
65
65
  matchParentKeys: ['put'],
66
66
  },
67
67
  {
68
- type: 'ResponsesMap',
68
+ type: 'Responses',
69
69
  matchParentKeys: [201, 200],
70
70
  },
71
71
  ];
@@ -75,7 +75,7 @@ describe('Oas3 assertions', () => {
75
75
  expect(visitors).toMatchInlineSnapshot(`
76
76
  Object {
77
77
  "Operation": Object {
78
- "ResponsesMap": Object {
78
+ "Responses": Object {
79
79
  "MediaTypesMap": [Function],
80
80
  "skip": [Function],
81
81
  },
@@ -5,7 +5,7 @@ import { Oas2Paths } from '../../typings/swagger';
5
5
 
6
6
  export const NoAmbiguousPaths: Oas3Rule | Oas2Rule = () => {
7
7
  return {
8
- PathsMap(pathMap: Oas3Paths | Oas2Paths, { report, location }: UserContext) {
8
+ Paths(pathMap: Oas3Paths | Oas2Paths, { report, location }: UserContext) {
9
9
  const seenPaths: string[] = [];
10
10
 
11
11
  for (const currentPath of Object.keys(pathMap)) {
@@ -5,18 +5,18 @@ import { Oas2Paths } from '../../typings/swagger';
5
5
 
6
6
  export const NoIdenticalPaths: Oas3Rule | Oas2Rule = () => {
7
7
  return {
8
- PathsMap(pathMap: Oas3Paths | Oas2Paths, { report, location }: UserContext) {
9
- const pathsMap = new Map<string, string>();
8
+ Paths(pathMap: Oas3Paths | Oas2Paths, { report, location }: UserContext) {
9
+ const Paths = new Map<string, string>();
10
10
  for (const pathName of Object.keys(pathMap)) {
11
11
  const id = pathName.replace(/{.+?}/g, '{VARIABLE}');
12
- const existingSamePath = pathsMap.get(id);
12
+ const existingSamePath = Paths.get(id);
13
13
  if (existingSamePath) {
14
14
  report({
15
15
  message: `The path already exists which differs only by path parameter name(s): \`${existingSamePath}\` and \`${pathName}\`.`,
16
16
  location: location.child([pathName]).key(),
17
17
  });
18
18
  } else {
19
- pathsMap.set(id, pathName);
19
+ Paths.set(id, pathName);
20
20
  }
21
21
  }
22
22
  },
@@ -3,8 +3,8 @@ import { UserContext } from '../../walk';
3
3
 
4
4
  export const Operation2xxResponse: Oas3Rule | Oas2Rule = () => {
5
5
  return {
6
- ResponsesMap(responses: Record<string, object>, { report }: UserContext) {
7
- const codes = Object.keys(responses);
6
+ Responses(responses: Record<string, object>, { report }: UserContext) {
7
+ const codes = Object.keys(responses || {});
8
8
  if (!codes.some((code) => code === 'default' || /2[Xx0-9]{2}/.test(code))) {
9
9
  report({
10
10
  message: 'Operation must have at least one `2XX` response.',