@redocly/openapi-core 1.0.0-rc.3 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (275) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/__tests__/utils.ts +88 -0
  3. package/lib/config/all.js +0 -1
  4. package/lib/config/minimal.js +0 -1
  5. package/lib/config/recommended.js +0 -1
  6. package/package.json +1 -1
  7. package/src/__tests__/__snapshots__/bundle.test.ts.snap +437 -0
  8. package/src/__tests__/bundle.test.ts +236 -0
  9. package/src/__tests__/codeframes.test.ts +530 -0
  10. package/src/__tests__/fixtures/.redocly.lint-ignore.yaml +5 -0
  11. package/src/__tests__/fixtures/extension.js +24 -0
  12. package/src/__tests__/fixtures/refs/definitions.yaml +3 -0
  13. package/src/__tests__/fixtures/refs/examples.yaml +8 -0
  14. package/src/__tests__/fixtures/refs/external-request-body.yaml +13 -0
  15. package/src/__tests__/fixtures/refs/externalref.yaml +35 -0
  16. package/src/__tests__/fixtures/refs/hosted.yaml +35 -0
  17. package/src/__tests__/fixtures/refs/openapi-with-external-refs-conflicting-names.yaml +21 -0
  18. package/src/__tests__/fixtures/refs/openapi-with-external-refs.yaml +33 -0
  19. package/src/__tests__/fixtures/refs/openapi-with-url-refs.yaml +18 -0
  20. package/src/__tests__/fixtures/refs/param-b.yaml +1 -0
  21. package/src/__tests__/fixtures/refs/param-c.yaml +1 -0
  22. package/src/__tests__/fixtures/refs/rename.yaml +1 -0
  23. package/src/__tests__/fixtures/refs/requestBody.yaml +9 -0
  24. package/src/__tests__/fixtures/refs/schema-a.yaml +1 -0
  25. package/src/__tests__/fixtures/refs/simple.yaml +1 -0
  26. package/src/__tests__/fixtures/refs/vendor.schema.yaml +20 -0
  27. package/src/__tests__/fixtures/resolve/External.yaml +10 -0
  28. package/src/__tests__/fixtures/resolve/External2.yaml +4 -0
  29. package/src/__tests__/fixtures/resolve/description.md +3 -0
  30. package/src/__tests__/fixtures/resolve/externalInfo.yaml +4 -0
  31. package/src/__tests__/fixtures/resolve/externalLicense.yaml +1 -0
  32. package/src/__tests__/fixtures/resolve/openapi-with-back.yaml +13 -0
  33. package/src/__tests__/fixtures/resolve/openapi-with-md-description.yaml +5 -0
  34. package/src/__tests__/fixtures/resolve/openapi.yaml +28 -0
  35. package/src/__tests__/fixtures/resolve/schemas/type-a.yaml +10 -0
  36. package/src/__tests__/fixtures/resolve/schemas/type-b.yaml +6 -0
  37. package/src/__tests__/fixtures/resolve/transitive/a.yaml +1 -0
  38. package/src/__tests__/fixtures/resolve/transitive/components.yaml +5 -0
  39. package/src/__tests__/fixtures/resolve/transitive/schemas.yaml +3 -0
  40. package/src/__tests__/format.test.ts +76 -0
  41. package/src/__tests__/js-yaml.test.ts +73 -0
  42. package/src/__tests__/lint.test.ts +392 -0
  43. package/src/__tests__/logger-browser.test.ts +53 -0
  44. package/src/__tests__/logger.test.ts +47 -0
  45. package/src/__tests__/login.test.ts +17 -0
  46. package/src/__tests__/normalizeVisitors.test.ts +151 -0
  47. package/src/__tests__/output-browser.test.ts +18 -0
  48. package/src/__tests__/output.test.ts +15 -0
  49. package/src/__tests__/ref-utils.test.ts +120 -0
  50. package/src/__tests__/resolve-http.test.ts +77 -0
  51. package/src/__tests__/resolve.test.ts +431 -0
  52. package/src/__tests__/utils-browser.test.ts +11 -0
  53. package/src/__tests__/utils.test.ts +144 -0
  54. package/src/__tests__/walk.test.ts +1545 -0
  55. package/src/benchmark/benches/lint-with-many-rules.bench.ts +35 -0
  56. package/src/benchmark/benches/lint-with-nested-rule.bench.ts +39 -0
  57. package/src/benchmark/benches/lint-with-no-rules.bench.ts +20 -0
  58. package/src/benchmark/benches/lint-with-top-level-rule-report.bench.ts +35 -0
  59. package/src/benchmark/benches/lint-with-top-level-rule.bench.ts +32 -0
  60. package/src/benchmark/benches/rebilly.yaml +32275 -0
  61. package/src/benchmark/benches/recommended-oas3.bench.ts +22 -0
  62. package/src/benchmark/benches/resolve-with-no-external.bench.ts +23 -0
  63. package/src/benchmark/benchmark.js +311 -0
  64. package/src/benchmark/colors.js +29 -0
  65. package/src/benchmark/fork.js +83 -0
  66. package/src/benchmark/utils.ts +36 -0
  67. package/src/bundle.ts +417 -0
  68. package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +164 -0
  69. package/src/config/__tests__/__snapshots__/config.test.ts.snap +144 -0
  70. package/src/config/__tests__/config-resolvers.test.ts +491 -0
  71. package/src/config/__tests__/config.test.ts +312 -0
  72. package/src/config/__tests__/fixtures/ingore-file.ts +8 -0
  73. package/src/config/__tests__/fixtures/load-redocly.yaml +2 -0
  74. package/src/config/__tests__/fixtures/plugin-config.yaml +2 -0
  75. package/src/config/__tests__/fixtures/plugin.js +56 -0
  76. package/src/config/__tests__/fixtures/resolve-config/api/nested-config.yaml +11 -0
  77. package/src/config/__tests__/fixtures/resolve-config/api/plugin.js +69 -0
  78. package/src/config/__tests__/fixtures/resolve-config/local-config-with-circular.yaml +7 -0
  79. package/src/config/__tests__/fixtures/resolve-config/local-config-with-custom-function.yaml +17 -0
  80. package/src/config/__tests__/fixtures/resolve-config/local-config-with-file.yaml +18 -0
  81. package/src/config/__tests__/fixtures/resolve-config/local-config-with-wrong-custom-function.yaml +15 -0
  82. package/src/config/__tests__/fixtures/resolve-config/local-config.yaml +9 -0
  83. package/src/config/__tests__/fixtures/resolve-config/plugin.js +80 -0
  84. package/src/config/__tests__/fixtures/resolve-remote-configs/nested-remote-config.yaml +3 -0
  85. package/src/config/__tests__/fixtures/resolve-remote-configs/remote-config.yaml +4 -0
  86. package/src/config/__tests__/load.test.ts +167 -0
  87. package/src/config/__tests__/resolve-plugins.test.ts +27 -0
  88. package/src/config/__tests__/utils.test.ts +204 -0
  89. package/src/config/all.ts +74 -0
  90. package/src/config/builtIn.ts +37 -0
  91. package/src/config/config-resolvers.ts +474 -0
  92. package/src/config/config.ts +332 -0
  93. package/src/config/index.ts +7 -0
  94. package/src/config/load.ts +144 -0
  95. package/src/config/minimal.ts +61 -0
  96. package/src/config/recommended.ts +61 -0
  97. package/src/config/rules.ts +54 -0
  98. package/src/config/types.ts +231 -0
  99. package/src/config/utils.ts +349 -0
  100. package/src/decorators/__tests__/filter-in.test.ts +310 -0
  101. package/src/decorators/__tests__/filter-out.test.ts +335 -0
  102. package/src/decorators/__tests__/media-type-examples-override.test.ts +665 -0
  103. package/src/decorators/__tests__/remove-x-internal.test.ts +316 -0
  104. package/src/decorators/__tests__/resources/request.yaml +3 -0
  105. package/src/decorators/__tests__/resources/response.yaml +3 -0
  106. package/src/decorators/common/filters/filter-helper.ts +72 -0
  107. package/src/decorators/common/filters/filter-in.ts +18 -0
  108. package/src/decorators/common/filters/filter-out.ts +18 -0
  109. package/src/decorators/common/info-description-override.ts +24 -0
  110. package/src/decorators/common/info-override.ts +15 -0
  111. package/src/decorators/common/media-type-examples-override.ts +79 -0
  112. package/src/decorators/common/operation-description-override.ts +30 -0
  113. package/src/decorators/common/registry-dependencies.ts +25 -0
  114. package/src/decorators/common/remove-x-internal.ts +59 -0
  115. package/src/decorators/common/tag-description-override.ts +25 -0
  116. package/src/decorators/oas2/index.ts +20 -0
  117. package/src/decorators/oas3/index.ts +22 -0
  118. package/src/env.ts +5 -0
  119. package/src/format/codeframes.ts +216 -0
  120. package/src/format/format.ts +375 -0
  121. package/src/index.ts +71 -0
  122. package/src/js-yaml/index.ts +14 -0
  123. package/src/lint.ts +148 -0
  124. package/src/logger.ts +34 -0
  125. package/src/oas-types.ts +57 -0
  126. package/src/output.ts +7 -0
  127. package/src/redocly/__tests__/redocly-client.test.ts +146 -0
  128. package/src/redocly/index.ts +187 -0
  129. package/src/redocly/redocly-client-types.ts +10 -0
  130. package/src/redocly/registry-api-types.ts +32 -0
  131. package/src/redocly/registry-api.ts +150 -0
  132. package/src/ref-utils.ts +85 -0
  133. package/src/resolve.ts +417 -0
  134. package/src/rules/__tests__/fixtures/code-sample.php +9 -0
  135. package/src/rules/__tests__/fixtures/invalid-yaml.yaml +1 -0
  136. package/src/rules/__tests__/fixtures/ref.yaml +1 -0
  137. package/src/rules/__tests__/no-unresolved-refs.test.ts +257 -0
  138. package/src/rules/__tests__/utils.test.ts +160 -0
  139. package/src/rules/ajv.ts +102 -0
  140. package/src/rules/common/__tests__/info-license.test.ts +62 -0
  141. package/src/rules/common/__tests__/license-url.test.ts +63 -0
  142. package/src/rules/common/__tests__/no-ambiguous-paths.test.ts +96 -0
  143. package/src/rules/common/__tests__/no-enum-type-mismatch.test.ts +210 -0
  144. package/src/rules/common/__tests__/no-identical-paths.test.ts +58 -0
  145. package/src/rules/common/__tests__/no-path-trailing-slash.test.ts +85 -0
  146. package/src/rules/common/__tests__/operation-2xx-response.test.ts +192 -0
  147. package/src/rules/common/__tests__/operation-4xx-response.test.ts +231 -0
  148. package/src/rules/common/__tests__/operation-operationId-unique.test.ts +76 -0
  149. package/src/rules/common/__tests__/operation-operationId-url-safe.test.ts +45 -0
  150. package/src/rules/common/__tests__/operation-parameters-unique.test.ts +167 -0
  151. package/src/rules/common/__tests__/operation-singular-tag.test.ts +72 -0
  152. package/src/rules/common/__tests__/path-http-verbs-order.test.ts +95 -0
  153. package/src/rules/common/__tests__/path-not-include-query.test.ts +64 -0
  154. package/src/rules/common/__tests__/path-params-defined.test.ts +202 -0
  155. package/src/rules/common/__tests__/paths-kebab-case.test.ts +108 -0
  156. package/src/rules/common/__tests__/scalar-property-missing-example.test.ts +264 -0
  157. package/src/rules/common/__tests__/security-defined.test.ts +175 -0
  158. package/src/rules/common/__tests__/spec-strict-refs.test.ts +69 -0
  159. package/src/rules/common/__tests__/spec.test.ts +610 -0
  160. package/src/rules/common/__tests__/tag-description.test.ts +65 -0
  161. package/src/rules/common/__tests__/tags-alphabetical.test.ts +64 -0
  162. package/src/rules/common/assertions/__tests__/asserts.test.ts +869 -0
  163. package/src/rules/common/assertions/__tests__/index.test.ts +100 -0
  164. package/src/rules/common/assertions/__tests__/utils.test.ts +236 -0
  165. package/src/rules/common/assertions/asserts.ts +357 -0
  166. package/src/rules/common/assertions/index.ts +53 -0
  167. package/src/rules/common/assertions/utils.ts +331 -0
  168. package/src/rules/common/info-contact.ts +15 -0
  169. package/src/rules/common/info-license-url.ts +10 -0
  170. package/src/rules/common/info-license.ts +15 -0
  171. package/src/rules/common/no-ambiguous-paths.ts +50 -0
  172. package/src/rules/common/no-enum-type-mismatch.ts +52 -0
  173. package/src/rules/common/no-http-verbs-in-paths.ts +36 -0
  174. package/src/rules/common/no-identical-paths.ts +24 -0
  175. package/src/rules/common/no-invalid-parameter-examples.ts +36 -0
  176. package/src/rules/common/no-invalid-schema-examples.ts +27 -0
  177. package/src/rules/common/no-path-trailing-slash.ts +15 -0
  178. package/src/rules/common/operation-2xx-response.ts +24 -0
  179. package/src/rules/common/operation-4xx-response.ts +24 -0
  180. package/src/rules/common/operation-description.ts +13 -0
  181. package/src/rules/common/operation-operationId-unique.ts +21 -0
  182. package/src/rules/common/operation-operationId-url-safe.ts +19 -0
  183. package/src/rules/common/operation-operationId.ts +17 -0
  184. package/src/rules/common/operation-parameters-unique.ts +48 -0
  185. package/src/rules/common/operation-singular-tag.ts +17 -0
  186. package/src/rules/common/operation-summary.ts +13 -0
  187. package/src/rules/common/operation-tag-defined.ts +26 -0
  188. package/src/rules/common/parameter-description.ts +22 -0
  189. package/src/rules/common/path-declaration-must-exist.ts +15 -0
  190. package/src/rules/common/path-excludes-patterns.ts +23 -0
  191. package/src/rules/common/path-http-verbs-order.ts +30 -0
  192. package/src/rules/common/path-not-include-query.ts +17 -0
  193. package/src/rules/common/path-params-defined.ts +65 -0
  194. package/src/rules/common/path-segment-plural.ts +31 -0
  195. package/src/rules/common/paths-kebab-case.ts +19 -0
  196. package/src/rules/common/required-string-property-missing-min-length.ts +44 -0
  197. package/src/rules/common/response-contains-header.ts +35 -0
  198. package/src/rules/common/scalar-property-missing-example.ts +58 -0
  199. package/src/rules/common/security-defined.ts +65 -0
  200. package/src/rules/common/spec-strict-refs.ts +30 -0
  201. package/src/rules/common/spec.ts +175 -0
  202. package/src/rules/common/tag-description.ts +10 -0
  203. package/src/rules/common/tags-alphabetical.ts +20 -0
  204. package/src/rules/no-unresolved-refs.ts +51 -0
  205. package/src/rules/oas2/__tests__/boolean-parameter-prefixes.test.ts +110 -0
  206. package/src/rules/oas2/__tests__/response-contains-header.test.ts +174 -0
  207. package/src/rules/oas2/__tests__/response-contains-property.test.ts +155 -0
  208. package/src/rules/oas2/__tests__/spec/fixtures/description.md +1 -0
  209. package/src/rules/oas2/__tests__/spec/info.test.ts +355 -0
  210. package/src/rules/oas2/__tests__/spec/operation.test.ts +123 -0
  211. package/src/rules/oas2/__tests__/spec/paths.test.ts +245 -0
  212. package/src/rules/oas2/__tests__/spec/referenceableScalars.test.ts +35 -0
  213. package/src/rules/oas2/__tests__/spec/utils.ts +32 -0
  214. package/src/rules/oas2/boolean-parameter-prefixes.ts +26 -0
  215. package/src/rules/oas2/index.ts +91 -0
  216. package/src/rules/oas2/remove-unused-components.ts +81 -0
  217. package/src/rules/oas2/request-mime-type.ts +16 -0
  218. package/src/rules/oas2/response-contains-property.ts +36 -0
  219. package/src/rules/oas2/response-mime-type.ts +16 -0
  220. package/src/rules/oas3/__tests__/boolean-parameter-prefixes.test.ts +111 -0
  221. package/src/rules/oas3/__tests__/component-name-unique.test.ts +823 -0
  222. package/src/rules/oas3/__tests__/fixtures/common.yaml +11 -0
  223. package/src/rules/oas3/__tests__/no-empty-enum-servers.com.test.ts +205 -0
  224. package/src/rules/oas3/__tests__/no-example-value-and-externalValue.test.ts +65 -0
  225. package/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts +473 -0
  226. package/src/rules/oas3/__tests__/no-server-example.com.test.ts +60 -0
  227. package/src/rules/oas3/__tests__/no-server-trailing-slash.test.ts +79 -0
  228. package/src/rules/oas3/__tests__/no-unused-components.test.ts +131 -0
  229. package/src/rules/oas3/__tests__/operation-4xx-problem-details-rfc7807.test.ts +145 -0
  230. package/src/rules/oas3/__tests__/response-contains-header.test.ts +389 -0
  231. package/src/rules/oas3/__tests__/response-contains-property.test.ts +403 -0
  232. package/src/rules/oas3/__tests__/spec/callbacks.test.ts +41 -0
  233. package/src/rules/oas3/__tests__/spec/fixtures/description.md +1 -0
  234. package/src/rules/oas3/__tests__/spec/info.test.ts +391 -0
  235. package/src/rules/oas3/__tests__/spec/operation.test.ts +253 -0
  236. package/src/rules/oas3/__tests__/spec/paths.test.ts +284 -0
  237. package/src/rules/oas3/__tests__/spec/referenceableScalars.test.ts +77 -0
  238. package/src/rules/oas3/__tests__/spec/servers.test.ts +505 -0
  239. package/src/rules/oas3/__tests__/spec/spec.test.ts +298 -0
  240. package/src/rules/oas3/__tests__/spec/utils.ts +32 -0
  241. package/src/rules/oas3/__tests__/spec-components-invalid-map-name.test.ts +276 -0
  242. package/src/rules/oas3/__tests__/utils/lint-document-for-test.ts +23 -0
  243. package/src/rules/oas3/boolean-parameter-prefixes.ts +28 -0
  244. package/src/rules/oas3/component-name-unique.ts +158 -0
  245. package/src/rules/oas3/index.ts +113 -0
  246. package/src/rules/oas3/no-empty-servers.ts +22 -0
  247. package/src/rules/oas3/no-example-value-and-externalValue.ts +14 -0
  248. package/src/rules/oas3/no-invalid-media-type-examples.ts +49 -0
  249. package/src/rules/oas3/no-server-example.com.ts +14 -0
  250. package/src/rules/oas3/no-server-trailing-slash.ts +15 -0
  251. package/src/rules/oas3/no-server-variables-empty-enum.ts +66 -0
  252. package/src/rules/oas3/no-undefined-server-variable.ts +30 -0
  253. package/src/rules/oas3/no-unused-components.ts +75 -0
  254. package/src/rules/oas3/operation-4xx-problem-details-rfc7807.ts +35 -0
  255. package/src/rules/oas3/remove-unused-components.ts +95 -0
  256. package/src/rules/oas3/request-mime-type.ts +30 -0
  257. package/src/rules/oas3/response-contains-property.ts +38 -0
  258. package/src/rules/oas3/response-mime-type.ts +30 -0
  259. package/src/rules/oas3/spec-components-invalid-map-name.ts +69 -0
  260. package/src/rules/other/stats.ts +73 -0
  261. package/src/rules/utils.ts +193 -0
  262. package/src/types/config-external-schemas.ts +917 -0
  263. package/src/types/index.ts +149 -0
  264. package/src/types/oas2.ts +478 -0
  265. package/src/types/oas3.ts +597 -0
  266. package/src/types/oas3_1.ts +258 -0
  267. package/src/types/redocly-yaml.ts +1040 -0
  268. package/src/typings/common.ts +17 -0
  269. package/src/typings/openapi.ts +298 -0
  270. package/src/typings/swagger.ts +236 -0
  271. package/src/utils.ts +276 -0
  272. package/src/visitors.ts +491 -0
  273. package/src/walk.ts +439 -0
  274. package/tsconfig.json +8 -0
  275. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,100 @@
