moonflower 0.9.0

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 (268) hide show
  1. package/.eslintrc.js +26 -0
  2. package/.prettierrc.js +7 -0
  3. package/README.md +383 -0
  4. package/cli/cli.ts +59 -0
  5. package/cli/entry.cjs +3 -0
  6. package/cli/prettyprint.ts +16 -0
  7. package/dist/cli/cli.d.ts +2 -0
  8. package/dist/cli/cli.d.ts.map +1 -0
  9. package/dist/cli/cli.js +79 -0
  10. package/dist/cli/prettyprint.d.ts +4 -0
  11. package/dist/cli/prettyprint.d.ts.map +1 -0
  12. package/dist/cli/prettyprint.js +18 -0
  13. package/dist/src/errors/BaseHttpError.d.ts +13 -0
  14. package/dist/src/errors/BaseHttpError.d.ts.map +1 -0
  15. package/dist/src/errors/BaseHttpError.js +13 -0
  16. package/dist/src/errors/HttpErrorHandler.d.ts +3 -0
  17. package/dist/src/errors/HttpErrorHandler.d.ts.map +1 -0
  18. package/dist/src/errors/HttpErrorHandler.js +23 -0
  19. package/dist/src/errors/UserFacingErrors.d.ts +11 -0
  20. package/dist/src/errors/UserFacingErrors.d.ts.map +1 -0
  21. package/dist/src/errors/UserFacingErrors.js +23 -0
  22. package/dist/src/hooks/authentication/useAuth.d.ts +3 -0
  23. package/dist/src/hooks/authentication/useAuth.d.ts.map +1 -0
  24. package/dist/src/hooks/authentication/useAuth.js +7 -0
  25. package/dist/src/hooks/authentication/useOptionalAuth.d.ts +3 -0
  26. package/dist/src/hooks/authentication/useOptionalAuth.d.ts.map +1 -0
  27. package/dist/src/hooks/authentication/useOptionalAuth.js +16 -0
  28. package/dist/src/hooks/useApiEndpoint.d.ts +8 -0
  29. package/dist/src/hooks/useApiEndpoint.d.ts.map +1 -0
  30. package/dist/src/hooks/useApiEndpoint.js +7 -0
  31. package/dist/src/hooks/useApiHeader/index.d.ts +2 -0
  32. package/dist/src/hooks/useApiHeader/index.d.ts.map +1 -0
  33. package/dist/src/hooks/useApiHeader/index.js +17 -0
  34. package/dist/src/hooks/useApiHeader/useApiHeader.d.ts +3 -0
  35. package/dist/src/hooks/useApiHeader/useApiHeader.d.ts.map +1 -0
  36. package/dist/src/hooks/useApiHeader/useApiHeader.js +6 -0
  37. package/dist/src/hooks/useApiHeader/useApiHeader.spec.data.d.ts +2 -0
  38. package/dist/src/hooks/useApiHeader/useApiHeader.spec.data.d.ts.map +1 -0
  39. package/dist/src/hooks/useApiHeader/useApiHeader.spec.data.js +22 -0
  40. package/dist/src/hooks/useCookieParams.d.ts +9 -0
  41. package/dist/src/hooks/useCookieParams.d.ts.map +1 -0
  42. package/dist/src/hooks/useCookieParams.js +50 -0
  43. package/dist/src/hooks/useExposeApiModel/index.d.ts +2 -0
  44. package/dist/src/hooks/useExposeApiModel/index.d.ts.map +1 -0
  45. package/dist/src/hooks/useExposeApiModel/index.js +17 -0
  46. package/dist/src/hooks/useExposeApiModel/useExposeApiModel.d.ts +3 -0
  47. package/dist/src/hooks/useExposeApiModel/useExposeApiModel.d.ts.map +1 -0
  48. package/dist/src/hooks/useExposeApiModel/useExposeApiModel.js +9 -0
  49. package/dist/src/hooks/useExposeApiModel/useExposeApiModel.spec.data.d.ts +2 -0
  50. package/dist/src/hooks/useExposeApiModel/useExposeApiModel.spec.data.d.ts.map +1 -0
  51. package/dist/src/hooks/useExposeApiModel/useExposeApiModel.spec.data.js +16 -0
  52. package/dist/src/hooks/useHeaderParams.d.ts +12 -0
  53. package/dist/src/hooks/useHeaderParams.d.ts.map +1 -0
  54. package/dist/src/hooks/useHeaderParams.js +52 -0
  55. package/dist/src/hooks/usePathParams.d.ts +22 -0
  56. package/dist/src/hooks/usePathParams.d.ts.map +1 -0
  57. package/dist/src/hooks/usePathParams.js +46 -0
  58. package/dist/src/hooks/useQueryParams.d.ts +9 -0
  59. package/dist/src/hooks/useQueryParams.d.ts.map +1 -0
  60. package/dist/src/hooks/useQueryParams.js +50 -0
  61. package/dist/src/hooks/useRequestBody.d.ts +9 -0
  62. package/dist/src/hooks/useRequestBody.d.ts.map +1 -0
  63. package/dist/src/hooks/useRequestBody.js +59 -0
  64. package/dist/src/hooks/useRequestRawBody.d.ts +7 -0
  65. package/dist/src/hooks/useRequestRawBody.d.ts.map +1 -0
  66. package/dist/src/hooks/useRequestRawBody.js +34 -0
  67. package/dist/src/index.d.ts +18 -0
  68. package/dist/src/index.d.ts.map +1 -0
  69. package/dist/src/index.js +33 -0
  70. package/dist/src/openapi/analyzerModule/analyzerModule.d.ts +18 -0
  71. package/dist/src/openapi/analyzerModule/analyzerModule.d.ts.map +1 -0
  72. package/dist/src/openapi/analyzerModule/analyzerModule.js +192 -0
  73. package/dist/src/openapi/analyzerModule/nodeParsers.d.ts +19 -0
  74. package/dist/src/openapi/analyzerModule/nodeParsers.d.ts.map +1 -0
  75. package/dist/src/openapi/analyzerModule/nodeParsers.js +521 -0
  76. package/dist/src/openapi/analyzerModule/parseEndpoint.d.ts +4 -0
  77. package/dist/src/openapi/analyzerModule/parseEndpoint.d.ts.map +1 -0
  78. package/dist/src/openapi/analyzerModule/parseEndpoint.js +246 -0
  79. package/dist/src/openapi/analyzerModule/parseExposedModels.d.ts +5 -0
  80. package/dist/src/openapi/analyzerModule/parseExposedModels.d.ts.map +1 -0
  81. package/dist/src/openapi/analyzerModule/parseExposedModels.js +32 -0
  82. package/dist/src/openapi/analyzerModule/test/openApiAnalyzer.spec.data.d.ts +2 -0
  83. package/dist/src/openapi/analyzerModule/test/openApiAnalyzer.spec.data.d.ts.map +1 -0
  84. package/dist/src/openapi/analyzerModule/test/openApiAnalyzer.spec.data.js +400 -0
  85. package/dist/src/openapi/analyzerModule/types.d.ts +53 -0
  86. package/dist/src/openapi/analyzerModule/types.d.ts.map +1 -0
  87. package/dist/src/openapi/analyzerModule/types.js +2 -0
  88. package/dist/src/openapi/discoveryModule/discoverImports/discoverImports.d.ts +8 -0
  89. package/dist/src/openapi/discoveryModule/discoverImports/discoverImports.d.ts.map +1 -0
  90. package/dist/src/openapi/discoveryModule/discoverImports/discoverImports.js +33 -0
  91. package/dist/src/openapi/discoveryModule/discoverRouterFiles/data/testRouterA.spec.data.d.ts +4 -0
  92. package/dist/src/openapi/discoveryModule/discoverRouterFiles/data/testRouterA.spec.data.d.ts.map +1 -0
  93. package/dist/src/openapi/discoveryModule/discoverRouterFiles/data/testRouterA.spec.data.js +8 -0
  94. package/dist/src/openapi/discoveryModule/discoverRouterFiles/data/testRouterB.spec.data.d.ts +4 -0
  95. package/dist/src/openapi/discoveryModule/discoverRouterFiles/data/testRouterB.spec.data.d.ts.map +1 -0
  96. package/dist/src/openapi/discoveryModule/discoverRouterFiles/data/testRouterB.spec.data.js +8 -0
  97. package/dist/src/openapi/discoveryModule/discoverRouterFiles/discoverRouterFiles.d.ts +17 -0
  98. package/dist/src/openapi/discoveryModule/discoverRouterFiles/discoverRouterFiles.d.ts.map +1 -0
  99. package/dist/src/openapi/discoveryModule/discoverRouterFiles/discoverRouterFiles.js +80 -0
  100. package/dist/src/openapi/discoveryModule/discoverRouters/discoverRouters.d.ts +6 -0
  101. package/dist/src/openapi/discoveryModule/discoverRouters/discoverRouters.d.ts.map +1 -0
  102. package/dist/src/openapi/discoveryModule/discoverRouters/discoverRouters.js +31 -0
  103. package/dist/src/openapi/discoveryModule/discoverRouters/discoverRouters.spec.data.d.ts +5 -0
  104. package/dist/src/openapi/discoveryModule/discoverRouters/discoverRouters.spec.data.d.ts.map +1 -0
  105. package/dist/src/openapi/discoveryModule/discoverRouters/discoverRouters.spec.data.js +38 -0
  106. package/dist/src/openapi/discoveryModule/index.d.ts +3 -0
  107. package/dist/src/openapi/discoveryModule/index.d.ts.map +1 -0
  108. package/dist/src/openapi/discoveryModule/index.js +18 -0
  109. package/dist/src/openapi/generatorModule/generateComponentSchemas.d.ts +4 -0
  110. package/dist/src/openapi/generatorModule/generateComponentSchemas.d.ts.map +1 -0
  111. package/dist/src/openapi/generatorModule/generateComponentSchemas.js +12 -0
  112. package/dist/src/openapi/generatorModule/generatePaths.d.ts +10 -0
  113. package/dist/src/openapi/generatorModule/generatePaths.d.ts.map +1 -0
  114. package/dist/src/openapi/generatorModule/generatePaths.js +116 -0
  115. package/dist/src/openapi/generatorModule/generatorModule.d.ts +16 -0
  116. package/dist/src/openapi/generatorModule/generatorModule.d.ts.map +1 -0
  117. package/dist/src/openapi/generatorModule/generatorModule.js +18 -0
  118. package/dist/src/openapi/generatorModule/getSchema.d.ts +36 -0
  119. package/dist/src/openapi/generatorModule/getSchema.d.ts.map +1 -0
  120. package/dist/src/openapi/generatorModule/getSchema.js +133 -0
  121. package/dist/src/openapi/generatorModule/index.d.ts +5 -0
  122. package/dist/src/openapi/generatorModule/index.d.ts.map +1 -0
  123. package/dist/src/openapi/generatorModule/index.js +20 -0
  124. package/dist/src/openapi/generatorModule/test/openApiGenerator.spec.data.d.ts +515 -0
  125. package/dist/src/openapi/generatorModule/test/openApiGenerator.spec.data.d.ts.map +1 -0
  126. package/dist/src/openapi/generatorModule/test/openApiGenerator.spec.data.js +1119 -0
  127. package/dist/src/openapi/initOpenApiEngine.d.ts +4 -0
  128. package/dist/src/openapi/initOpenApiEngine.d.ts.map +1 -0
  129. package/dist/src/openapi/initOpenApiEngine.js +14 -0
  130. package/dist/src/openapi/manager/OpenApiManager.d.ts +67 -0
  131. package/dist/src/openapi/manager/OpenApiManager.d.ts.map +1 -0
  132. package/dist/src/openapi/manager/OpenApiManager.js +86 -0
  133. package/dist/src/openapi/router/OpenApiRouter.d.ts +4 -0
  134. package/dist/src/openapi/router/OpenApiRouter.d.ts.map +1 -0
  135. package/dist/src/openapi/router/OpenApiRouter.js +11 -0
  136. package/dist/src/openapi/types.d.ts +81 -0
  137. package/dist/src/openapi/types.d.ts.map +1 -0
  138. package/dist/src/openapi/types.js +2 -0
  139. package/dist/src/router/Router.d.ts +23 -0
  140. package/dist/src/router/Router.d.ts.map +1 -0
  141. package/dist/src/router/Router.js +81 -0
  142. package/dist/src/router/responseValueToJson.d.ts +2 -0
  143. package/dist/src/router/responseValueToJson.d.ts.map +1 -0
  144. package/dist/src/router/responseValueToJson.js +10 -0
  145. package/dist/src/setupTests.d.ts +1 -0
  146. package/dist/src/setupTests.d.ts.map +1 -0
  147. package/dist/src/setupTests.js +3 -0
  148. package/dist/src/test/TestAppRouter.d.ts +8 -0
  149. package/dist/src/test/TestAppRouter.d.ts.map +1 -0
  150. package/dist/src/test/TestAppRouter.js +58 -0
  151. package/dist/src/test/app.d.ts +3 -0
  152. package/dist/src/test/app.d.ts.map +1 -0
  153. package/dist/src/test/app.js +41 -0
  154. package/dist/src/utils/TypeUtils.d.ts +22 -0
  155. package/dist/src/utils/TypeUtils.d.ts.map +1 -0
  156. package/dist/src/utils/TypeUtils.js +2 -0
  157. package/dist/src/utils/fromZodSchema.d.ts +2 -0
  158. package/dist/src/utils/fromZodSchema.d.ts.map +1 -0
  159. package/dist/src/utils/fromZodSchema.js +6 -0
  160. package/dist/src/utils/loadTestData.d.ts +2 -0
  161. package/dist/src/utils/loadTestData.d.ts.map +1 -0
  162. package/dist/src/utils/loadTestData.js +39 -0
  163. package/dist/src/utils/mockContext.d.ts +20 -0
  164. package/dist/src/utils/mockContext.d.ts.map +1 -0
  165. package/dist/src/utils/mockContext.js +85 -0
  166. package/dist/src/utils/nameOf.d.ts +5 -0
  167. package/dist/src/utils/nameOf.d.ts.map +1 -0
  168. package/dist/src/utils/nameOf.js +7 -0
  169. package/dist/src/utils/object.d.ts +7 -0
  170. package/dist/src/utils/object.d.ts.map +1 -0
  171. package/dist/src/utils/object.js +21 -0
  172. package/dist/src/utils/printers.d.ts +6 -0
  173. package/dist/src/utils/printers.d.ts.map +1 -0
  174. package/dist/src/utils/printers.js +76 -0
  175. package/dist/src/utils/validationMessages.d.ts +18 -0
  176. package/dist/src/utils/validationMessages.d.ts.map +1 -0
  177. package/dist/src/utils/validationMessages.js +43 -0
  178. package/dist/src/validators/BuiltInValidators.d.ts +61 -0
  179. package/dist/src/validators/BuiltInValidators.d.ts.map +1 -0
  180. package/dist/src/validators/BuiltInValidators.js +66 -0
  181. package/dist/src/validators/InternalParamWrappers.d.ts +5 -0
  182. package/dist/src/validators/InternalParamWrappers.d.ts.map +1 -0
  183. package/dist/src/validators/InternalParamWrappers.js +5 -0
  184. package/dist/src/validators/ParamWrappers.d.ts +11 -0
  185. package/dist/src/validators/ParamWrappers.d.ts.map +1 -0
  186. package/dist/src/validators/ParamWrappers.js +9 -0
  187. package/dist/src/validators/types.d.ts +18 -0
  188. package/dist/src/validators/types.d.ts.map +1 -0
  189. package/dist/src/validators/types.js +2 -0
  190. package/dist/tsconfig.build.tsbuildinfo +1 -0
  191. package/package.json +59 -0
  192. package/src/errors/BaseHttpError.ts +16 -0
  193. package/src/errors/HttpErrorHandler.ts +20 -0
  194. package/src/errors/UserFacingErrors.ts +39 -0
  195. package/src/hooks/authentication/useAuth.ts +8 -0
  196. package/src/hooks/authentication/useOptionalAuth.ts +17 -0
  197. package/src/hooks/useApiEndpoint.spec.ts +11 -0
  198. package/src/hooks/useApiEndpoint.ts +10 -0
  199. package/src/hooks/useApiHeader/index.ts +1 -0
  200. package/src/hooks/useApiHeader/useApiHeader.spec.data.ts +22 -0
  201. package/src/hooks/useApiHeader/useApiHeader.spec.ts +34 -0
  202. package/src/hooks/useApiHeader/useApiHeader.ts +6 -0
  203. package/src/hooks/useCookieParams.spec.ts +174 -0
  204. package/src/hooks/useCookieParams.ts +73 -0
  205. package/src/hooks/useExposeApiModel/index.ts +1 -0
  206. package/src/hooks/useExposeApiModel/useExposeApiModel.spec.data.ts +48 -0
  207. package/src/hooks/useExposeApiModel/useExposeApiModel.spec.ts +388 -0
  208. package/src/hooks/useExposeApiModel/useExposeApiModel.ts +9 -0
  209. package/src/hooks/useHeaderParams.spec.ts +186 -0
  210. package/src/hooks/useHeaderParams.ts +83 -0
  211. package/src/hooks/usePathParams.spec.ts +161 -0
  212. package/src/hooks/usePathParams.ts +89 -0
  213. package/src/hooks/useQueryParams.spec.ts +224 -0
  214. package/src/hooks/useQueryParams.ts +73 -0
  215. package/src/hooks/useRequestBody.spec.ts +215 -0
  216. package/src/hooks/useRequestBody.ts +94 -0
  217. package/src/hooks/useRequestRawBody.spec.ts +154 -0
  218. package/src/hooks/useRequestRawBody.ts +56 -0
  219. package/src/index.ts +17 -0
  220. package/src/openapi/analyzerModule/analyzerModule.ts +228 -0
  221. package/src/openapi/analyzerModule/nodeParsers.ts +648 -0
  222. package/src/openapi/analyzerModule/parseEndpoint.ts +305 -0
  223. package/src/openapi/analyzerModule/parseExposedModels.ts +34 -0
  224. package/src/openapi/analyzerModule/test/openApiAnalyzer.spec.data.ts +521 -0
  225. package/src/openapi/analyzerModule/test/openApiAnalyzer.spec.ts +1043 -0
  226. package/src/openapi/analyzerModule/types.ts +72 -0
  227. package/src/openapi/discoveryModule/discoverImports/discoverImports.ts +43 -0
  228. package/src/openapi/discoveryModule/discoverRouterFiles/data/testRouterA.spec.data.ts +7 -0
  229. package/src/openapi/discoveryModule/discoverRouterFiles/data/testRouterB.spec.data.ts +7 -0
  230. package/src/openapi/discoveryModule/discoverRouterFiles/discoverRouterFiles.spec.ts +36 -0
  231. package/src/openapi/discoveryModule/discoverRouterFiles/discoverRouterFiles.ts +80 -0
  232. package/src/openapi/discoveryModule/discoverRouters/discoverRouters.spec.data.ts +42 -0
  233. package/src/openapi/discoveryModule/discoverRouters/discoverRouters.spec.ts +18 -0
  234. package/src/openapi/discoveryModule/discoverRouters/discoverRouters.ts +39 -0
  235. package/src/openapi/discoveryModule/index.ts +2 -0
  236. package/src/openapi/generatorModule/generateComponentSchemas.ts +12 -0
  237. package/src/openapi/generatorModule/generatePaths.ts +138 -0
  238. package/src/openapi/generatorModule/generatorModule.ts +17 -0
  239. package/src/openapi/generatorModule/getSchema.ts +169 -0
  240. package/src/openapi/generatorModule/index.ts +4 -0
  241. package/src/openapi/generatorModule/test/openApiGenerator.spec.data.ts +1119 -0
  242. package/src/openapi/generatorModule/test/openApiGenerator.spec.ts +783 -0
  243. package/src/openapi/initOpenApiEngine.ts +20 -0
  244. package/src/openapi/manager/OpenApiManager.ts +153 -0
  245. package/src/openapi/router/OpenApiRouter.ts +11 -0
  246. package/src/openapi/types.ts +86 -0
  247. package/src/router/Router.ts +123 -0
  248. package/src/router/responseValueToJson.ts +6 -0
  249. package/src/setupTests.ts +3 -0
  250. package/src/test/TestAppRouter.ts +76 -0
  251. package/src/test/app.spec.ts +130 -0
  252. package/src/test/app.ts +43 -0
  253. package/src/utils/TypeUtils.ts +51 -0
  254. package/src/utils/loadTestData.ts +15 -0
  255. package/src/utils/mockContext.ts +86 -0
  256. package/src/utils/nameOf.ts +7 -0
  257. package/src/utils/object.spec.ts +27 -0
  258. package/src/utils/object.ts +17 -0
  259. package/src/utils/printers.spec.ts +103 -0
  260. package/src/utils/printers.ts +49 -0
  261. package/src/utils/validationMessages.ts +65 -0
  262. package/src/validators/BuiltInValidators.ts +64 -0
  263. package/src/validators/InternalParamWrappers.ts +14 -0
  264. package/src/validators/ParamWrappers.ts +22 -0
  265. package/src/validators/types.ts +35 -0
  266. package/tsconfig.build.json +15 -0
  267. package/tsconfig.json +29 -0
  268. package/vite.config.ts +16 -0
