@redocly/openapi-core 1.0.0-beta.106 → 1.0.0-beta.109

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 (193) hide show
  1. package/lib/benchmark/benches/lint-with-top-level-rule-report.bench.js +0 -1
  2. package/lib/benchmark/benches/resolve-with-no-external.bench.js +1 -1
  3. package/lib/bundle.d.ts +1 -1
  4. package/lib/bundle.js +9 -6
  5. package/lib/config/all.js +5 -3
  6. package/lib/config/config-resolvers.js +32 -14
  7. package/lib/config/config.d.ts +3 -5
  8. package/lib/config/config.js +7 -4
  9. package/lib/config/load.d.ts +7 -0
  10. package/lib/config/load.js +14 -6
  11. package/lib/config/minimal.js +7 -4
  12. package/lib/config/recommended.js +7 -4
  13. package/lib/config/rules.d.ts +1 -1
  14. package/lib/config/rules.js +1 -1
  15. package/lib/config/types.d.ts +7 -0
  16. package/lib/config/utils.d.ts +2 -2
  17. package/lib/config/utils.js +49 -11
  18. package/lib/decorators/common/registry-dependencies.js +2 -2
  19. package/lib/env.d.ts +3 -0
  20. package/lib/env.js +8 -0
  21. package/lib/format/codeframes.js +16 -10
  22. package/lib/format/format.d.ts +1 -1
  23. package/lib/format/format.js +49 -26
  24. package/lib/index.d.ts +5 -5
  25. package/lib/index.js +3 -1
  26. package/lib/js-yaml/index.js +1 -0
  27. package/lib/lint.js +2 -2
  28. package/lib/logger.d.ts +10 -0
  29. package/lib/logger.js +31 -0
  30. package/lib/output.d.ts +3 -0
  31. package/lib/output.js +9 -0
  32. package/lib/redocly/index.js +10 -9
  33. package/lib/redocly/registry-api-types.d.ts +28 -30
  34. package/lib/redocly/registry-api.d.ts +3 -3
  35. package/lib/redocly/registry-api.js +7 -1
  36. package/lib/ref-utils.js +2 -1
  37. package/lib/resolve.d.ts +1 -1
  38. package/lib/resolve.js +4 -2
  39. package/lib/rules/ajv.d.ts +1 -1
  40. package/lib/rules/ajv.js +7 -7
  41. package/lib/rules/common/assertions/asserts.js +4 -4
  42. package/lib/rules/common/assertions/index.js +1 -1
  43. package/lib/rules/common/no-ambiguous-paths.js +1 -1
  44. package/lib/rules/common/no-identical-paths.js +1 -1
  45. package/lib/rules/common/no-invalid-parameter-examples.js +3 -3
  46. package/lib/rules/common/no-invalid-schema-examples.js +3 -3
  47. package/lib/rules/common/operation-2xx-response.js +1 -1
  48. package/lib/rules/common/operation-4xx-response.js +1 -1
  49. package/lib/rules/common/operation-operationId.js +1 -1
  50. package/lib/rules/common/operation-tag-defined.js +1 -1
  51. package/lib/rules/common/path-not-include-query.js +1 -1
  52. package/lib/rules/common/security-defined.d.ts +2 -0
  53. package/lib/rules/common/{operation-security-defined.js → security-defined.js} +19 -5
  54. package/lib/rules/common/spec.js +14 -3
  55. package/lib/rules/common/tags-alphabetical.js +1 -1
  56. package/lib/rules/oas2/index.d.ts +1 -1
  57. package/lib/rules/oas2/index.js +2 -2
  58. package/lib/rules/oas2/remove-unused-components.js +3 -3
  59. package/lib/rules/oas2/request-mime-type.js +1 -1
  60. package/lib/rules/oas2/response-mime-type.js +1 -1
  61. package/lib/rules/oas3/index.js +8 -4
  62. package/lib/rules/oas3/no-empty-servers.js +1 -1
  63. package/lib/rules/oas3/no-invalid-media-type-examples.js +2 -2
  64. package/lib/rules/oas3/no-server-variables-empty-enum.d.ts +2 -0
  65. package/lib/rules/oas3/{no-servers-empty-enum.js → no-server-variables-empty-enum.js} +5 -5
  66. package/lib/rules/oas3/no-unused-components.js +2 -2
  67. package/lib/rules/oas3/operation-4xx-problem-details-rfc7807.d.ts +5 -0
  68. package/lib/rules/oas3/operation-4xx-problem-details-rfc7807.js +36 -0
  69. package/lib/rules/oas3/remove-unused-components.js +4 -4
  70. package/lib/rules/oas3/request-mime-type.js +1 -1
  71. package/lib/rules/oas3/response-mime-type.js +1 -1
  72. package/lib/rules/oas3/spec-components-invalid-map-name.d.ts +2 -0
  73. package/lib/rules/oas3/spec-components-invalid-map-name.js +46 -0
  74. package/lib/rules/other/stats.d.ts +2 -2
  75. package/lib/rules/other/stats.js +2 -2
  76. package/lib/rules/utils.d.ts +3 -2
  77. package/lib/rules/utils.js +16 -4
  78. package/lib/types/oas2.js +5 -5
  79. package/lib/types/oas3.js +27 -20
  80. package/lib/types/oas3_1.js +3 -3
  81. package/lib/types/redocly-yaml.js +47 -56
  82. package/lib/utils.d.ts +6 -1
  83. package/lib/utils.js +24 -7
  84. package/lib/visitors.d.ts +12 -12
  85. package/lib/visitors.js +15 -3
  86. package/lib/walk.d.ts +2 -1
  87. package/lib/walk.js +6 -3
  88. package/package.json +2 -2
  89. package/src/__tests__/__snapshots__/bundle.test.ts.snap +3 -3
  90. package/src/__tests__/fixtures/extension.js +3 -3
  91. package/src/__tests__/format.test.ts +76 -0
  92. package/src/__tests__/lint.test.ts +106 -131
  93. package/src/__tests__/logger-browser.test.ts +53 -0
  94. package/src/__tests__/logger.test.ts +47 -0
  95. package/src/__tests__/output-browser.test.ts +18 -0
  96. package/src/__tests__/output.test.ts +15 -0
  97. package/src/__tests__/resolve-http.test.ts +1 -1
  98. package/src/__tests__/resolve.test.ts +9 -9
  99. package/src/__tests__/utils-browser.test.ts +11 -0
  100. package/src/__tests__/utils.test.ts +7 -0
  101. package/src/__tests__/walk.test.ts +78 -10
  102. package/src/benchmark/benches/lint-with-top-level-rule-report.bench.ts +0 -1
  103. package/src/benchmark/benches/resolve-with-no-external.bench.ts +1 -1
  104. package/src/bundle.ts +10 -7
  105. package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +12 -6
  106. package/src/config/__tests__/config.test.ts +35 -0
  107. package/src/config/__tests__/fixtures/plugin-config.yaml +2 -3
  108. package/src/config/__tests__/fixtures/resolve-config/api/nested-config.yaml +11 -12
  109. package/src/config/__tests__/fixtures/resolve-config/local-config-with-circular.yaml +7 -8
  110. package/src/config/__tests__/fixtures/resolve-config/local-config-with-file.yaml +18 -19
  111. package/src/config/__tests__/fixtures/resolve-config/local-config.yaml +9 -10
  112. package/src/config/__tests__/fixtures/resolve-remote-configs/nested-remote-config.yaml +3 -4
  113. package/src/config/__tests__/fixtures/resolve-remote-configs/remote-config.yaml +4 -5
  114. package/src/config/__tests__/load.test.ts +76 -1
  115. package/src/config/__tests__/utils.test.ts +64 -4
  116. package/src/config/all.ts +5 -3
  117. package/src/config/config-resolvers.ts +45 -19
  118. package/src/config/config.ts +10 -8
  119. package/src/config/load.ts +31 -7
  120. package/src/config/minimal.ts +7 -4
  121. package/src/config/recommended.ts +7 -4
  122. package/src/config/rules.ts +2 -2
  123. package/src/config/types.ts +11 -0
  124. package/src/config/utils.ts +115 -25
  125. package/src/decorators/common/registry-dependencies.ts +2 -2
  126. package/src/env.ts +5 -0
  127. package/src/format/codeframes.ts +15 -9
  128. package/src/format/format.ts +59 -34
  129. package/src/index.ts +6 -4
  130. package/src/js-yaml/index.ts +1 -0
  131. package/src/lint.ts +2 -2
  132. package/src/logger.ts +34 -0
  133. package/src/output.ts +7 -0
  134. package/src/redocly/index.ts +7 -4
  135. package/src/redocly/registry-api-types.ts +27 -29
  136. package/src/redocly/registry-api.ts +18 -7
  137. package/src/ref-utils.ts +2 -1
  138. package/src/resolve.ts +7 -5
  139. package/src/rules/__tests__/utils.test.ts +39 -1
  140. package/src/rules/ajv.ts +7 -7
  141. package/src/rules/common/__tests__/no-enum-type-mismatch.test.ts +1 -0
  142. package/src/rules/common/__tests__/operation-2xx-response.test.ts +1 -1
  143. package/src/rules/common/__tests__/operation-4xx-response.test.ts +26 -3
  144. package/src/rules/common/__tests__/security-defined.test.ts +175 -0
  145. package/src/rules/common/__tests__/spec.test.ts +79 -0
  146. package/src/rules/common/assertions/__tests__/utils.test.ts +2 -2
  147. package/src/rules/common/assertions/asserts.ts +4 -4
  148. package/src/rules/common/assertions/index.ts +1 -1
  149. package/src/rules/common/no-ambiguous-paths.ts +1 -1
  150. package/src/rules/common/no-identical-paths.ts +1 -1
  151. package/src/rules/common/no-invalid-parameter-examples.ts +4 -4
  152. package/src/rules/common/no-invalid-schema-examples.ts +4 -4
  153. package/src/rules/common/operation-2xx-response.ts +1 -1
  154. package/src/rules/common/operation-4xx-response.ts +1 -1
  155. package/src/rules/common/operation-operationId.ts +1 -1
  156. package/src/rules/common/operation-tag-defined.ts +1 -1
  157. package/src/rules/common/path-not-include-query.ts +1 -1
  158. package/src/rules/common/{operation-security-defined.ts → security-defined.ts} +20 -5
  159. package/src/rules/common/spec.ts +17 -3
  160. package/src/rules/common/tags-alphabetical.ts +1 -1
  161. package/src/rules/oas2/index.ts +2 -2
  162. package/src/rules/oas2/remove-unused-components.ts +3 -3
  163. package/src/rules/oas2/request-mime-type.ts +1 -1
  164. package/src/rules/oas2/response-mime-type.ts +1 -1
  165. package/src/rules/oas3/__tests__/no-empty-enum-servers.com.test.ts +16 -16
  166. package/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts +5 -5
  167. package/src/rules/oas3/__tests__/operation-4xx-problem-details-rfc7807.test.ts +145 -0
  168. package/src/rules/oas3/__tests__/spec/spec.test.ts +10 -0
  169. package/src/rules/oas3/__tests__/spec-components-invalid-map-name.test.ts +217 -0
  170. package/src/rules/oas3/index.ts +8 -4
  171. package/src/rules/oas3/no-empty-servers.ts +1 -1
  172. package/src/rules/oas3/no-invalid-media-type-examples.ts +3 -3
  173. package/src/rules/oas3/{no-servers-empty-enum.ts → no-server-variables-empty-enum.ts} +3 -3
  174. package/src/rules/oas3/no-unused-components.ts +2 -2
  175. package/src/rules/oas3/operation-4xx-problem-details-rfc7807.ts +36 -0
  176. package/src/rules/oas3/remove-unused-components.ts +5 -5
  177. package/src/rules/oas3/request-mime-type.ts +1 -1
  178. package/src/rules/oas3/response-mime-type.ts +1 -1
  179. package/src/rules/oas3/spec-components-invalid-map-name.ts +53 -0
  180. package/src/rules/other/stats.ts +2 -2
  181. package/src/rules/utils.ts +17 -3
  182. package/src/types/index.ts +2 -2
  183. package/src/types/oas2.ts +5 -5
  184. package/src/types/oas3.ts +27 -20
  185. package/src/types/oas3_1.ts +3 -3
  186. package/src/types/redocly-yaml.ts +53 -41
  187. package/src/utils.ts +31 -4
  188. package/src/visitors.ts +34 -18
  189. package/src/walk.ts +15 -11
  190. package/tsconfig.tsbuildinfo +1 -1
  191. package/lib/rules/common/operation-security-defined.d.ts +0 -2
  192. package/lib/rules/oas3/no-servers-empty-enum.d.ts +0 -2
  193. package/src/rules/common/__tests__/operation-security-defined.test.ts +0 -69