1
+ import { Assertions } from '../.';
2
+
3
+ const opts = {
4
+ '0': {
5
+ subject: {
6
+ type: 'Operation',
7
+ property: 'summary',
8
+ },
9
+ description: 'example warn text',
10
+ severity: 'warn',
11
+ assertions: { pattern: '/example/' },
12
+ },
13
+ '1': {
14
+ subject: {
15
+ type: 'PathItem',
16
+ },
17
+ where: [
18
+ {
19
+ subject: { type: 'Operation', filterInParentKeys: ['post'], property: 'responses' },
20
+ assertions: { defined: true },
21
+ },
22
+ ],
23
+ description: 'example warn text',
24
+ severity: 'warn',
25
+ assertions: { mutuallyExclusive: ['summary', 'security'] },
26
+ },
27
+ '2': {
28
+ subject: { type: 'PathItem', property: 'tags' },
29
+ where: [
30
+ { subject: { type: 'Operation', property: 'responses' }, assertions: { defined: true } },
31
+ ],
32
+ description: 'example warn text',
33
+ severity: 'warn',
34
+ assertions: { sortOrder: 'desc' },
35
+ },
36
+ '3': {
37
+ subject: { type: 'Foo', property: 'test' },
38
+ where: [
39
+ { subject: { type: 'Bar' }, assertions: {} },
40
+ { subject: { type: 'Baz' }, assertions: {} },
41
+ ],
42
+ description: 'example warn text',
43
+ severity: 'warn',
44
+ assertions: { sortOrder: 'desc' },
45
+ },
46
+ '4': {
47
+ subject: {
48
+ type: 'any',
49
+ property: 'description',
50
+ },
51
+ description: 'example warn text',
52
+ severity: 'warn',
53
+ assertions: { notPattern: '/example/' },
54
+ },
55
+ };
56
+
57
+ describe('Oas3 assertions', () => {
58
+ it('should return the right visitor structure', () => {
59
+ const visitors = Assertions(opts as any);
60
+ expect(visitors).toMatchInlineSnapshot(`
61
+ Array [
62
+ Object {
63
+ "Operation": Object {
64
+ "enter": [Function],
65
+ },
66
+ },
67
+ Object {
68
+ "Operation": Object {
69
+ "PathItem": Object {
70
+ "enter": [Function],
71
+ },
72
+ "skip": [Function],
73
+ },
74
+ },
75
+ Object {
76
+ "Operation": Object {
77
+ "PathItem": Object {
78
+ "enter": [Function],
79
+ },
80
+ "skip": [Function],
81
+ },
82
+ },
83
+ Object {
84
+ "Bar": Object {
85
+ "Baz": Object {
86
+ "Foo": Object {
87
+ "enter": [Function],
88
+ },
89
+ },
90
+ },
91
+ },
92
+ Object {
93
+ "any": Object {
94
+ "enter": [Function],
95
+ },
96
+ },
97
+ ]
98
+ `);
99
+ });
100
+ });
@@ -0,0 +1,236 @@
1
+ import { Assertion, AssertionDefinition } from '..';
2
+ import { AssertionContext } from '../../../../config';
3
+ import { Location } from '../../../../ref-utils';
4
+ import { Source } from '../../../../resolve';
5
+ import { isOrdered, buildVisitorObject, getIntersectionLength, runAssertion } from '../utils';
6
+
7
+ describe('Oas3 assertions', () => {
8
+ describe('Utils', () => {
9
+ describe('getCounts', () => {
10
+ it('should return the right counts', () => {
11
+ const arr = ['foo', 'bar', 'baz'];
12
+ expect(getIntersectionLength(arr, ['foo'])).toBe(1);
13
+ expect(getIntersectionLength(arr, ['foo', 'bar', 'baz'])).toBe(3);
14
+ expect(getIntersectionLength(arr, ['foo', 'test', 'baz'])).toBe(2);
15
+ expect(getIntersectionLength(arr, ['example', 'test'])).toBe(0);
16
+ });
17
+ });
18
+
19
+ describe('isOrdered', () => {
20
+ it('should say if array is ordered or not in specific direction', () => {
21
+ expect(isOrdered(['example', 'foo', 'test'], 'asc')).toBeTruthy();
22
+ expect(isOrdered(['example'], 'asc')).toBeTruthy();
23
+ expect(isOrdered(['test', 'foo', 'example'], 'desc')).toBeTruthy();
24
+ expect(isOrdered(['example'], 'desc')).toBeTruthy();
25
+ expect(isOrdered(['example', 'test', 'foo'], 'asc')).toBeFalsy();
26
+ expect(isOrdered(['example', 'foo', 'test'], 'desc')).toBeFalsy();
27
+ });
28
+ });
29
+
30
+ describe('buildVisitorObject', () => {
31
+ it('should return a consistent visitor structure', () => {
32
+ const where: AssertionDefinition[] = [
33
+ {
34
+ subject: {
35
+ type: 'Foo',
36
+ filterInParentKeys: ['test'],
37
+ },
38
+ assertions: {},
39
+ },
40
+ {
41
+ subject: {
42
+ type: 'Bar',
43
+ filterInParentKeys: ['test'],
44
+ },
45
+ assertions: {},
46
+ },
47
+ {
48
+ subject: {
49
+ type: 'Roof',
50
+ filterInParentKeys: ['test'],
51
+ },
52
+ assertions: {},
53
+ },
54
+ ] as AssertionDefinition[];
55
+
56
+ const visitors = buildVisitorObject(
57
+ { subject: { type: 'Bar' }, where, assertions: {} } as Assertion,
58
+ () => {}
59
+ );
60
+
61
+ expect(visitors).toMatchInlineSnapshot(`
62
+ Object {
63
+ "Foo": Object {
64
+ "Bar": Object {
65
+ "Roof": Object {
66
+ "Bar": Object {
67
+ "enter": [Function],
68
+ },
69
+ "skip": [Function],
70
+ },
71
+ "skip": [Function],
72
+ },
73
+ "skip": [Function],
74
+ },
75
+ }
76
+ `);
77
+ });
78
+
79
+ it('should return the right visitor structure', () => {
80
+ const where = [
81
+ {
82
+ subject: {
83
+ type: 'Operation',
84
+ filterInParentKeys: ['put'],
85
+ },
86
+ assertions: {},
87
+ },
88
+ {
89
+ subject: {
90
+ type: 'Responses',
91
+ filterInParentKeys: [201, 200],
92
+ },
93
+ assertions: {},
94
+ },
95
+ ];
96
+
97
+ const visitors = buildVisitorObject(
98
+ { subject: { type: 'MediaTypesMap' }, where, assertions: {} } as Assertion,
99
+ () => {}
100
+ );
101
+
102
+ expect(visitors).toMatchInlineSnapshot(`
103
+ Object {
104
+ "Operation": Object {
105
+ "Responses": Object {
106
+ "MediaTypesMap": Object {
107
+ "enter": [Function],
108
+ },
109
+ "skip": [Function],
110
+ },
111
+ "skip": [Function],
112
+ },
113
+ }
114
+ `);
115
+ });
116
+ });
117
+
118
+ describe('runAssertion', () => {
119
+ const baseLocation = new Location(jest.fn() as any as Source, 'pointer');
120
+ const rawLocation = new Location(jest.fn() as any as Source, 'raw-pointer');
121
+ // { $ref: 'text' }, true, {...assertionProperties, rawValue: { $ref: 'text' }}
122
+
123
+ const ctxStub = {
124
+ location: baseLocation,
125
+ node: {
126
+ property: 'test',
127
+ },
128
+ rawNode: {
129
+ property: 'test',
130
+ },
131
+ rawLocation: rawLocation,
132
+ } as AssertionContext;
133
+
134
+ it('should catch error cause property should be not defined with assertionProperty', () => {
135
+ const result = runAssertion({
136
+ assert: {
137
+ name: 'defined',
138
+ conditions: false,
139
+ runsOnKeys: true,
140
+ runsOnValues: false,
141
+ },
142
+ ctx: ctxStub,
143
+ assertionProperty: 'property',
144
+ });
145
+
146
+ const expectedLocation = new Location(jest.fn() as any as Source, 'pointer/property');
147
+
148
+ expect(JSON.stringify(result)).toEqual(
149
+ JSON.stringify([
150
+ {
151
+ message: 'Should be not defined',
152
+ location: expectedLocation,
153
+ },
154
+ ])
155
+ );
156
+ });
157
+
158
+ it('should pass cause property defined', () => {
159
+ const result = runAssertion({
160
+ assert: {
161
+ name: 'defined',
162
+ conditions: true,
163
+ runsOnKeys: true,
164
+ runsOnValues: false,
165
+ },
166
+ ctx: ctxStub,
167
+ assertionProperty: 'property',
168
+ });
169
+
170
+ expect(result).toEqual([]);
171
+ });
172
+
173
+ it('should failure cause property does not passed', () => {
174
+ const result = runAssertion({
175
+ assert: {
176
+ name: 'defined',
177
+ conditions: false,
178
+ runsOnKeys: true,
179
+ runsOnValues: false,
180
+ },
181
+ ctx: ctxStub,
182
+ });
183
+
184
+ expect(result).toEqual([
185
+ {
186
+ message: 'Should be not defined',
187
+ location: baseLocation,
188
+ },
189
+ ]);
190
+ });
191
+
192
+ it('should pass with ref assertion cause it is defined', () => {
193
+ const result = runAssertion({
194
+ assert: {
195
+ name: 'ref',
196
+ conditions: true,
197
+ runsOnKeys: true,
198
+ runsOnValues: false,
199
+ },
200
+ ctx: {
201
+ ...ctxStub,
202
+ rawNode: {
203
+ $ref: 'test',
204
+ },
205
+ },
206
+ });
207
+
208
+ expect(result).toEqual([]);
209
+ });
210
+
211
+ it('should failure with ref assertion cause it is defined', () => {
212
+ const result = runAssertion({
213
+ assert: {
214
+ name: 'ref',
215
+ conditions: false,
216
+ runsOnKeys: true,
217
+ runsOnValues: false,
218
+ },
219
+ ctx: {
220
+ ...ctxStub,
221
+ rawNode: {
222
+ $ref: 'test',
223
+ },
224
+ },
225
+ });
226
+
227
+ expect(result).toEqual([
228
+ {
229
+ message: 'should not use $ref',
230
+ location: rawLocation,
231
+ },
232
+ ]);
233
+ });
234
+ });
235
+ });
236
+ });
@@ -0,0 +1,357 @@
1
+ import { AssertionContext, AssertResult, CustomFunction } from 'core/src/config/types';
2
+ import { Location } from '../../../ref-utils';
3
+ import { isPlainObject, isString as runOnValue, isTruthy } from '../../../utils';
4
+ import {
5
+ OrderOptions,
6
+ OrderDirection,
7
+ isOrdered,
8
+ getIntersectionLength,
9
+ regexFromString,
10
+ } from './utils';
11
+
12
+ export type AssertionFnContext = AssertionContext & { baseLocation: Location; rawValue?: any };
13
+
14
+ export type AssertionFn = (value: any, condition: any, ctx: AssertionFnContext) => AssertResult[];
15
+
16
+ export type Asserts = {
17
+ pattern: AssertionFn;
18
+ notPattern: AssertionFn;
19
+ enum: AssertionFn;
20
+ defined: AssertionFn;
21
+ required: AssertionFn;
22
+ disallowed: AssertionFn;
23
+ undefined: AssertionFn;
24
+ nonEmpty: AssertionFn;
25
+ minLength: AssertionFn;
26
+ maxLength: AssertionFn;
27
+ casing: AssertionFn;
28
+ sortOrder: AssertionFn;
29
+ mutuallyExclusive: AssertionFn;
30
+ mutuallyRequired: AssertionFn;
31
+ requireAny: AssertionFn;
32
+ ref: AssertionFn;
33
+ const: AssertionFn;
34
+ };
35
+
36
+ export const runOnKeysSet = new Set<keyof Asserts>([
37
+ 'mutuallyExclusive',
38
+ 'mutuallyRequired',
39
+ 'enum',
40
+ 'pattern',
41
+ 'notPattern',
42
+ 'minLength',
43
+ 'maxLength',
44
+ 'casing',
45
+ 'sortOrder',
46
+ 'disallowed',
47
+ 'required',
48
+ 'requireAny',
49
+ 'ref',
50
+ 'const',
51
+ 'defined', // In case if `property` for assertions is not added
52
+ ]);
53
+ export const runOnValuesSet = new Set<keyof Asserts>([
54
+ 'pattern',
55
+ 'notPattern',
56
+ 'enum',
57
+ 'defined',
58
+ 'undefined',
59
+ 'nonEmpty',
60
+ 'minLength',
61
+ 'maxLength',
62
+ 'casing',
63
+ 'sortOrder',
64
+ 'ref',
65
+ 'const',
66
+ ]);
67
+
68
+ export const asserts: Asserts = {
69
+ pattern: (value: string | string[], condition: string, { baseLocation }: AssertionFnContext) => {
70
+ if (typeof value === 'undefined' || isPlainObject(value)) return []; // property doesn't exist or is an object, no need to lint it with this assert
71
+ const values = Array.isArray(value) ? value : [value];
72
+ const regex = regexFromString(condition);
73
+
74
+ return values
75
+ .map(
76
+ (_val) =>
77
+ !regex?.test(_val) && {
78
+ message: `"${_val}" should match a regex ${condition}`,
79
+ location: runOnValue(value) ? baseLocation : baseLocation.key(),
80
+ }
81
+ )
82
+ .filter(isTruthy);
83
+ },
84
+ notPattern: (
85
+ value: string | string[],
86
+ condition: string,
87
+ { baseLocation }: AssertionFnContext
88
+ ) => {
89
+ if (typeof value === 'undefined' || isPlainObject(value)) return []; // property doesn't exist or is an object, no need to lint it with this assert
90
+ const values = Array.isArray(value) ? value : [value];
91
+ const regex = regexFromString(condition);
92
+
93
+ return values
94
+ .map(
95
+ (_val) =>
96
+ regex?.test(_val) && {
97
+ message: `"${_val}" should not match a regex ${condition}`,
98
+ location: runOnValue(value) ? baseLocation : baseLocation.key(),
99
+ }
100
+ )
101
+ .filter(isTruthy);
102
+ },
103
+ enum: (value: string | string[], condition: string[], { baseLocation }: AssertionFnContext) => {
104
+ if (typeof value === 'undefined' || isPlainObject(value)) return []; // property doesn't exist or is an object, no need to lint it with this assert
105
+ const values = Array.isArray(value) ? value : [value];
106
+ return values
107
+ .map(
108
+ (_val) =>
109
+ !condition.includes(_val) && {
110
+ message: `"${_val}" should be one of the predefined values`,
111
+ location: runOnValue(value) ? baseLocation : baseLocation.child(_val).key(),
112
+ }
113
+ )
114
+ .filter(isTruthy);
115
+ },
116
+ defined: (
117
+ value: string | undefined,
118
+ condition: boolean = true,
119
+ { baseLocation }: AssertionFnContext
120
+ ) => {
121
+ const isDefined = typeof value !== 'undefined';
122
+ const isValid = condition ? isDefined : !isDefined;
123
+ return isValid
124
+ ? []
125
+ : [
126
+ {
127
+ message: condition ? `Should be defined` : 'Should be not defined',
128
+ location: baseLocation,
129
+ },
130
+ ];
131
+ },
132
+ required: (value: string[], keys: string[], { baseLocation }: AssertionFnContext) => {
133
+ return keys
134
+ .map(
135
+ (requiredKey) =>
136
+ !value.includes(requiredKey) && {
137
+ message: `${requiredKey} is required`,
138
+ location: baseLocation.key(),
139
+ }
140
+ )
141
+ .filter(isTruthy);
142
+ },
143
+ disallowed: (
144
+ value: string | string[],
145
+ condition: string[],
146
+ { baseLocation }: AssertionFnContext
147
+ ) => {
148
+ if (typeof value === 'undefined' || isPlainObject(value)) return []; // property doesn't exist or is an object, no need to lint it with this assert
149
+ const values = Array.isArray(value) ? value : [value];
150
+ return values
151
+ .map(
152
+ (_val) =>
153
+ condition.includes(_val) && {
154
+ message: `"${_val}" is disallowed`,
155
+ location: runOnValue(value) ? baseLocation : baseLocation.child(_val).key(),
156
+ }
157
+ )
158
+ .filter(isTruthy);
159
+ },
160
+ const: (
161
+ value: string | number | boolean | string[] | number[],
162
+ condition: string | number | boolean,
163
+ { baseLocation }: AssertionFnContext
164
+ ) => {
165
+ if (typeof value === 'undefined') return [];
166
+
167
+ if (Array.isArray(value)) {
168
+ return value
169
+ .map(
170
+ (_val) =>
171
+ condition !== _val && {
172
+ message: `"${_val}" should be equal ${condition} `,
173
+ location: runOnValue(value) ? baseLocation : baseLocation.child(_val).key(),
174
+ }
175
+ )
176
+ .filter(isTruthy);
177
+ } else {
178
+ return value !== condition
179
+ ? [
180
+ {
181
+ message: `${value} should be equal ${condition}`,
182
+ location: baseLocation,
183
+ },
184
+ ]
185
+ : [];
186
+ }
187
+ },
188
+ undefined: (value: unknown, condition: boolean = true, { baseLocation }: AssertionFnContext) => {
189
+ const isUndefined = typeof value === 'undefined';
190
+ const isValid = condition ? isUndefined : !isUndefined;
191
+ return isValid
192
+ ? []
193
+ : [
194
+ {
195
+ message: condition ? `Should not be defined` : 'Should be defined',
196
+ location: baseLocation,
197
+ },
198
+ ];
199
+ },
200
+ nonEmpty: (
201
+ value: string | undefined | null,
202
+ condition: boolean = true,
203
+ { baseLocation }: AssertionFnContext
204
+ ) => {
205
+ const isEmpty = typeof value === 'undefined' || value === null || value === '';
206
+ const isValid = condition ? !isEmpty : isEmpty;
207
+ return isValid
208
+ ? []
209
+ : [
210
+ {
211
+ message: condition ? `Should not be empty` : 'Should be empty',
212
+ location: baseLocation,
213
+ },
214
+ ];
215
+ },
216
+ minLength: (value: string | any[], condition: number, { baseLocation }: AssertionFnContext) => {
217
+ if (typeof value === 'undefined' || value.length >= condition) return []; // property doesn't exist, no need to lint it with this assert
218
+ return [
219
+ {
220
+ message: `Should have at least ${condition} characters`,
221
+ location: baseLocation,
222
+ },
223
+ ];
224
+ },
225
+ maxLength: (value: string | any[], condition: number, { baseLocation }: AssertionFnContext) => {
226
+ if (typeof value === 'undefined' || value.length <= condition) return []; // property doesn't exist, no need to lint it with this assert
227
+ return [
228
+ {
229
+ message: `Should have at most ${condition} characters`,
230
+ location: baseLocation,
231
+ },
232
+ ];
233
+ },
234
+ casing: (value: string | string[], condition: string, { baseLocation }: AssertionFnContext) => {
235
+ if (typeof value === 'undefined' || isPlainObject(value)) return []; // property doesn't exist or is an object, no need to lint it with this assert
236
+ const values = Array.isArray(value) ? value : [value];
237
+ const casingRegexes: Record<string, RegExp> = {
238
+ camelCase: /^[a-z][a-zA-Z0-9]+$/g,
239
+ 'kebab-case': /^([a-z][a-z0-9]*)(-[a-z0-9]+)*$/g,
240
+ snake_case: /^([a-z][a-z0-9]*)(_[a-z0-9]+)*$/g,
241
+ PascalCase: /^[A-Z][a-zA-Z0-9]+$/g,
242
+ MACRO_CASE: /^([A-Z][A-Z0-9]*)(_[A-Z0-9]+)*$/g,
243
+ 'COBOL-CASE': /^([A-Z][A-Z0-9]*)(-[A-Z0-9]+)*$/g,
244
+ flatcase: /^[a-z][a-z0-9]+$/g,
245
+ };
246
+ return values
247
+ .map(
248
+ (_val) =>
249
+ !_val.match(casingRegexes[condition]) && {
250
+ message: `"${_val}" should use ${condition}`,
251
+ location: runOnValue(value) ? baseLocation : baseLocation.child(_val).key(),
252
+ }
253
+ )
254
+ .filter(isTruthy);
255
+ },
256
+ sortOrder: (
257
+ value: unknown[],
258
+ condition: OrderOptions | OrderDirection,
259
+ { baseLocation }: AssertionFnContext
260
+ ) => {
261
+ const direction = (condition as OrderOptions).direction || (condition as OrderDirection);
262
+ const property = (condition as OrderOptions).property;
263
+ if (Array.isArray(value) && value.length > 0 && typeof value[0] === 'object' && !property) {
264
+ return [
265
+ {
266
+ message: `Please define a property to sort objects by`,
267
+ location: baseLocation,
268
+ },
269
+ ];
270
+ }
271
+ if (typeof value === 'undefined' || isOrdered(value, condition)) return [];
272
+
273
+ return [
274
+ {
275
+ message: `Should be sorted in ${
276
+ direction === 'asc' ? 'an ascending' : 'a descending'
277
+ } order${property ? ` by property ${property}` : ''}`,
278
+ location: baseLocation,
279
+ },
280
+ ];
281
+ },
282
+ mutuallyExclusive: (
283
+ value: string[],
284
+ condition: string[],
285
+ { baseLocation }: AssertionFnContext
286
+ ) => {
287
+ if (getIntersectionLength(value, condition) < 2) return [];
288
+ return [
289
+ {
290
+ message: `${condition.join(', ')} keys should be mutually exclusive`,
291
+ location: baseLocation.key(),
292
+ },
293
+ ];
294
+ },
295
+ mutuallyRequired: (
296
+ value: string[],
297
+ condition: string[],
298
+ { baseLocation }: AssertionFnContext
299
+ ) => {
300
+ const isValid =
301
+ getIntersectionLength(value, condition) > 0
302
+ ? getIntersectionLength(value, condition) === condition.length
303
+ : true;
304
+ return isValid
305
+ ? []
306
+ : [
307
+ {
308
+ message: `Properties ${condition.join(', ')} are mutually required`,
309
+ location: baseLocation.key(),
310
+ },
311
+ ];
312
+ },
313
+ requireAny: (value: string[], condition: string[], { baseLocation }: AssertionFnContext) => {
314
+ return getIntersectionLength(value, condition) >= 1
315
+ ? []
316
+ : [
317
+ {
318
+ message: `Should have any of ${condition.join(', ')}`,
319
+ location: baseLocation.key(),
320
+ },
321
+ ];
322
+ },
323
+ ref: (
324
+ _value: unknown,
325
+ condition: string | boolean,
326
+ { baseLocation, rawValue }: AssertionFnContext
327
+ ) => {
328
+ if (typeof rawValue === 'undefined') return []; // property doesn't exist, no need to lint it with this assert
329
+ const hasRef = rawValue.hasOwnProperty('$ref');
330
+ if (typeof condition === 'boolean') {
331
+ const isValid = condition ? hasRef : !hasRef;
332
+ return isValid
333
+ ? []
334
+ : [
335
+ {
336
+ message: condition ? `should use $ref` : 'should not use $ref',
337
+ location: hasRef ? baseLocation : baseLocation.key(),
338
+ },
339
+ ];
340
+ }
341
+ const regex = regexFromString(condition);
342
+ const isValid = hasRef && regex?.test(rawValue['$ref']);
343
+ return isValid
344
+ ? []
345
+ : [
346
+ {
347
+ message: `$ref value should match ${condition}`,
348
+ location: hasRef ? baseLocation : baseLocation.key(),
349
+ },
350
+ ];
351
+ },
352
+ };
353
+
354
+ export function buildAssertCustomFunction(fn: CustomFunction): AssertionFn {
355
+ return (value: string[], options: any, ctx: AssertionFnContext) =>
356
+ fn.call(null, value, options, ctx);
357
+ }