@@ -0,0 +1,161 @@
1
+ import {
2
+ BooleanValidator,
3
+ NumberValidator,
4
+ PathParam,
5
+ RequiredParam,
6
+ StringValidator,
7
+ ValidationError,
8
+ } from '..'
9
+ import { mockContext, mockContextPath } from '../utils/mockContext'
10
+ import { usePathParams } from './usePathParams'
11
+
12
+ describe('usePathParams', () => {
13
+ it('parses params correctly', () => {
14
+ const ctx = mockContextPath(mockContext(), '/test/:stringParam/:numberParam/:booleanParam/:objectParam', {
15
+ stringParam: 'test_string',
16
+ numberParam: '12',
17
+ booleanParam: 'true',
18
+ objectParam: '{ "foo": "aaa", "bar": "bbb" }',
19
+ })
20
+
21
+ const params = usePathParams(ctx, {
22
+ stringParam: StringValidator,
23
+ numberParam: NumberValidator,
24
+ booleanParam: BooleanValidator,
25
+ objectParam: RequiredParam<{ foo: string; bar: string }>({
26
+ parse: (v) => JSON.parse(String(v)),
27
+ }),
28
+ })
29
+
30
+ expect(params.stringParam).toEqual('test_string')
31
+ expect(params.numberParam).toEqual(12)
32
+ expect(params.booleanParam).toEqual(true)
33
+ expect(params.objectParam).toEqual({ foo: 'aaa', bar: 'bbb' })
34
+ })
35
+
36
+ it('passes validation on valid parameter', () => {
37
+ const ctx = mockContextPath(mockContext(), '/test/:testParam', {
38
+ testParam: '12',
39
+ })
40
+
41
+ const params = usePathParams(ctx, {
42
+ testParam: NumberValidator,
43
+ })
44
+
45
+ expect(params.testParam).toEqual(12)
46
+ })
47
+
48
+ it('fails validation on invalid parameter', () => {
49
+ const test = () => {
50
+ const ctx = mockContextPath(mockContext(), '/test/:testParam', {
51
+ testParam: 'qwerty',
52
+ })
53
+
54
+ usePathParams(ctx, {
55
+ testParam: NumberValidator,
56
+ })
57
+ }
58
+
59
+ expect(test).toThrow(ValidationError)
60
+ expect(test).toThrow("Failed route param validation: 'testParam'")
61
+ })
62
+
63
+ it('passes validation when optional parameter is not provided', () => {
64
+ const ctx = mockContextPath(mockContext(), '/test/:testParam?', {})
65
+
66
+ const params = usePathParams(ctx, {
67
+ testParam: NumberValidator,
68
+ })
69
+
70
+ expect(params.testParam).toEqual(undefined)
71
+ })
72
+
73
+ it('passes prevalidation on valid parameter', () => {
74
+ const ctx = mockContextPath(mockContext(), '/test/:testParam', {
75
+ testParam: 'valid',
76
+ })
77
+
78
+ const params = usePathParams(ctx, {
79
+ testParam: PathParam({
80
+ prevalidate: (v) => v === 'valid',
81
+ parse: (v) => String(v),
82
+ }),
83
+ })
84
+
85
+ expect(params.testParam).toEqual('valid')
86
+ })
87
+
88
+ it('fails prevalidation on invalid parameter', () => {
89
+ const test = () => {
90
+ const ctx = mockContextPath(mockContext(), '/test/:testParam', {
91
+ testParam: 'invalid',
92
+ })
93
+
94
+ usePathParams(ctx, {
95
+ testParam: PathParam({
96
+ prevalidate: (v) => v === 'valid',
97
+ parse: (v) => String(v),
98
+ }),
99
+ })
100
+ }
101
+
102
+ expect(test).toThrow(ValidationError)
103
+ expect(test).toThrow("Failed route param validation: 'testParam'")
104
+ })
105
+
106
+ it('fails prevalidation on parse error', () => {
107
+ const test = () => {
108
+ const ctx = mockContextPath(mockContext(), '/test/:testParam', {
109
+ testParam: 'not a json',
110
+ })
111
+
112
+ usePathParams(ctx, {
113
+ testParam: PathParam<{ foo: 'aaa' }>({
114
+ parse: (v) => JSON.parse(String(v)),
115
+ }),
116
+ })
117
+ }
118
+
119
+ expect(test).toThrow(ValidationError)
120
+ expect(test).toThrow("Failed route param validation: 'testParam'")
121
+ })
122
+
123
+ it('sends an error message when validation fails', () => {
124
+ const test = () => {
125
+ const ctx = mockContextPath(mockContext(), '/test/:testParam', {
126
+ testParam: 'invalid',
127
+ })
128
+
129
+ usePathParams(ctx, {
130
+ testParam: PathParam({
131
+ prevalidate: (v) => v === 'valid',
132
+ parse: (v) => String(v),
133
+ description: 'Description',
134
+ errorMessage: 'Error message',
135
+ }),
136
+ })
137
+ }
138
+
139
+ expect(test).toThrow(ValidationError)
140
+ expect(test).toThrow("Failed route param validation: 'testParam' (Error message)")
141
+ })
142
+
143
+ it('sends the description when validation fails with no error message provided', () => {
144
+ const test = () => {
145
+ const ctx = mockContextPath(mockContext(), '/test/:testParam', {
146
+ testParam: 'invalid',
147
+ })
148
+
149
+ usePathParams(ctx, {
150
+ testParam: PathParam({
151
+ prevalidate: (v) => v === 'valid',
152
+ parse: (v) => String(v),
153
+ description: 'Description',
154
+ }),
155
+ })
156
+ }
157
+
158
+ expect(test).toThrow(ValidationError)
159
+ expect(test).toThrow("Failed route param validation: 'testParam' (Description)")
160
+ })
161
+ })
@@ -0,0 +1,89 @@
1
+ import { ParameterizedContext } from 'koa'
2
+
3
+ import { ValidationError } from '../errors/UserFacingErrors'
4
+ import { keysOf } from '../utils/object'
5
+ import { CleanUpPathParam } from '../utils/TypeUtils'
6
+ import { getValidationResultMessage } from '../utils/validationMessages'
7
+ import { Validator } from '../validators/types'
8
+
9
+ type CheckIfOptional<T, B> = B extends string ? (B extends `${string}?` ? T | undefined : T) : never
10
+
11
+ type ValidatedData<
12
+ ParamsT extends string[],
13
+ TestTemplate extends {
14
+ [K in keyof ParamsT]: {
15
+ original: ParamsT[K]
16
+ cleaned: CleanUpPathParam<ParamsT[K]>
17
+ callback: ValidatorsT[CleanUpPathParam<ParamsT[K]>]
18
+ }
19
+ },
20
+ ValidatorsT extends Record<TestTemplate[number]['cleaned'], Omit<Validator<any>, 'optional'>>
21
+ > = {
22
+ [K in keyof TestTemplate as K extends `${number}` ? TestTemplate[K]['cleaned'] : never]: CheckIfOptional<
23
+ ReturnType<TestTemplate[K]['callback']['parse']>,
24
+ TestTemplate[K]['original']
25
+ >
26
+ }
27
+
28
+ export const usePathParams = <
29
+ ParamsT extends string[],
30
+ TestTemplate extends {
31
+ [K in keyof ParamsT]: {
32
+ original: ParamsT[K]
33
+ cleaned: CleanUpPathParam<ParamsT[K]>
34
+ callback: ValidatorsT[CleanUpPathParam<ParamsT[K]>]
35
+ }
36
+ },
37
+ ValidatorsT extends Record<CleanUpPathParam<ParamsT[number]>, Omit<Validator<any>, 'optional'>>
38
+ >(
39
+ ctx: ParameterizedContext & { parsedPathParams: ParamsT },
40
+ validators: ValidatorsT
41
+ ): ValidatedData<ParamsT, TestTemplate, ValidatorsT> => {
42
+ const params = ctx.params
43
+ const expectedParams = keysOf(validators).map((name) => ({
44
+ name,
45
+ validator: validators[name],
46
+ }))
47
+
48
+ const validationResults = expectedParams.map((param) => {
49
+ const paramValue = params[param.name] as string
50
+
51
+ // Param is optional and is not provided - skip validation
52
+ if (paramValue === undefined) {
53
+ return { param, validated: true }
54
+ }
55
+
56
+ try {
57
+ const validatorObject = param.validator
58
+ const prevalidatorSuccess = !validatorObject.prevalidate || validatorObject.prevalidate(paramValue)
59
+ const parsedValue = validatorObject.parse(paramValue)
60
+ const validatorSuccess = !validatorObject.validate || validatorObject.validate(parsedValue)
61
+ return {
62
+ param,
63
+ validated: prevalidatorSuccess && validatorSuccess,
64
+ parsedValue,
65
+ }
66
+ } catch (error) {
67
+ return { param, validated: false }
68
+ }
69
+ })
70
+
71
+ const failedValidations = validationResults.filter((result) => !result.validated)
72
+
73
+ if (failedValidations.length > 0) {
74
+ throw new ValidationError(
75
+ `Failed route param validation: ${failedValidations
76
+ .map((result) => getValidationResultMessage(result.param))
77
+ .join(', ')}`
78
+ )
79
+ }
80
+
81
+ const successfulValidations = validationResults.filter((result) => result.validated)
82
+
83
+ const returnValue: Record<string, unknown> = {}
84
+ successfulValidations.forEach((result) => {
85
+ returnValue[result.param.name] = result.parsedValue
86
+ })
87
+
88
+ return returnValue as ValidatedData<ParamsT, TestTemplate, ValidatorsT>
89
+ }
@@ -0,0 +1,224 @@
1
+ import {
2
+ BigIntValidator,
3
+ BooleanValidator,
4
+ NumberValidator,
5
+ OptionalParam,
6
+ RequiredParam,
7
+ StringValidator,
8
+ useQueryParams,
9
+ ValidationError,
10
+ } from '..'
11
+ import { mockContext, mockContextQuery } from '../utils/mockContext'
12
+
13
+ describe('useQueryParams', () => {
14
+ it('parses params correctly', () => {
15
+ const ctx = mockContextQuery(mockContext(), {
16
+ stringParam: 'test_string',
17
+ numberParam: '12',
18
+ booleanParam: 'true',
19
+ objectParam: '{ "foo": "aaa", "bar": "bbb" }',
20
+ })
21
+
22
+ const params = useQueryParams(ctx, {
23
+ stringParam: StringValidator,
24
+ numberParam: NumberValidator,
25
+ booleanParam: BooleanValidator,
26
+ objectParam: RequiredParam<{ foo: string; bar: string }>({
27
+ parse: (v) => JSON.parse(String(v)),
28
+ }),
29
+ })
30
+
31
+ expect(params.stringParam).toEqual('test_string')
32
+ expect(params.numberParam).toEqual(12)
33
+ expect(params.booleanParam).toEqual(true)
34
+ expect(params.objectParam).toEqual({ foo: 'aaa', bar: 'bbb' })
35
+ })
36
+
37
+ it('passes validation on valid parameter', () => {
38
+ const ctx = mockContextQuery(mockContext(), {
39
+ testParam: '12',
40
+ })
41
+
42
+ const params = useQueryParams(ctx, {
43
+ testParam: NumberValidator,
44
+ })
45
+
46
+ expect(params.testParam).toEqual(12)
47
+ })
48
+
49
+ it('fails validation on invalid parameter', () => {
50
+ const test = () => {
51
+ const ctx = mockContextQuery(mockContext(), {
52
+ testParam: 'qwerty',
53
+ })
54
+
55
+ useQueryParams(ctx, {
56
+ testParam: NumberValidator,
57
+ })
58
+ }
59
+
60
+ expect(test).toThrow(ValidationError)
61
+ expect(test).toThrow("Failed query param validation: 'testParam'")
62
+ })
63
+
64
+ it('passes validation on large number', () => {
65
+ const reallyBigNumber = `1${Array(100)
66
+ .fill('0')
67
+ .reduce((total, current) => total + current, '')}`
68
+ const ctx = mockContextQuery(mockContext(), {
69
+ testParam: reallyBigNumber,
70
+ })
71
+
72
+ const params = useQueryParams(ctx, {
73
+ testParam: NumberValidator,
74
+ })
75
+
76
+ expect(params.testParam).toEqual(1e100)
77
+ })
78
+
79
+ it('fails validation on number being too large', () => {
80
+ const test = () => {
81
+ const reallyBigNumber = `1${Array(500)
82
+ .fill('0')
83
+ .reduce((total, current) => total + current, '')}`
84
+ const ctx = mockContextQuery(mockContext(), {
85
+ testParam: reallyBigNumber,
86
+ })
87
+
88
+ useQueryParams(ctx, {
89
+ testParam: NumberValidator,
90
+ })
91
+ }
92
+
93
+ expect(test).toThrow(ValidationError)
94
+ expect(test).toThrow("Failed query param validation: 'testParam'")
95
+ })
96
+
97
+ it('passes validation on very large biging', () => {
98
+ const reallyBigNumber = `1${Array(1000)
99
+ .fill('0')
100
+ .reduce((total, current) => total + current, '')}`
101
+ const ctx = mockContextQuery(mockContext(), {
102
+ testParam: reallyBigNumber,
103
+ })
104
+
105
+ const params = useQueryParams(ctx, {
106
+ testParam: BigIntValidator,
107
+ })
108
+
109
+ const expectedBigInt = BigInt(reallyBigNumber)
110
+ expect(params.testParam).toEqual(expectedBigInt)
111
+ })
112
+
113
+ it('passes validation when optional parameter is not provided', () => {
114
+ const ctx = mockContextQuery(mockContext(), {})
115
+
116
+ const params = useQueryParams(ctx, {
117
+ testParam: OptionalParam(NumberValidator),
118
+ })
119
+
120
+ expect(params.testParam).toEqual(undefined)
121
+ })
122
+
123
+ it('fails validation when required parameter is not provided', () => {
124
+ const test = () => {
125
+ const ctx = mockContextQuery(mockContext(), {})
126
+
127
+ useQueryParams(ctx, {
128
+ testParam: NumberValidator,
129
+ })
130
+ }
131
+
132
+ expect(test).toThrow(ValidationError)
133
+ expect(test).toThrow("Missing query params: 'testParam'")
134
+ })
135
+
136
+ it('passes prevalidation on valid parameter', () => {
137
+ const ctx = mockContextQuery(mockContext(), {
138
+ testParam: 'valid',
139
+ })
140
+
141
+ const params = useQueryParams(ctx, {
142
+ testParam: RequiredParam({
143
+ prevalidate: (v) => v === 'valid',
144
+ parse: (v) => String(v),
145
+ }),
146
+ })
147
+
148
+ expect(params.testParam).toEqual('valid')
149
+ })
150
+
151
+ it('fails prevalidation on invalid parameter', () => {
152
+ const test = () => {
153
+ const ctx = mockContextQuery(mockContext(), {
154
+ testParam: 'invalid',
155
+ })
156
+
157
+ useQueryParams(ctx, {
158
+ testParam: RequiredParam({
159
+ prevalidate: (v) => v === 'valid',
160
+ parse: (v) => String(v),
161
+ }),
162
+ })
163
+ }
164
+
165
+ expect(test).toThrow(ValidationError)
166
+ expect(test).toThrow("Failed query param validation: 'testParam'")
167
+ })
168
+
169
+ it('fails prevalidation on parse error', () => {
170
+ const test = () => {
171
+ const ctx = mockContextQuery(mockContext(), {
172
+ testParam: 'not a json',
173
+ })
174
+
175
+ useQueryParams(ctx, {
176
+ testParam: RequiredParam<{ foo: 'aaa' }>({
177
+ parse: (v) => JSON.parse(String(v)),
178
+ }),
179
+ })
180
+ }
181
+
182
+ expect(test).toThrow(ValidationError)
183
+ expect(test).toThrow("Failed query param validation: 'testParam'")
184
+ })
185
+
186
+ it('sends an error message when validation fails', () => {
187
+ const test = () => {
188
+ const ctx = mockContextQuery(mockContext(), {
189
+ testParam: 'invalid',
190
+ })
191
+
192
+ useQueryParams(ctx, {
193
+ testParam: RequiredParam({
194
+ prevalidate: (v) => v === 'valid',
195
+ parse: (v) => String(v),
196
+ description: 'Description',
197
+ errorMessage: 'Error message',
198
+ }),
199
+ })
200
+ }
201
+
202
+ expect(test).toThrow(ValidationError)
203
+ expect(test).toThrow("Failed query param validation: 'testParam' (Error message)")
204
+ })
205
+
206
+ it('sends the description when validation fails with no error message provided', () => {
207
+ const test = () => {
208
+ const ctx = mockContextQuery(mockContext(), {
209
+ testParam: 'invalid',
210
+ })
211
+
212
+ useQueryParams(ctx, {
213
+ testParam: RequiredParam({
214
+ prevalidate: (v) => v === 'valid',
215
+ parse: (v) => String(v),
216
+ description: 'Description',
217
+ }),
218
+ })
219
+ }
220
+
221
+ expect(test).toThrow(ValidationError)
222
+ expect(test).toThrow("Failed query param validation: 'testParam' (Description)")
223
+ })
224
+ })
@@ -0,0 +1,73 @@
1
+ import { ParameterizedContext } from 'koa'
2
+
3
+ import { ValidationError } from '../errors/UserFacingErrors'
4
+ import { keysOf } from '../utils/object'
5
+ import { getMissingParamMessage, getValidationResultMessage } from '../utils/validationMessages'
6
+ import { Validator } from '../validators/types'
7
+
8
+ type CheckIfOptional<T, B extends boolean | undefined> = B extends false ? T : T | undefined
9
+
10
+ type ValidatedData<T extends Record<string, Validator<any>>> = {
11
+ [K in keyof T]: CheckIfOptional<ReturnType<T[K]['parse']>, T[K]['optional']>
12
+ }
13
+
14
+ export const useQueryParams = <ValidatorsT extends Record<string, Validator<any>>>(
15
+ ctx: ParameterizedContext,
16
+ validators: ValidatorsT
17
+ ): ValidatedData<ValidatorsT> => {
18
+ const query = ctx.query
19
+ const params = keysOf(validators).map((name) => ({
20
+ name,
21
+ validator: validators[name],
22
+ }))
23
+
24
+ const missingParams = params.filter((param) => !query[param.name] && !param.validator.optional)
25
+
26
+ if (missingParams.length > 0) {
27
+ throw new ValidationError(
28
+ `Missing query params: ${missingParams.map((param) => getMissingParamMessage(param)).join(', ')}`
29
+ )
30
+ }
31
+
32
+ const validationResults = params.map((param) => {
33
+ const paramValue = query[param.name] as string
34
+
35
+ // Param is optional and is not provided - skip validation
36
+ if (paramValue === undefined) {
37
+ return { param, validated: true }
38
+ }
39
+
40
+ try {
41
+ const validatorObject = param.validator
42
+ const prevalidatorSuccess = !validatorObject.prevalidate || validatorObject.prevalidate(paramValue)
43
+ const parsedValue = validatorObject.parse(paramValue)
44
+ const validatorSuccess = !validatorObject.validate || validatorObject.validate(parsedValue)
45
+ return {
46
+ param,
47
+ validated: prevalidatorSuccess && validatorSuccess,
48
+ parsedValue,
49
+ }
50
+ } catch (error) {
51
+ return { param, validated: false }
52
+ }
53
+ })
54
+
55
+ const failedValidations = validationResults.filter((result) => !result.validated)
56
+
57
+ if (failedValidations.length > 0) {
58
+ throw new ValidationError(
59
+ `Failed query param validation: ${failedValidations
60
+ .map((result) => getValidationResultMessage(result.param))
61
+ .join(', ')}`
62
+ )
63
+ }
64
+
65
+ const successfulValidations = validationResults.filter((result) => result.validated)
66
+
67
+ const returnValue: Record<string, unknown> = {}
68
+ successfulValidations.forEach((result) => {
69
+ returnValue[result.param.name] = result.parsedValue
70
+ })
71
+
72
+ return returnValue as ValidatedData<ValidatorsT>
73
+ }