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,48 @@
1
+ import { useExposeApiModel, useExposeNamedApiModels } from './useExposeApiModel'
2
+
3
+ type FooBarObject = {
4
+ foo: string
5
+ bar: number
6
+ baz: boolean
7
+ }
8
+
9
+ useExposeApiModel<FooBarObject>()
10
+ useExposeNamedApiModels<{
11
+ SimpleString: string
12
+ SimpleNumber: number
13
+ SimpleBoolean: boolean
14
+ NumberBase: 'foo' | 'bar'
15
+ }>()
16
+
17
+ type OptionalFooObject = Partial<Pick<FooBarObject, 'foo'>>
18
+ useExposeApiModel<OptionalFooObject>()
19
+
20
+ type UnionWithTuple = { fff: string | [string, string, string] }
21
+ useExposeApiModel<UnionWithTuple>()
22
+
23
+ type NumberBase = 'dec' | 'hex' | 'bin'
24
+
25
+ type ModelWithPrimitiveRecord = {
26
+ key: Record<string, number>
27
+ }
28
+ useExposeApiModel<ModelWithPrimitiveRecord>()
29
+
30
+ type ModelWithSimpleRecord = {
31
+ key: Record<NumberBase, number>
32
+ }
33
+ useExposeApiModel<ModelWithSimpleRecord>()
34
+
35
+ type ModelWithComplexRecord = {
36
+ key: Record<NumberBase, NumberBase>
37
+ }
38
+ useExposeApiModel<ModelWithComplexRecord>()
39
+
40
+ const modelAsObject = {
41
+ foo: '123',
42
+ bar: 123,
43
+ }
44
+ useExposeApiModel<typeof modelAsObject>
45
+
46
+ useExposeNamedApiModels<{
47
+ RenamedModelAsObject: typeof modelAsObject
48
+ }>
@@ -0,0 +1,388 @@
1
+ import { analyzeSourceFileExposedModels } from '../../openapi/analyzerModule/analyzerModule'
2
+ import { loadTestData } from '../../utils/loadTestData'
3
+ import { useExposeApiModel, useExposeNamedApiModels } from './useExposeApiModel'
4
+
5
+ describe('useExposeApiModel', () => {
6
+ describe('when analyzing a test data file', () => {
7
+ const dataFile = loadTestData('useExposeApiModel.spec.data.ts')
8
+ let analysisResult: ReturnType<typeof analyzeSourceFileExposedModels>
9
+
10
+ const analyzeModelByName = (name: string) => {
11
+ if (!analysisResult) {
12
+ analysisResult = analyzeSourceFileExposedModels(dataFile)
13
+ }
14
+ const exposedModel = analysisResult.find((model) => model.name === name)
15
+ if (!exposedModel) {
16
+ throw new Error(`No exposed model with name ${name} found!`)
17
+ }
18
+ return exposedModel
19
+ }
20
+
21
+ it('does not have side effects when invoked directly', () => {
22
+ useExposeApiModel()
23
+ useExposeNamedApiModels()
24
+ })
25
+
26
+ it('parses single expose model correctly', () => {
27
+ const model = analyzeModelByName('FooBarObject')
28
+
29
+ expect(model.shape).toEqual([
30
+ {
31
+ role: 'property',
32
+ identifier: 'foo',
33
+ shape: 'string',
34
+ optional: false,
35
+ },
36
+ {
37
+ role: 'property',
38
+ identifier: 'bar',
39
+ shape: 'number',
40
+ optional: false,
41
+ },
42
+ {
43
+ role: 'property',
44
+ identifier: 'baz',
45
+ shape: 'boolean',
46
+ optional: false,
47
+ },
48
+ ])
49
+ })
50
+
51
+ it('parses named exposed models correctly', () => {
52
+ const simpleStringModel = analyzeModelByName('SimpleString')
53
+ const simpleNumberModel = analyzeModelByName('SimpleNumber')
54
+ const numberBaseModel = analyzeModelByName('NumberBase')
55
+
56
+ expect(simpleStringModel.shape).toEqual('string')
57
+ expect(simpleNumberModel.shape).toEqual('number')
58
+ expect(numberBaseModel.shape).toEqual([
59
+ {
60
+ role: 'union',
61
+ optional: false,
62
+ shape: [
63
+ {
64
+ role: 'union_entry',
65
+ optional: false,
66
+ shape: [{ optional: false, role: 'literal_string', shape: 'foo' }],
67
+ },
68
+ {
69
+ role: 'union_entry',
70
+ optional: false,
71
+ shape: [{ optional: false, role: 'literal_string', shape: 'bar' }],
72
+ },
73
+ ],
74
+ },
75
+ ])
76
+ })
77
+
78
+ it('parses model with type utilities correctly', () => {
79
+ const optionalFooObject = analyzeModelByName('OptionalFooObject')
80
+
81
+ expect(optionalFooObject).toEqual({
82
+ name: 'OptionalFooObject',
83
+ shape: [{ identifier: 'foo', optional: true, role: 'property', shape: 'string' }],
84
+ })
85
+ })
86
+
87
+ it('parses model with tuple correctly', () => {
88
+ const unionWithTupleObject = analyzeModelByName('UnionWithTuple')
89
+
90
+ expect(unionWithTupleObject).toEqual({
91
+ name: 'UnionWithTuple',
92
+ shape: [
93
+ {
94
+ role: 'property',
95
+ identifier: 'fff',
96
+ shape: [
97
+ {
98
+ role: 'union',
99
+ shape: [
100
+ { role: 'union_entry', shape: 'string', optional: false },
101
+ {
102
+ role: 'union_entry',
103
+ shape: [
104
+ {
105
+ role: 'tuple',
106
+ shape: [
107
+ {
108
+ role: 'tuple_entry',
109
+ shape: 'string',
110
+ optional: false,
111
+ },
112
+ {
113
+ role: 'tuple_entry',
114
+ shape: 'string',
115
+ optional: false,
116
+ },
117
+ {
118
+ role: 'tuple_entry',
119
+ shape: 'string',
120
+ optional: false,
121
+ },
122
+ ],
123
+ optional: false,
124
+ },
125
+ ],
126
+ optional: false,
127
+ },
128
+ ],
129
+ optional: false,
130
+ },
131
+ ],
132
+ optional: false,
133
+ },
134
+ ],
135
+ })
136
+ })
137
+
138
+ it('parses model with primitive Record correctly', () => {
139
+ const modelWithRecord = analyzeModelByName('ModelWithPrimitiveRecord')
140
+
141
+ expect(modelWithRecord).toEqual({
142
+ name: 'ModelWithPrimitiveRecord',
143
+ shape: [
144
+ {
145
+ role: 'property',
146
+ identifier: 'key',
147
+ shape: [{ role: 'record', shape: 'number', optional: false }],
148
+ optional: false,
149
+ },
150
+ ],
151
+ })
152
+ })
153
+
154
+ it('parses model with simple Record correctly', () => {
155
+ const modelWithRecord = analyzeModelByName('ModelWithSimpleRecord')
156
+
157
+ expect(modelWithRecord).toEqual({
158
+ name: 'ModelWithSimpleRecord',
159
+ shape: [
160
+ {
161
+ role: 'property',
162
+ identifier: 'key',
163
+ shape: [
164
+ {
165
+ role: 'property',
166
+ identifier: 'dec',
167
+ shape: 'number',
168
+ optional: false,
169
+ },
170
+ {
171
+ role: 'property',
172
+ identifier: 'hex',
173
+ shape: 'number',
174
+ optional: false,
175
+ },
176
+ {
177
+ role: 'property',
178
+ identifier: 'bin',
179
+ shape: 'number',
180
+ optional: false,
181
+ },
182
+ ],
183
+ optional: false,
184
+ },
185
+ ],
186
+ })
187
+ })
188
+
189
+ it('parses model with complex Record correctly', () => {
190
+ const modelWithRecord = analyzeModelByName('ModelWithComplexRecord')
191
+
192
+ expect(modelWithRecord).toEqual({
193
+ name: 'ModelWithComplexRecord',
194
+ shape: [
195
+ {
196
+ role: 'property',
197
+ identifier: 'key',
198
+ shape: [
199
+ {
200
+ role: 'property',
201
+ identifier: 'dec',
202
+ shape: [
203
+ {
204
+ role: 'union',
205
+ shape: [
206
+ {
207
+ role: 'union_entry',
208
+ shape: [
209
+ {
210
+ role: 'literal_string',
211
+ shape: 'dec',
212
+ optional: false,
213
+ },
214
+ ],
215
+ optional: false,
216
+ },
217
+ {
218
+ role: 'union_entry',
219
+ shape: [
220
+ {
221
+ role: 'literal_string',
222
+ shape: 'hex',
223
+ optional: false,
224
+ },
225
+ ],
226
+ optional: false,
227
+ },
228
+ {
229
+ role: 'union_entry',
230
+ shape: [
231
+ {
232
+ role: 'literal_string',
233
+ shape: 'bin',
234
+ optional: false,
235
+ },
236
+ ],
237
+ optional: false,
238
+ },
239
+ ],
240
+ optional: false,
241
+ },
242
+ ],
243
+ optional: false,
244
+ },
245
+ {
246
+ role: 'property',
247
+ identifier: 'hex',
248
+ shape: [
249
+ {
250
+ role: 'union',
251
+ shape: [
252
+ {
253
+ role: 'union_entry',
254
+ shape: [
255
+ {
256
+ role: 'literal_string',
257
+ shape: 'dec',
258
+ optional: false,
259
+ },
260
+ ],
261
+ optional: false,
262
+ },
263
+ {
264
+ role: 'union_entry',
265
+ shape: [
266
+ {
267
+ role: 'literal_string',
268
+ shape: 'hex',
269
+ optional: false,
270
+ },
271
+ ],
272
+ optional: false,
273
+ },
274
+ {
275
+ role: 'union_entry',
276
+ shape: [
277
+ {
278
+ role: 'literal_string',
279
+ shape: 'bin',
280
+ optional: false,
281
+ },
282
+ ],
283
+ optional: false,
284
+ },
285
+ ],
286
+ optional: false,
287
+ },
288
+ ],
289
+ optional: false,
290
+ },
291
+ {
292
+ role: 'property',
293
+ identifier: 'bin',
294
+ shape: [
295
+ {
296
+ role: 'union',
297
+ shape: [
298
+ {
299
+ role: 'union_entry',
300
+ shape: [
301
+ {
302
+ role: 'literal_string',
303
+ shape: 'dec',
304
+ optional: false,
305
+ },
306
+ ],
307
+ optional: false,
308
+ },
309
+ {
310
+ role: 'union_entry',
311
+ shape: [
312
+ {
313
+ role: 'literal_string',
314
+ shape: 'hex',
315
+ optional: false,
316
+ },
317
+ ],
318
+ optional: false,
319
+ },
320
+ {
321
+ role: 'union_entry',
322
+ shape: [
323
+ {
324
+ role: 'literal_string',
325
+ shape: 'bin',
326
+ optional: false,
327
+ },
328
+ ],
329
+ optional: false,
330
+ },
331
+ ],
332
+ optional: false,
333
+ },
334
+ ],
335
+ optional: false,
336
+ },
337
+ ],
338
+ optional: false,
339
+ },
340
+ ],
341
+ })
342
+ })
343
+
344
+ it('parses model defined with typeof expression', () => {
345
+ const modelWithRecord = analyzeModelByName('modelAsObject')
346
+
347
+ expect(modelWithRecord).toEqual({
348
+ name: 'modelAsObject',
349
+ shape: [
350
+ {
351
+ role: 'property',
352
+ identifier: 'foo',
353
+ shape: 'string',
354
+ optional: false,
355
+ },
356
+ {
357
+ role: 'property',
358
+ identifier: 'bar',
359
+ shape: 'number',
360
+ optional: false,
361
+ },
362
+ ],
363
+ })
364
+ })
365
+
366
+ it('parses named model defined with typeof expression', () => {
367
+ const modelWithRecord = analyzeModelByName('RenamedModelAsObject')
368
+
369
+ expect(modelWithRecord).toEqual({
370
+ name: 'RenamedModelAsObject',
371
+ shape: [
372
+ {
373
+ role: 'property',
374
+ identifier: 'foo',
375
+ shape: 'string',
376
+ optional: false,
377
+ },
378
+ {
379
+ role: 'property',
380
+ identifier: 'bar',
381
+ shape: 'number',
382
+ optional: false,
383
+ },
384
+ ],
385
+ })
386
+ })
387
+ })
388
+ })
@@ -0,0 +1,9 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2
+ export const useExposeApiModel = <_T>() => {
3
+ // Empty
4
+ }
5
+
6
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
7
+ export const useExposeNamedApiModels = <_T extends Record<string, any>>() => {
8
+ // Empty
9
+ }
@@ -0,0 +1,186 @@
1
+ import {
2
+ BooleanValidator,
3
+ NumberValidator,
4
+ OptionalParam,
5
+ RequiredParam,
6
+ StringValidator,
7
+ useHeaderParams,
8
+ ValidationError,
9
+ } from '..'
10
+ import { mockContext, mockContextHeaders } from '../utils/mockContext'
11
+
12
+ describe('useHeaderParams', () => {
13
+ it('parses params correctly', () => {
14
+ const ctx = mockContextHeaders(mockContext(), {
15
+ 'string-header': 'test_string',
16
+ 'number-header': '12',
17
+ 'boolean-header': 'true',
18
+ 'object-header': '{ "foo": "aaa", "bar": "bbb" }',
19
+ })
20
+
21
+ const params = useHeaderParams(ctx, {
22
+ 'string-header': StringValidator,
23
+ 'number-header': NumberValidator,
24
+ 'boolean-header': BooleanValidator,
25
+ 'object-header': RequiredParam<{ foo: string; bar: string }>({
26
+ parse: (v) => JSON.parse(String(v)),
27
+ }),
28
+ })
29
+
30
+ expect(params.stringHeader).toEqual('test_string')
31
+ expect(params.numberHeader).toEqual(12)
32
+ expect(params.booleanHeader).toEqual(true)
33
+ expect(params.objectHeader).toEqual({ foo: 'aaa', bar: 'bbb' })
34
+ })
35
+
36
+ it('parses camelCase params with camelCase headers correctly', () => {
37
+ const ctx = mockContextHeaders(mockContext(), {
38
+ stringheader: 'test_string',
39
+ })
40
+
41
+ const params = useHeaderParams(ctx, {
42
+ stringHeader: StringValidator,
43
+ })
44
+
45
+ expect(params.stringHeader).toEqual('test_string')
46
+ })
47
+
48
+ it('passes validation on valid parameter', () => {
49
+ const ctx = mockContextHeaders(mockContext(), {
50
+ 'test-header': '12',
51
+ })
52
+
53
+ const params = useHeaderParams(ctx, {
54
+ 'test-header': NumberValidator,
55
+ })
56
+
57
+ expect(params.testHeader).toEqual(12)
58
+ })
59
+
60
+ it('fails validation on invalid parameter', () => {
61
+ const test = () => {
62
+ const ctx = mockContextHeaders(mockContext(), {
63
+ 'test-header': 'qwerty',
64
+ })
65
+
66
+ useHeaderParams(ctx, {
67
+ 'test-header': NumberValidator,
68
+ })
69
+ }
70
+
71
+ expect(test).toThrow(ValidationError)
72
+ expect(test).toThrow("Failed header validation: 'test-header'")
73
+ })
74
+
75
+ it('passes validation when optional parameter is not provided', () => {
76
+ const ctx = mockContextHeaders(mockContext(), {})
77
+
78
+ const params = useHeaderParams(ctx, {
79
+ 'test-header': OptionalParam(NumberValidator),
80
+ })
81
+
82
+ expect(params.testHeader).toEqual(undefined)
83
+ })
84
+
85
+ it('fails validation when required parameter is not provided', () => {
86
+ const test = () => {
87
+ const ctx = mockContextHeaders(mockContext(), {})
88
+
89
+ useHeaderParams(ctx, {
90
+ 'test-header': NumberValidator,
91
+ })
92
+ }
93
+
94
+ expect(test).toThrow(ValidationError)
95
+ expect(test).toThrow("Missing headers: 'test-header'")
96
+ })
97
+
98
+ it('passes prevalidation on valid parameter', () => {
99
+ const ctx = mockContextHeaders(mockContext(), {
100
+ 'test-header': 'valid',
101
+ })
102
+
103
+ const params = useHeaderParams(ctx, {
104
+ 'test-header': RequiredParam({
105
+ prevalidate: (v) => v === 'valid',
106
+ parse: (v) => String(v),
107
+ }),
108
+ })
109
+
110
+ expect(params.testHeader).toEqual('valid')
111
+ })
112
+
113
+ it('fails prevalidation on invalid parameter', () => {
114
+ const test = () => {
115
+ const ctx = mockContextHeaders(mockContext(), {
116
+ 'test-header': 'invalid',
117
+ })
118
+
119
+ useHeaderParams(ctx, {
120
+ 'test-header': RequiredParam({
121
+ prevalidate: (v) => v === 'valid',
122
+ parse: (v) => String(v),
123
+ }),
124
+ })
125
+ }
126
+
127
+ expect(test).toThrow(ValidationError)
128
+ expect(test).toThrow("Failed header validation: 'test-header'")
129
+ })
130
+
131
+ it('fails prevalidation on parse error', () => {
132
+ const test = () => {
133
+ const ctx = mockContextHeaders(mockContext(), {
134
+ 'test-header': 'not a json',
135
+ })
136
+
137
+ useHeaderParams(ctx, {
138
+ 'test-header': RequiredParam<{ foo: 'aaa' }>({
139
+ parse: (v) => JSON.parse(String(v)),
140
+ }),
141
+ })
142
+ }
143
+
144
+ expect(test).toThrow(ValidationError)
145
+ expect(test).toThrow("Failed header validation: 'test-header'")
146
+ })
147
+
148
+ it('sends an error message when validation fails', () => {
149
+ const test = () => {
150
+ const ctx = mockContextHeaders(mockContext(), {
151
+ 'test-header': 'invalid',
152
+ })
153
+
154
+ useHeaderParams(ctx, {
155
+ 'test-header': RequiredParam({
156
+ prevalidate: (v) => v === 'valid',
157
+ parse: (v) => String(v),
158
+ description: 'Description',
159
+ errorMessage: 'Error message',
160
+ }),
161
+ })
162
+ }
163
+
164
+ expect(test).toThrow(ValidationError)
165
+ expect(test).toThrow("Failed header validation: 'test-header' (Error message)")
166
+ })
167
+
168
+ it('sends the description when validation fails with no error message provided', () => {
169
+ const test = () => {
170
+ const ctx = mockContextHeaders(mockContext(), {
171
+ 'test-header': 'invalid',
172
+ })
173
+
174
+ useHeaderParams(ctx, {
175
+ 'test-header': RequiredParam({
176
+ prevalidate: (v) => v === 'valid',
177
+ parse: (v) => String(v),
178
+ description: 'Description',
179
+ }),
180
+ })
181
+ }
182
+
183
+ expect(test).toThrow(ValidationError)
184
+ expect(test).toThrow("Failed header validation: 'test-header' (Description)")
185
+ })
186
+ })
@@ -0,0 +1,83 @@
1
+ import { ParameterizedContext } from 'koa'
2
+
3
+ import { ValidationError } from '../errors/UserFacingErrors'
4
+ import { kebabToCamelCase, keysOf } from '../utils/object'
5
+ import { CamelCase } from '../utils/TypeUtils'
6
+ import {
7
+ getMissingParamMessage,
8
+ getValidationResultMessage as getValidationFailedMessage,
9
+ } from '../utils/validationMessages'
10
+ import { Validator } from '../validators/types'
11
+
12
+ type CheckIfOptional<T, B extends boolean | undefined> = B extends false ? T : T | undefined
13
+
14
+ type HeaderToCamelCase<T> = T extends string ? CamelCase<Uncapitalize<T>> : T
15
+
16
+ type ValidatedData<T extends Record<string, Validator<any>>> = {
17
+ [K in keyof T as HeaderToCamelCase<K>]: CheckIfOptional<ReturnType<T[K]['parse']>, T[K]['optional']>
18
+ }
19
+
20
+ export const useHeaderParams = <ValidatorsT extends Record<string, Validator<any>>>(
21
+ ctx: ParameterizedContext,
22
+ validators: ValidatorsT
23
+ ) => {
24
+ const headers = ctx.headers
25
+ const params = keysOf(validators).map((name) => ({
26
+ name: name.toLowerCase(),
27
+ originalName: name,
28
+ validator: validators[name],
29
+ }))
30
+
31
+ const missingParams = params.filter((param) => !headers[param.name] && !param.validator.optional)
32
+
33
+ if (missingParams.length > 0) {
34
+ throw new ValidationError(
35
+ `Missing headers: ${missingParams.map((param) => getMissingParamMessage(param)).join(', ')}`
36
+ )
37
+ }
38
+
39
+ const validationResults = params.map((param) => {
40
+ const paramValue = headers[param.name]
41
+
42
+ // Param is optional and is not provided - skip validation
43
+ if (paramValue === undefined) {
44
+ return { param, validated: true }
45
+ }
46
+
47
+ try {
48
+ const validatorObject = param.validator
49
+ const prevalidatorSuccess =
50
+ !validatorObject.prevalidate || validatorObject.prevalidate(paramValue as string)
51
+ const parsedValue = validatorObject.parse(paramValue as string)
52
+ const validatorSuccess = !validatorObject.validate || validatorObject.validate(parsedValue)
53
+ return {
54
+ param,
55
+ validated: prevalidatorSuccess && validatorSuccess,
56
+ parsedValue,
57
+ }
58
+ } catch (error) {
59
+ return { param, validated: false }
60
+ }
61
+ })
62
+
63
+ const failedValidations = validationResults.filter((result) => !result.validated)
64
+
65
+ if (failedValidations.length > 0) {
66
+ throw new ValidationError(
67
+ `Failed header validation: ${failedValidations
68
+ .map((result) => getValidationFailedMessage(result.param))
69
+ .join(', ')}`
70
+ )
71
+ }
72
+
73
+ const successfulValidations = validationResults.filter((result) => result.validated)
74
+
75
+ const returnValue: Record<string, unknown> = {}
76
+ successfulValidations.forEach((result) => {
77
+ returnValue[kebabToCamelCase(result.param.originalName)] = result.parsedValue
78
+ })
79
+
80
+ return returnValue as ValidatedData<ValidatorsT>
81
+ }
82
+
83
+ export const useRequestHeaders = useHeaderParams