package/src/rules/ajv.ts CHANGED
@@ -8,7 +8,7 @@ export function releaseAjvInstance() {
8
8
  ajvInstance = null;
9
9
  }
10
10
 
11
- function getAjv(resolve: ResolveFn, disallowAdditionalProperties: boolean) {
11
+ function getAjv(resolve: ResolveFn, allowAdditionalProperties: boolean) {
12
12
  if (!ajvInstance) {
13
13
  ajvInstance = new Ajv({
14
14
  schemaId: '$id',
@@ -20,7 +20,7 @@ function getAjv(resolve: ResolveFn, disallowAdditionalProperties: boolean) {
20
20
  discriminator: true,
21
21
  allowUnionTypes: true,
22
22
  validateFormats: false, // TODO: fix it
23
- defaultAdditionalProperties: !disallowAdditionalProperties,
23
+ defaultAdditionalProperties: allowAdditionalProperties,
24
24
  loadSchemaSync(base: string, $ref: string) {
25
25
  const resolvedRef = resolve({ $ref }, base.split('#')[0]);
26
26
  if (!resolvedRef || !resolvedRef.location) return false;
@@ -36,9 +36,9 @@ function getAjvValidator(
36
36
  schema: any,
37
37
  loc: Location,
38
38
  resolve: ResolveFn,
39
- disallowAdditionalProperties: boolean
39
+ allowAdditionalProperties: boolean
40
40
  ): ValidateFunction | undefined {
41
- const ajv = getAjv(resolve, disallowAdditionalProperties);
41
+ const ajv = getAjv(resolve, allowAdditionalProperties);
42
42
 
43
43
  if (!ajv.getSchema(loc.absolutePointer)) {
44
44
  ajv.addSchema({ $id: loc.absolutePointer, ...schema }, loc.absolutePointer);
@@ -53,9 +53,9 @@ export function validateJsonSchema(
53
53
  schemaLoc: Location,
54
54
  instancePath: string,
55
55
  resolve: ResolveFn,
56
- disallowAdditionalProperties: boolean
56
+ allowAdditionalProperties: boolean
57
57
  ): { valid: boolean; errors: (ErrorObject & { suggest?: string[] })[] } {
58
- const validate = getAjvValidator(schema, schemaLoc, resolve, disallowAdditionalProperties);
58
+ const validate = getAjvValidator(schema, schemaLoc, resolve, allowAdditionalProperties);
59
59
  if (!validate) return { valid: true, errors: [] }; // unresolved refs are reported
60
60
 
61
61
  const valid = validate(data, {
@@ -73,7 +73,7 @@ export function validateJsonSchema(
73
73
 
74
74
  function beatifyErrorMessage(error: ErrorObject) {
75
75
  let message = error.message;
76
- let suggest = error.keyword === 'enum' ? error.params.allowedValues : undefined;
76
+ const suggest = error.keyword === 'enum' ? error.params.allowedValues : undefined;
77
77
  if (suggest) {
78
78
  message += ` ${suggest.map((e: any) => `"${e}"`).join(', ')}`;
79
79
  }
@@ -191,6 +191,7 @@ describe('Oas3 typed enum', () => {
191
191
  expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
192
192
  Array [
193
193
  Object {
194
+ "from": undefined,
194
195
  "location": Array [
195
196
  Object {
196
197
  "pointer": "#/paths/~1some/get/responses/200/content/application~1json/schema",
@@ -34,7 +34,7 @@ describe('Oas3 operation-2xx-response', () => {
34
34
  "source": "foobar.yaml",
35
35
  },
36
36
  ],
37
- "message": "Operation must have at least one \`2xx\` response.",
37
+ "message": "Operation must have at least one \`2XX\` response.",
38
38
  "ruleId": "operation-2xx-response",
39
39
  "severity": "error",
40
40
  "suggest": Array [],
@@ -34,7 +34,7 @@ describe('Oas3 operation-4xx-response', () => {
34
34
  "source": "foobar.yaml",
35
35
  },
36
36
  ],
37
- "message": "Operation must have at least one \`4xx\` response.",
37
+ "message": "Operation must have at least one \`4XX\` response.",
38
38
  "ruleId": "operation-4xx-response",
39
39
  "severity": "error",
40
40
  "suggest": Array [],
@@ -43,7 +43,7 @@ describe('Oas3 operation-4xx-response', () => {
43
43
  `);
44
44
  });
45
45
 
46
- it('should not report for present 4xx response', async () => {
46
+ it('should not report for present 400 response', async () => {
47
47
  const document = parseYamlToDocument(
48
48
  outdent`
49
49
  openapi: 3.0.0
@@ -66,6 +66,29 @@ describe('Oas3 operation-4xx-response', () => {
66
66
  expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
67
67
  });
68
68
 
69
+ it('should not report for present 4XX response', async () => {
70
+ const document = parseYamlToDocument(
71
+ outdent`
72
+ openapi: 3.0.0
73
+ paths:
74
+ '/test/':
75
+ put:
76
+ responses:
77
+ 4XX:
78
+ description: error response
79
+ `,
80
+ 'foobar.yaml'
81
+ );
82
+
83
+ const results = await lintDocument({
84
+ externalRefResolver: new BaseResolver(),
85
+ document,
86
+ config: await makeConfig({ 'operation-4xx-response': 'error' }),
87
+ });
88
+
89
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
90
+ });
91
+
69
92
  it('should report if default is present but missing 4xx response', async () => {
70
93
  const document = parseYamlToDocument(
71
94
  outdent`
@@ -96,7 +119,7 @@ describe('Oas3 operation-4xx-response', () => {
96
119
  "source": "foobar.yaml",
97
120
  },
98
121
  ],
99
- "message": "Operation must have at least one \`4xx\` response.",
122
+ "message": "Operation must have at least one \`4XX\` response.",
100
123
  "ruleId": "operation-4xx-response",
101
124
  "severity": "error",
102
125
  "suggest": Array [],
@@ -0,0 +1,175 @@
1
+ import { outdent } from 'outdent';
2
+ import { lintDocument } from '../../../lint';
3
+ import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
4
+ import { BaseResolver } from '../../../resolve';
5
+
6
+ describe('Oas3 security-defined', () => {
7
+ it('should report on securityRequirements object if security scheme is not defined in components', async () => {
8
+ const document = parseYamlToDocument(
9
+ outdent`
10
+ openapi: 3.0.0
11
+ paths:
12
+ /pets:
13
+ get:
14
+ security:
15
+ - some: []`,
16
+ 'foobar.yaml'
17
+ );
18
+
19
+ const results = await lintDocument({
20
+ externalRefResolver: new BaseResolver(),
21
+ document,
22
+ config: await makeConfig({ 'security-defined': 'error' }),
23
+ });
24
+
25
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
26
+ Array [
27
+ Object {
28
+ "location": Array [
29
+ Object {
30
+ "pointer": "#/paths/~1pets/get/security/0/some",
31
+ "reportOnKey": true,
32
+ "source": "foobar.yaml",
33
+ },
34
+ ],
35
+ "message": "There is no \`some\` security scheme defined.",
36
+ "ruleId": "security-defined",
37
+ "severity": "error",
38
+ "suggest": Array [],
39
+ },
40
+ ]
41
+ `);
42
+ });
43
+
44
+ it('should not report if security defined with an empty array', async () => {
45
+ const document = parseYamlToDocument(
46
+ outdent`
47
+ openapi: 3.0.0
48
+ security: []
49
+ paths:`,
50
+ 'foobar.yaml'
51
+ );
52
+
53
+ const results = await lintDocument({
54
+ externalRefResolver: new BaseResolver(),
55
+ document,
56
+ config: await makeConfig({ 'security-defined': 'error' }),
57
+ });
58
+
59
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
60
+ });
61
+
62
+ it('should report if security not defined at all', async () => {
63
+ const document = parseYamlToDocument(
64
+ outdent`
65
+ openapi: 3.0.0
66
+ paths:
67
+ /pets:
68
+ get:
69
+ requestBody:`,
70
+ 'foobar.yaml'
71
+ );
72
+
73
+ const results = await lintDocument({
74
+ externalRefResolver: new BaseResolver(),
75
+ document,
76
+ config: await makeConfig({ 'security-defined': 'error' }),
77
+ });
78
+
79
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
80
+ Array [
81
+ Object {
82
+ "location": Array [
83
+ Object {
84
+ "pointer": "#/",
85
+ "reportOnKey": false,
86
+ "source": "foobar.yaml",
87
+ },
88
+ ],
89
+ "message": "Every API should have security defined on the root level or for each operation.",
90
+ "ruleId": "security-defined",
91
+ "severity": "error",
92
+ "suggest": Array [],
93
+ },
94
+ ]
95
+ `);
96
+ });
97
+
98
+ it('should report if security not defined for each operation', async () => {
99
+ const document = parseYamlToDocument(
100
+ outdent`
101
+ openapi: 3.0.0
102
+ paths:
103
+ /pets:
104
+ get:
105
+ security:
106
+ - some: []
107
+ /cats:
108
+ get:`,
109
+ 'foobar.yaml'
110
+ );
111
+
112
+ const results = await lintDocument({
113
+ externalRefResolver: new BaseResolver(),
114
+ document,
115
+ config: await makeConfig({ 'security-defined': 'error' }),
116
+ });
117
+
118
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
119
+ Array [
120
+ Object {
121
+ "location": Array [
122
+ Object {
123
+ "pointer": "#/paths/~1pets/get/security/0/some",
124
+ "reportOnKey": true,
125
+ "source": "foobar.yaml",
126
+ },
127
+ ],
128
+ "message": "There is no \`some\` security scheme defined.",
129
+ "ruleId": "security-defined",
130
+ "severity": "error",
131
+ "suggest": Array [],
132
+ },
133
+ Object {
134
+ "location": Array [
135
+ Object {
136
+ "pointer": "#/",
137
+ "reportOnKey": false,
138
+ "source": "foobar.yaml",
139
+ },
140
+ ],
141
+ "message": "Every API should have security defined on the root level or for each operation.",
142
+ "ruleId": "security-defined",
143
+ "severity": "error",
144
+ "suggest": Array [],
145
+ },
146
+ ]
147
+ `);
148
+ });
149
+
150
+ it('should not report on securityRequirements object if security scheme is defined in components', async () => {
151
+ const document = parseYamlToDocument(
152
+ outdent`
153
+ openapi: 3.0.0
154
+ paths:
155
+ /pets:
156
+ get:
157
+ security:
158
+ some: []
159
+ components:
160
+ securitySchemes:
161
+ some:
162
+ type: http
163
+ scheme: basic`,
164
+ 'foobar.yaml'
165
+ );
166
+
167
+ const results = await lintDocument({
168
+ externalRefResolver: new BaseResolver(),
169
+ document,
170
+ config: await makeConfig({ 'security-defined': 'error' }),
171
+ });
172
+
173
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
174
+ });
175
+ });
@@ -31,6 +31,7 @@ describe('Oas3 spec', () => {
31
31
  expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
32
32
  Array [
33
33
  Object {
34
+ "from": undefined,
34
35
  "location": Array [
35
36
  Object {
36
37
  "pointer": "#/",
@@ -44,6 +45,7 @@ describe('Oas3 spec', () => {
44
45
  "suggest": Array [],
45
46
  },
46
47
  Object {
48
+ "from": undefined,
47
49
  "location": Array [
48
50
  Object {
49
51
  "pointer": "#/paths/~1test/get/parameters/0",
@@ -59,4 +61,81 @@ describe('Oas3 spec', () => {
59
61
  ]
60
62
  `);
61
63
  });
64
+
65
+ it('should report with "referenced from"', async () => {
66
+ const document = parseYamlToDocument(
67
+ outdent`
68
+ openapi: 3.0.0
69
+ components:
70
+ requestBodies:
71
+ TestRequestBody:
72
+ content:
73
+ application/json:
74
+ schema:
75
+ type: object
76
+ schemas:
77
+ TestSchema:
78
+ title: TestSchema
79
+ allOf:
80
+ - $ref: "#/components/requestBodies/TestRequestBody"
81
+ `,
82
+ 'foobar.yaml'
83
+ );
84
+
85
+ const results = await lintDocument({
86
+ externalRefResolver: new BaseResolver(),
87
+ document,
88
+ config: await makeConfig({ spec: 'error' }),
89
+ });
90
+
91
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
92
+ Array [
93
+ Object {
94
+ "from": undefined,
95
+ "location": Array [
96
+ Object {
97
+ "pointer": "#/",
98
+ "reportOnKey": true,
99
+ "source": "foobar.yaml",
100
+ },
101
+ ],
102
+ "message": "The field \`paths\` must be present on this level.",
103
+ "ruleId": "spec",
104
+ "severity": "error",
105
+ "suggest": Array [],
106
+ },
107
+ Object {
108
+ "from": undefined,
109
+ "location": Array [
110
+ Object {
111
+ "pointer": "#/",
112
+ "reportOnKey": true,
113
+ "source": "foobar.yaml",
114
+ },
115
+ ],
116
+ "message": "The field \`info\` must be present on this level.",
117
+ "ruleId": "spec",
118
+ "severity": "error",
119
+ "suggest": Array [],
120
+ },
121
+ Object {
122
+ "from": Object {
123
+ "pointer": "#/components/schemas/TestSchema/allOf/0",
124
+ "source": "foobar.yaml",
125
+ },
126
+ "location": Array [
127
+ Object {
128
+ "pointer": "#/components/requestBodies/TestRequestBody/content",
129
+ "reportOnKey": true,
130
+ "source": "foobar.yaml",
131
+ },
132
+ ],
133
+ "message": "Property \`content\` is not expected here.",
134
+ "ruleId": "spec",
135
+ "severity": "error",
136
+ "suggest": Array [],
137
+ },
138
+ ]
139
+ `);
140
+ });
62
141
  });
@@ -70,13 +70,13 @@ describe('Oas3 assertions', () => {
70
70
  },
71
71
  ];
72
72
 
73
- const visitors = buildVisitorObject('MediaTypeMap', context, () => {}) as any;
73
+ const visitors = buildVisitorObject('MediaTypesMap', context, () => {}) as any;
74
74
 
75
75
  expect(visitors).toMatchInlineSnapshot(`
76
76
  Object {
77
77
  "Operation": Object {
78
78
  "ResponsesMap": Object {
79
- "MediaTypeMap": [Function],
79
+ "MediaTypesMap": [Function],
80
80
  "skip": [Function],
81
81
  },
82
82
  "skip": [Function],
@@ -46,7 +46,7 @@ export const asserts: Asserts = {
46
46
  if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
47
47
  const values = runOnValue(value) ? [value] : value;
48
48
  const regx = regexFromString(condition);
49
- for (let _val of values) {
49
+ for (const _val of values) {
50
50
  if (!regx?.test(_val)) {
51
51
  return { isValid: false, location: runOnValue(value) ? baseLocation : baseLocation.key() };
52
52
  }
@@ -56,7 +56,7 @@ export const asserts: Asserts = {
56
56
  enum: (value: string | string[], condition: string[], baseLocation: Location) => {
57
57
  if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
58
58
  const values = runOnValue(value) ? [value] : value;
59
- for (let _val of values) {
59
+ for (const _val of values) {
60
60
  if (!condition.includes(_val)) {
61
61
  return {
62
62
  isValid: false,
@@ -81,7 +81,7 @@ export const asserts: Asserts = {
81
81
  disallowed: (value: string | string[], condition: string[], baseLocation: Location) => {
82
82
  if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
83
83
  const values = runOnValue(value) ? [value] : value;
84
- for (let _val of values) {
84
+ for (const _val of values) {
85
85
  if (condition.includes(_val)) {
86
86
  return {
87
87
  isValid: false,
@@ -114,7 +114,7 @@ export const asserts: Asserts = {
114
114
  casing: (value: string | string[], condition: string, baseLocation: Location) => {
115
115
  if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
116
116
  const values: string[] = runOnValue(value) ? [value] : value;
117
- for (let _val of values) {
117
+ for (const _val of values) {
118
118
  let matchCase = false;
119
119
  switch (condition) {
120
120
  case 'camelCase':
@@ -3,7 +3,7 @@ import { AssertToApply, buildSubjectVisitor, buildVisitorObject } from './utils'
3
3
  import { Oas2Rule, Oas3Rule } from '../../../visitors';
4
4
 
5
5
  export const Assertions: Oas3Rule | Oas2Rule = (opts: object) => {
6
- let visitors: any[] = [];
6
+ const visitors: any[] = [];
7
7
 
8
8
  // As 'Assertions' has an array of asserts,
9
9
  // that array spreads into an 'opts' object on init rules phase here
@@ -5,7 +5,7 @@ import { Oas2Paths } from '../../typings/swagger';
5
5
 
6
6
  export const NoAmbiguousPaths: Oas3Rule | Oas2Rule = () => {
7
7
  return {
8
- PathMap(pathMap: Oas3Paths | Oas2Paths, { report, location }: UserContext) {
8
+ PathsMap(pathMap: Oas3Paths | Oas2Paths, { report, location }: UserContext) {
9
9
  const seenPaths: string[] = [];
10
10
 
11
11
  for (const currentPath of Object.keys(pathMap)) {
@@ -5,7 +5,7 @@ import { Oas2Paths } from '../../typings/swagger';
5
5
 
6
6
  export const NoIdenticalPaths: Oas3Rule | Oas2Rule = () => {
7
7
  return {
8
- PathMap(pathMap: Oas3Paths | Oas2Paths, { report, location }: UserContext) {
8
+ PathsMap(pathMap: Oas3Paths | Oas2Paths, { report, location }: UserContext) {
9
9
  const pathsMap = new Map<string, string>();
10
10
  for (const pathName of Object.keys(pathMap)) {
11
11
  const id = pathName.replace(/{.+?}/g, '{VARIABLE}');
@@ -1,9 +1,9 @@
1
1
  import { UserContext } from '../../walk';
2
2
  import { Oas3Parameter } from '../../typings/openapi';
3
- import { validateExample } from '../utils';
3
+ import { getAdditionalPropertiesOption, validateExample } from '../utils';
4
4
 
5
5
  export const NoInvalidParameterExamples: any = (opts: any) => {
6
- const disallowAdditionalProperties = opts.disallowAdditionalProperties ?? true;
6
+ const allowAdditionalProperties = getAdditionalPropertiesOption(opts) ?? false;
7
7
  return {
8
8
  Parameter: {
9
9
  leave(parameter: Oas3Parameter, ctx: UserContext) {
@@ -13,7 +13,7 @@ export const NoInvalidParameterExamples: any = (opts: any) => {
13
13
  parameter.schema!,
14
14
  ctx.location.child('example'),
15
15
  ctx,
16
- disallowAdditionalProperties
16
+ allowAdditionalProperties
17
17
  );
18
18
  }
19
19
 
@@ -25,7 +25,7 @@ export const NoInvalidParameterExamples: any = (opts: any) => {
25
25
  parameter.schema!,
26
26
  ctx.location.child(['examples', key]),
27
27
  ctx,
28
- false
28
+ true
29
29
  );
30
30
  }
31
31
  }
@@ -1,9 +1,9 @@
1
1
  import { UserContext } from '../../walk';
2
2
  import { Oas3_1Schema } from '../../typings/openapi';
3
- import { validateExample } from '../utils';
3
+ import { getAdditionalPropertiesOption, validateExample } from '../utils';
4
4
 
5
5
  export const NoInvalidSchemaExamples: any = (opts: any) => {
6
- const disallowAdditionalProperties = opts.disallowAdditionalProperties ?? true;
6
+ const allowAdditionalProperties = getAdditionalPropertiesOption(opts) ?? false;
7
7
  return {
8
8
  Schema: {
9
9
  leave(schema: Oas3_1Schema, ctx: UserContext) {
@@ -14,12 +14,12 @@ export const NoInvalidSchemaExamples: any = (opts: any) => {
14
14
  schema,
15
15
  ctx.location.child(['examples', schema.examples.indexOf(example)]),
16
16
  ctx,
17
- disallowAdditionalProperties
17
+ allowAdditionalProperties
18
18
  );
19
19
  }
20
20
  }
21
21
  if (schema.example) {
22
- validateExample(schema.example, schema, ctx.location.child('example'), ctx, false);
22
+ validateExample(schema.example, schema, ctx.location.child('example'), ctx, true);
23
23
  }
24
24
  },
25
25
  },
@@ -7,7 +7,7 @@ export const Operation2xxResponse: Oas3Rule | Oas2Rule = () => {
7
7
  const codes = Object.keys(responses);
8
8
  if (!codes.some((code) => code === 'default' || /2[Xx0-9]{2}/.test(code))) {
9
9
  report({
10
- message: 'Operation must have at least one `2xx` response.',
10
+ message: 'Operation must have at least one `2XX` response.',
11
11
  location: { reportOnKey: true },
12
12
  });
13
13
  }
@@ -8,7 +8,7 @@ export const Operation4xxResponse: Oas3Rule | Oas2Rule = () => {
8
8
 
9
9
  if (!codes.some((code) => /4[Xx0-9]{2}/.test(code))) {
10
10
  report({
11
- message: 'Operation must have at least one `4xx` response.',
11
+ message: 'Operation must have at least one `4XX` response.',
12
12
  location: { reportOnKey: true },
13
13
  });
14
14
  }
@@ -6,7 +6,7 @@ import { Oas3Operation } from '../../typings/openapi';
6
6
 
7
7
  export const OperationOperationId: Oas3Rule | Oas2Rule = () => {
8
8
  return {
9
- DefinitionRoot: {
9
+ Root: {
10
10
  PathItem: {
11
11
  Operation(operation: Oas2Operation | Oas3Operation, ctx: UserContext) {
12
12
  validateDefinedAndNonEmpty('operationId', operation, ctx);
@@ -7,7 +7,7 @@ export const OperationTagDefined: Oas3Rule | Oas2Rule = () => {
7
7
  let definedTags: Set<string>;
8
8
 
9
9
  return {
10
- DefinitionRoot(root: Oas2Definition | Oas3Definition) {
10
+ Root(root: Oas2Definition | Oas3Definition) {
11
11
  definedTags = new Set((root.tags ?? []).map((t) => t.name));
12
12
  },
13
13
  Operation(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) {
@@ -3,7 +3,7 @@ import { UserContext } from '../../walk';
3
3
 
4
4
  export const PathNotIncludeQuery: Oas3Rule | Oas2Rule = () => {
5
5
  return {
6
- PathMap: {
6
+ PathsMap: {
7
7
  PathItem(_operation: object, { report, key }: UserContext) {
8
8
  if (key.toString().includes('?')) {
9
9
  report({
@@ -1,11 +1,11 @@
1
1
  import { Oas3Rule, Oas2Rule } from '../../visitors';
2
2
  import { Location } from '../../ref-utils';
3
3
  import { UserContext } from '../../walk';
4
- import { Oas2SecurityScheme } from '../../typings/swagger';
5
- import { Oas3SecurityScheme } from '../../typings/openapi';
4
+ import { Oas2Definition, Oas2Operation, Oas2SecurityScheme } from '../../typings/swagger';
5
+ import { Oas3Definition, Oas3Operation, Oas3SecurityScheme } from '../../typings/openapi';
6
6
 
7
- export const OperationSecurityDefined: Oas3Rule | Oas2Rule = () => {
8
- let referencedSchemes = new Map<
7
+ export const SecurityDefined: Oas3Rule | Oas2Rule = () => {
8
+ const referencedSchemes = new Map<
9
9
  string,
10
10
  {
11
11
  defined?: boolean;
@@ -13,9 +13,11 @@ export const OperationSecurityDefined: Oas3Rule | Oas2Rule = () => {
13
13
  }
14
14
  >();
15
15
 
16
+ let eachOperationHasSecurity: boolean = true;
17
+
16
18
  return {
17
19
  DefinitionRoot: {
18
- leave(_: object, { report }: UserContext) {
20
+ leave(root: Oas2Definition | Oas3Definition, { report }: UserContext) {
19
21
  for (const [name, scheme] of referencedSchemes.entries()) {
20
22
  if (scheme.defined) continue;
21
23
  for (const reportedFromLocation of scheme.from) {
@@ -25,6 +27,14 @@ export const OperationSecurityDefined: Oas3Rule | Oas2Rule = () => {
25
27
  });
26
28
  }
27
29
  }
30
+
31
+ if (root.security || eachOperationHasSecurity) {
32
+ return;
33
+ } else {
34
+ report({
35
+ message: `Every API should have security defined on the root level or for each operation.`,
36
+ });
37
+ }
28
38
  },
29
39
  },
30
40
  SecurityScheme(_securityScheme: Oas2SecurityScheme | Oas3SecurityScheme, { key }: UserContext) {
@@ -41,5 +51,10 @@ export const OperationSecurityDefined: Oas3Rule | Oas2Rule = () => {
41
51
  }
42
52
  }
43
53
  },
54
+ Operation(operation: Oas2Operation | Oas3Operation) {
55
+ if (!operation?.security) {
56
+ eachOperationHasSecurity = false;
57
+ }
58
+ },
44
59
  };
45
60
  };