@furystack/rest-service 4.0.19

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 (285) hide show
  1. package/LICENSE +339 -0
  2. package/README.md +219 -0
  3. package/dist/actions/error-action.d.ts +14 -0
  4. package/dist/actions/error-action.d.ts.map +1 -0
  5. package/dist/actions/error-action.js +29 -0
  6. package/dist/actions/error-action.js.map +1 -0
  7. package/dist/actions/error-action.spec.d.ts +2 -0
  8. package/dist/actions/error-action.spec.d.ts.map +1 -0
  9. package/dist/actions/error-action.spec.js +51 -0
  10. package/dist/actions/error-action.spec.js.map +1 -0
  11. package/dist/actions/get-current-user.d.ts +11 -0
  12. package/dist/actions/get-current-user.d.ts.map +1 -0
  13. package/dist/actions/get-current-user.js +15 -0
  14. package/dist/actions/get-current-user.js.map +1 -0
  15. package/dist/actions/get-current-user.spec.d.ts +2 -0
  16. package/dist/actions/get-current-user.spec.d.ts.map +1 -0
  17. package/dist/actions/get-current-user.spec.js +20 -0
  18. package/dist/actions/get-current-user.spec.js.map +1 -0
  19. package/dist/actions/index.d.ts +7 -0
  20. package/dist/actions/index.d.ts.map +1 -0
  21. package/dist/actions/index.js +10 -0
  22. package/dist/actions/index.js.map +1 -0
  23. package/dist/actions/is-authenticated.d.ts +14 -0
  24. package/dist/actions/is-authenticated.d.ts.map +1 -0
  25. package/dist/actions/is-authenticated.js +17 -0
  26. package/dist/actions/is-authenticated.js.map +1 -0
  27. package/dist/actions/is-authenticated.spec.d.ts +2 -0
  28. package/dist/actions/is-authenticated.spec.d.ts.map +1 -0
  29. package/dist/actions/is-authenticated.spec.js +19 -0
  30. package/dist/actions/is-authenticated.spec.js.map +1 -0
  31. package/dist/actions/login-action.spec.d.ts +2 -0
  32. package/dist/actions/login-action.spec.d.ts.map +1 -0
  33. package/dist/actions/login-action.spec.js +35 -0
  34. package/dist/actions/login-action.spec.js.map +1 -0
  35. package/dist/actions/login.d.ts +16 -0
  36. package/dist/actions/login.d.ts.map +1 -0
  37. package/dist/actions/login.js +26 -0
  38. package/dist/actions/login.js.map +1 -0
  39. package/dist/actions/logout-action.spec.d.ts +2 -0
  40. package/dist/actions/logout-action.spec.d.ts.map +1 -0
  41. package/dist/actions/logout-action.spec.js +23 -0
  42. package/dist/actions/logout-action.spec.js.map +1 -0
  43. package/dist/actions/logout.d.ts +14 -0
  44. package/dist/actions/logout.d.ts.map +1 -0
  45. package/dist/actions/logout.js +20 -0
  46. package/dist/actions/logout.js.map +1 -0
  47. package/dist/actions/not-found-action.d.ts +10 -0
  48. package/dist/actions/not-found-action.d.ts.map +1 -0
  49. package/dist/actions/not-found-action.js +14 -0
  50. package/dist/actions/not-found-action.js.map +1 -0
  51. package/dist/actions/not-found-action.spec.d.ts +2 -0
  52. package/dist/actions/not-found-action.spec.d.ts.map +1 -0
  53. package/dist/actions/not-found-action.spec.js +17 -0
  54. package/dist/actions/not-found-action.spec.js.map +1 -0
  55. package/dist/add-cors-header.spec.d.ts +2 -0
  56. package/dist/add-cors-header.spec.d.ts.map +1 -0
  57. package/dist/add-cors-header.spec.js +99 -0
  58. package/dist/add-cors-header.spec.js.map +1 -0
  59. package/dist/api-manager.d.ts +61 -0
  60. package/dist/api-manager.d.ts.map +1 -0
  61. package/dist/api-manager.js +144 -0
  62. package/dist/api-manager.js.map +1 -0
  63. package/dist/authenticate.d.ts +5 -0
  64. package/dist/authenticate.d.ts.map +1 -0
  65. package/dist/authenticate.js +20 -0
  66. package/dist/authenticate.js.map +1 -0
  67. package/dist/authenticate.spec.d.ts +2 -0
  68. package/dist/authenticate.spec.d.ts.map +1 -0
  69. package/dist/authenticate.spec.js +59 -0
  70. package/dist/authenticate.spec.js.map +1 -0
  71. package/dist/authorize.d.ts +5 -0
  72. package/dist/authorize.d.ts.map +1 -0
  73. package/dist/authorize.js +22 -0
  74. package/dist/authorize.js.map +1 -0
  75. package/dist/authorize.spec.d.ts +2 -0
  76. package/dist/authorize.spec.d.ts.map +1 -0
  77. package/dist/authorize.spec.js +55 -0
  78. package/dist/authorize.spec.js.map +1 -0
  79. package/dist/endpoint-generators/create-delete-endpoint.d.ts +17 -0
  80. package/dist/endpoint-generators/create-delete-endpoint.d.ts.map +1 -0
  81. package/dist/endpoint-generators/create-delete-endpoint.js +24 -0
  82. package/dist/endpoint-generators/create-delete-endpoint.js.map +1 -0
  83. package/dist/endpoint-generators/create-delete-endpoint.spec.d.ts +2 -0
  84. package/dist/endpoint-generators/create-delete-endpoint.spec.d.ts.map +1 -0
  85. package/dist/endpoint-generators/create-delete-endpoint.spec.js +33 -0
  86. package/dist/endpoint-generators/create-delete-endpoint.spec.js.map +1 -0
  87. package/dist/endpoint-generators/create-get-collection-endpoint.d.ts +17 -0
  88. package/dist/endpoint-generators/create-get-collection-endpoint.d.ts.map +1 -0
  89. package/dist/endpoint-generators/create-get-collection-endpoint.js +26 -0
  90. package/dist/endpoint-generators/create-get-collection-endpoint.js.map +1 -0
  91. package/dist/endpoint-generators/create-get-collection-endpoint.spec.d.ts +2 -0
  92. package/dist/endpoint-generators/create-get-collection-endpoint.spec.d.ts.map +1 -0
  93. package/dist/endpoint-generators/create-get-collection-endpoint.spec.js +143 -0
  94. package/dist/endpoint-generators/create-get-collection-endpoint.spec.js.map +1 -0
  95. package/dist/endpoint-generators/create-get-entity-endpoint.d.ts +17 -0
  96. package/dist/endpoint-generators/create-get-entity-endpoint.d.ts.map +1 -0
  97. package/dist/endpoint-generators/create-get-entity-endpoint.js +29 -0
  98. package/dist/endpoint-generators/create-get-entity-endpoint.js.map +1 -0
  99. package/dist/endpoint-generators/create-get-entity-endpoint.spec.d.ts +2 -0
  100. package/dist/endpoint-generators/create-get-entity-endpoint.spec.d.ts.map +1 -0
  101. package/dist/endpoint-generators/create-get-entity-endpoint.spec.js +74 -0
  102. package/dist/endpoint-generators/create-get-entity-endpoint.spec.js.map +1 -0
  103. package/dist/endpoint-generators/create-patch-endpoint.d.ts +18 -0
  104. package/dist/endpoint-generators/create-patch-endpoint.d.ts.map +1 -0
  105. package/dist/endpoint-generators/create-patch-endpoint.js +26 -0
  106. package/dist/endpoint-generators/create-patch-endpoint.js.map +1 -0
  107. package/dist/endpoint-generators/create-patch-endpoint.spec.d.ts +2 -0
  108. package/dist/endpoint-generators/create-patch-endpoint.spec.d.ts.map +1 -0
  109. package/dist/endpoint-generators/create-patch-endpoint.spec.js +36 -0
  110. package/dist/endpoint-generators/create-patch-endpoint.spec.js.map +1 -0
  111. package/dist/endpoint-generators/create-post-endpoint.d.ts +18 -0
  112. package/dist/endpoint-generators/create-post-endpoint.d.ts.map +1 -0
  113. package/dist/endpoint-generators/create-post-endpoint.js +29 -0
  114. package/dist/endpoint-generators/create-post-endpoint.js.map +1 -0
  115. package/dist/endpoint-generators/create-post-endpoint.spec.d.ts +2 -0
  116. package/dist/endpoint-generators/create-post-endpoint.spec.d.ts.map +1 -0
  117. package/dist/endpoint-generators/create-post-endpoint.spec.js +34 -0
  118. package/dist/endpoint-generators/create-post-endpoint.spec.js.map +1 -0
  119. package/dist/endpoint-generators/index.d.ts +6 -0
  120. package/dist/endpoint-generators/index.d.ts.map +1 -0
  121. package/dist/endpoint-generators/index.js +9 -0
  122. package/dist/endpoint-generators/index.js.map +1 -0
  123. package/dist/endpoint-generators/utils.d.ts +9 -0
  124. package/dist/endpoint-generators/utils.d.ts.map +1 -0
  125. package/dist/endpoint-generators/utils.js +27 -0
  126. package/dist/endpoint-generators/utils.js.map +1 -0
  127. package/dist/http-authentication-settings.d.ts +17 -0
  128. package/dist/http-authentication-settings.d.ts.map +1 -0
  129. package/dist/http-authentication-settings.js +26 -0
  130. package/dist/http-authentication-settings.js.map +1 -0
  131. package/dist/http-user-context.d.ts +54 -0
  132. package/dist/http-user-context.d.ts.map +1 -0
  133. package/dist/http-user-context.js +153 -0
  134. package/dist/http-user-context.js.map +1 -0
  135. package/dist/http-user-context.spec.d.ts +4 -0
  136. package/dist/http-user-context.spec.d.ts.map +1 -0
  137. package/dist/http-user-context.spec.js +267 -0
  138. package/dist/http-user-context.spec.js.map +1 -0
  139. package/dist/incoming-message-extensions.d.ts +8 -0
  140. package/dist/incoming-message-extensions.d.ts.map +1 -0
  141. package/dist/incoming-message-extensions.js +14 -0
  142. package/dist/incoming-message-extensions.js.map +1 -0
  143. package/dist/incoming-message-extensions.spec.d.ts +2 -0
  144. package/dist/incoming-message-extensions.spec.d.ts.map +1 -0
  145. package/dist/incoming-message-extensions.spec.js +39 -0
  146. package/dist/incoming-message-extensions.spec.js.map +1 -0
  147. package/dist/index.d.ts +17 -0
  148. package/dist/index.d.ts.map +1 -0
  149. package/dist/index.js +20 -0
  150. package/dist/index.js.map +1 -0
  151. package/dist/injector-extensions.d.ts +21 -0
  152. package/dist/injector-extensions.d.ts.map +1 -0
  153. package/dist/injector-extensions.js +14 -0
  154. package/dist/injector-extensions.js.map +1 -0
  155. package/dist/injector-extensions.spec.d.ts +2 -0
  156. package/dist/injector-extensions.spec.d.ts.map +1 -0
  157. package/dist/injector-extensions.spec.js +19 -0
  158. package/dist/injector-extensions.spec.js.map +1 -0
  159. package/dist/models/cors-options.d.ts +22 -0
  160. package/dist/models/cors-options.d.ts.map +1 -0
  161. package/dist/models/cors-options.js +3 -0
  162. package/dist/models/cors-options.js.map +1 -0
  163. package/dist/models/default-session.d.ts +14 -0
  164. package/dist/models/default-session.d.ts.map +1 -0
  165. package/dist/models/default-session.js +10 -0
  166. package/dist/models/default-session.js.map +1 -0
  167. package/dist/request-action-implementation.d.ts +54 -0
  168. package/dist/request-action-implementation.d.ts.map +1 -0
  169. package/dist/request-action-implementation.js +42 -0
  170. package/dist/request-action-implementation.js.map +1 -0
  171. package/dist/rest-service.integration.spec.d.ts +2 -0
  172. package/dist/rest-service.integration.spec.d.ts.map +1 -0
  173. package/dist/rest-service.integration.spec.js +129 -0
  174. package/dist/rest-service.integration.spec.js.map +1 -0
  175. package/dist/rest.integration.test.d.ts +58 -0
  176. package/dist/rest.integration.test.d.ts.map +1 -0
  177. package/dist/rest.integration.test.js +94 -0
  178. package/dist/rest.integration.test.js.map +1 -0
  179. package/dist/schema-validator/index.d.ts +3 -0
  180. package/dist/schema-validator/index.d.ts.map +1 -0
  181. package/dist/schema-validator/index.js +6 -0
  182. package/dist/schema-validator/index.js.map +1 -0
  183. package/dist/schema-validator/schema-validation-error.d.ts +10 -0
  184. package/dist/schema-validator/schema-validation-error.d.ts.map +1 -0
  185. package/dist/schema-validator/schema-validation-error.js +15 -0
  186. package/dist/schema-validator/schema-validation-error.js.map +1 -0
  187. package/dist/schema-validator/schema-validator.d.ts +20 -0
  188. package/dist/schema-validator/schema-validator.d.ts.map +1 -0
  189. package/dist/schema-validator/schema-validator.js +36 -0
  190. package/dist/schema-validator/schema-validator.js.map +1 -0
  191. package/dist/schema-validator/schema-validator.test.d.ts +2 -0
  192. package/dist/schema-validator/schema-validator.test.d.ts.map +1 -0
  193. package/dist/schema-validator/schema-validator.test.js +62 -0
  194. package/dist/schema-validator/schema-validator.test.js.map +1 -0
  195. package/dist/schema-validator/validate-examples.d.ts +37 -0
  196. package/dist/schema-validator/validate-examples.d.ts.map +1 -0
  197. package/dist/schema-validator/validate-examples.js +29 -0
  198. package/dist/schema-validator/validate-examples.js.map +1 -0
  199. package/dist/server-manager.d.ts +30 -0
  200. package/dist/server-manager.d.ts.map +1 -0
  201. package/dist/server-manager.js +71 -0
  202. package/dist/server-manager.js.map +1 -0
  203. package/dist/server-response-extensions.d.ts +21 -0
  204. package/dist/server-response-extensions.d.ts.map +1 -0
  205. package/dist/server-response-extensions.js +15 -0
  206. package/dist/server-response-extensions.js.map +1 -0
  207. package/dist/server-response-extensions.spec.d.ts +2 -0
  208. package/dist/server-response-extensions.spec.d.ts.map +1 -0
  209. package/dist/server-response-extensions.spec.js +49 -0
  210. package/dist/server-response-extensions.spec.js.map +1 -0
  211. package/dist/utils.d.ts +24 -0
  212. package/dist/utils.d.ts.map +1 -0
  213. package/dist/utils.js +66 -0
  214. package/dist/utils.js.map +1 -0
  215. package/dist/validate.d.ts +18 -0
  216. package/dist/validate.d.ts.map +1 -0
  217. package/dist/validate.integration.schema.d.ts +69 -0
  218. package/dist/validate.integration.schema.d.ts.map +1 -0
  219. package/dist/validate.integration.schema.js +3 -0
  220. package/dist/validate.integration.schema.js.map +1 -0
  221. package/dist/validate.integration.spec.d.ts +13 -0
  222. package/dist/validate.integration.spec.d.ts.map +1 -0
  223. package/dist/validate.integration.spec.js +223 -0
  224. package/dist/validate.integration.spec.js.map +1 -0
  225. package/dist/validate.integration.spec.schema.json +749 -0
  226. package/dist/validate.js +49 -0
  227. package/dist/validate.js.map +1 -0
  228. package/package.json +56 -0
  229. package/src/actions/error-action.spec.ts +54 -0
  230. package/src/actions/error-action.ts +34 -0
  231. package/src/actions/get-current-user.spec.ts +23 -0
  232. package/src/actions/get-current-user.ts +15 -0
  233. package/src/actions/index.ts +6 -0
  234. package/src/actions/is-authenticated.spec.ts +18 -0
  235. package/src/actions/is-authenticated.ts +13 -0
  236. package/src/actions/login-action.spec.ts +41 -0
  237. package/src/actions/login.ts +26 -0
  238. package/src/actions/logout-action.spec.ts +27 -0
  239. package/src/actions/logout.ts +16 -0
  240. package/src/actions/not-found-action.spec.ts +17 -0
  241. package/src/actions/not-found-action.ts +13 -0
  242. package/src/add-cors-header.spec.ts +133 -0
  243. package/src/api-manager.ts +222 -0
  244. package/src/authenticate.spec.ts +78 -0
  245. package/src/authenticate.ts +22 -0
  246. package/src/authorize.spec.ts +69 -0
  247. package/src/authorize.ts +19 -0
  248. package/src/endpoint-generators/create-delete-endpoint.spec.ts +34 -0
  249. package/src/endpoint-generators/create-delete-endpoint.ts +25 -0
  250. package/src/endpoint-generators/create-get-collection-endpoint.spec.ts +164 -0
  251. package/src/endpoint-generators/create-get-collection-endpoint.ts +28 -0
  252. package/src/endpoint-generators/create-get-entity-endpoint.spec.ts +75 -0
  253. package/src/endpoint-generators/create-get-entity-endpoint.ts +29 -0
  254. package/src/endpoint-generators/create-patch-endpoint.spec.ts +36 -0
  255. package/src/endpoint-generators/create-patch-endpoint.ts +27 -0
  256. package/src/endpoint-generators/create-post-endpoint.spec.ts +32 -0
  257. package/src/endpoint-generators/create-post-endpoint.ts +30 -0
  258. package/src/endpoint-generators/index.ts +5 -0
  259. package/src/endpoint-generators/utils.ts +34 -0
  260. package/src/http-authentication-settings.ts +23 -0
  261. package/src/http-user-context.spec.ts +299 -0
  262. package/src/http-user-context.ts +160 -0
  263. package/src/incoming-message-extensions.spec.ts +41 -0
  264. package/src/incoming-message-extensions.ts +19 -0
  265. package/src/index.ts +16 -0
  266. package/src/injector-extensions.spec.ts +19 -0
  267. package/src/injector-extensions.ts +35 -0
  268. package/src/models/cors-options.ts +21 -0
  269. package/src/models/default-session.ts +14 -0
  270. package/src/request-action-implementation.ts +70 -0
  271. package/src/rest-service.integration.spec.ts +166 -0
  272. package/src/rest.integration.test.ts +112 -0
  273. package/src/schema-validator/index.ts +2 -0
  274. package/src/schema-validator/schema-validation-error.ts +11 -0
  275. package/src/schema-validator/schema-validator.test.ts +72 -0
  276. package/src/schema-validator/schema-validator.ts +31 -0
  277. package/src/schema-validator/validate-examples.ts +38 -0
  278. package/src/server-manager.ts +88 -0
  279. package/src/server-response-extensions.spec.ts +53 -0
  280. package/src/server-response-extensions.ts +30 -0
  281. package/src/utils.ts +65 -0
  282. package/src/validate.integration.schema.ts +50 -0
  283. package/src/validate.integration.spec.schema.json +779 -0
  284. package/src/validate.integration.spec.ts +218 -0
  285. package/src/validate.ts +60 -0
@@ -0,0 +1,2 @@
1
+ export * from './schema-validator'
2
+ export * from './schema-validation-error'
@@ -0,0 +1,11 @@
1
+ import { RequestError } from '@furystack/rest'
2
+ import { ErrorObject } from 'ajv'
3
+
4
+ /**
5
+ * Custom Error class for Schema Validation Errors
6
+ */
7
+ export class SchemaValidationError extends RequestError {
8
+ constructor(public readonly errors: ErrorObject[]) {
9
+ super('Schema Validation failed', 400)
10
+ }
11
+ }
@@ -0,0 +1,72 @@
1
+ import { SchemaValidator } from './schema-validator'
2
+ import { exampleSchema, BodyParameters, Language } from './validate-examples'
3
+ import { SchemaValidationError } from './schema-validation-error'
4
+
5
+ describe('ValidateSchema', () => {
6
+ describe('String Literal checks', () => {
7
+ it('Should pass on valid string literal parameters', () => {
8
+ expect(new SchemaValidator(exampleSchema).isValid<Language>('en', { schemaName: 'Language' })).toBeTruthy()
9
+ })
10
+
11
+ it('Should throw a ValidationError on Age parameters', () => {
12
+ try {
13
+ const result = new SchemaValidator(exampleSchema).isValid<Language>('foo', { schemaName: 'Language' })
14
+ expect(result).toBeFalsy() // should not hit
15
+ } catch (error) {
16
+ expect(error).toBeInstanceOf(SchemaValidationError)
17
+ const { errors } = error as SchemaValidationError
18
+ expect(errors).toHaveLength(1)
19
+ expect(errors[0].message).toEqual('must be equal to one of the allowed values')
20
+ }
21
+ })
22
+ })
23
+
24
+ describe('Object checks', () => {
25
+ it('Should fail when string is passed instead of object', () => {
26
+ try {
27
+ new SchemaValidator(exampleSchema).isValid<BodyParameters>('foo', { schemaName: 'BodyParameters' })
28
+ } catch (error) {
29
+ expect(error).toBeInstanceOf(SchemaValidationError)
30
+ const { errors } = error as SchemaValidationError
31
+ expect(errors).toHaveLength(1)
32
+ expect(errors[0].message).toEqual('must be object')
33
+ }
34
+ })
35
+
36
+ it('Should fail when an additional property is present', () => {
37
+ try {
38
+ new SchemaValidator(exampleSchema).isValid<BodyParameters>(
39
+ { age: '3', foo: 2 },
40
+ { schemaName: 'BodyParameters' },
41
+ )
42
+ } catch (error) {
43
+ expect(error).toBeInstanceOf(SchemaValidationError)
44
+ const { errors } = error as SchemaValidationError
45
+ expect(errors).toHaveLength(1)
46
+ expect(errors[0].message).toEqual('must NOT have additional properties')
47
+ }
48
+ })
49
+
50
+ it('Should pass with empty objects', () => {
51
+ expect(
52
+ new SchemaValidator(exampleSchema).isValid<BodyParameters>({}, { schemaName: 'BodyParameters' }),
53
+ ).toBeTruthy()
54
+ })
55
+
56
+ it('Should pass with valid partial objects', () => {
57
+ expect(
58
+ new SchemaValidator(exampleSchema).isValid<BodyParameters>({ age: '3' }, { schemaName: 'BodyParameters' }),
59
+ ).toBeTruthy()
60
+ })
61
+ })
62
+
63
+ describe('Multiple checks per validator instance', () => {
64
+ it('Should pass with multiple types from schema per validator instance', () => {
65
+ const validator = new SchemaValidator(exampleSchema)
66
+ expect(validator.isValid<Language>('en', { schemaName: 'Language' })).toBeTruthy()
67
+ expect(
68
+ new SchemaValidator(exampleSchema).isValid<BodyParameters>({}, { schemaName: 'BodyParameters' }),
69
+ ).toBeTruthy()
70
+ })
71
+ })
72
+ })
@@ -0,0 +1,31 @@
1
+ import Ajv, { ErrorObject, Options } from 'ajv'
2
+ import useFormats from 'ajv-formats'
3
+ import { SchemaValidationError } from './schema-validation-error'
4
+
5
+ export class SchemaValidator<TSchema extends { definitions: {} }> {
6
+ private readonly ajv = new Ajv({
7
+ allErrors: true,
8
+ ...this.ajvOptions,
9
+ })
10
+ constructor(private readonly schema: TSchema, private readonly ajvOptions?: Options) {
11
+ useFormats(this.ajv)
12
+ }
13
+
14
+ /**
15
+ * @param data The object to validate
16
+ * @param options Options for the schema validation
17
+ * @param options.schemaName The name of the type in the Schema Definitions
18
+ * @throws SchemaValidationError when the validation has been failed
19
+ * @returns true in case of validation success
20
+ */
21
+ public isValid<T>(data: any, options: { schemaName: keyof TSchema['definitions'] }): data is T {
22
+ const schema = { ...this.schema, $ref: `#/definitions/${options.schemaName}` }
23
+ const validatorFn = this.ajv.compile(schema)
24
+ const isValid = validatorFn(data)
25
+ if (!isValid) {
26
+ throw new SchemaValidationError(validatorFn.errors as ErrorObject[])
27
+ }
28
+
29
+ return true
30
+ }
31
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * example string literal type - needed for Typescipt Generics
3
+ */
4
+ export type Language = 'en' | 'hu' | 'de' | 'all'
5
+
6
+ /**
7
+ * example object type - needed for TypeScipt Generics
8
+ */
9
+ export interface BodyParameters {
10
+ gender?: 'MALE' | 'FEMALE'
11
+ age?: string
12
+ }
13
+
14
+ /**
15
+ * Example Schema Definition - can be generated from the example types above with ts-json-schema-generator
16
+ */
17
+ export const exampleSchema = {
18
+ $schema: 'http://json-schema.org/draft-07/schema#',
19
+ definitions: {
20
+ BodyParameters: {
21
+ additionalProperties: false,
22
+ properties: {
23
+ age: {
24
+ type: 'string',
25
+ },
26
+ gender: {
27
+ enum: ['MALE', 'FEMALE'],
28
+ type: 'string',
29
+ },
30
+ },
31
+ type: 'object',
32
+ },
33
+ Language: {
34
+ enum: ['en', 'hu', 'de', 'all'],
35
+ type: 'string',
36
+ },
37
+ },
38
+ }
@@ -0,0 +1,88 @@
1
+ import { Injectable } from '@furystack/inject'
2
+ import { Disposable } from '@furystack/utils'
3
+ import { Server, createServer } from 'http'
4
+ import Semaphore from 'semaphore-async-await'
5
+ import { IncomingMessage, ServerResponse } from 'http'
6
+ import { Socket } from 'net'
7
+
8
+ export interface ServerOptions {
9
+ hostName?: string
10
+ port: number
11
+ }
12
+
13
+ export interface OnRequest {
14
+ req: IncomingMessage
15
+ res: ServerResponse
16
+ }
17
+
18
+ export interface ServerRecord {
19
+ server: Server
20
+ apis: Array<{ shouldExec: (options: OnRequest) => boolean; onRequest: (options: OnRequest) => void }>
21
+ }
22
+
23
+ @Injectable({ lifetime: 'singleton' })
24
+ export class ServerManager implements Disposable {
25
+ public static DEFAULT_HOST = 'localhost'
26
+
27
+ public servers = new Map<string, ServerRecord>()
28
+ private openedSockets = new Set<Socket>()
29
+
30
+ private readonly listenLock = new Semaphore(1)
31
+
32
+ private getHostUrl = (options: ServerOptions) =>
33
+ `http://${options.hostName || ServerManager.DEFAULT_HOST}:${options.port}`
34
+
35
+ private onConnection = (socket: Socket) => {
36
+ this.openedSockets.add(socket)
37
+ socket.once('close', () => this.openedSockets.delete(socket))
38
+ }
39
+
40
+ public async dispose() {
41
+ try {
42
+ await this.listenLock.waitFor(5000)
43
+ } finally {
44
+ this.openedSockets.forEach((s) => s.destroy())
45
+ await Promise.allSettled(
46
+ [...this.servers.values()].map(
47
+ (s) =>
48
+ new Promise<void>((resolve, reject) => {
49
+ s.server.close((err) => (err ? reject(err) : resolve()))
50
+ s.server.off('connection', this.onConnection)
51
+ }),
52
+ ),
53
+ )
54
+ this.servers.clear()
55
+ this.listenLock.release()
56
+ }
57
+ }
58
+
59
+ public async getOrCreate(options: ServerOptions): Promise<ServerRecord> {
60
+ const url = this.getHostUrl(options)
61
+ if (!this.servers.has(url)) {
62
+ await this.listenLock.acquire()
63
+ try {
64
+ if (!this.servers.has(url)) {
65
+ await new Promise<void>((resolve, reject) => {
66
+ const apis: ServerRecord['apis'] = []
67
+ const server = createServer((req, res) => {
68
+ const apiMatch = apis.find((api) => api.shouldExec({ req, res }))
69
+ if (apiMatch) {
70
+ apiMatch.onRequest({ req, res })
71
+ } else {
72
+ res.destroy()
73
+ }
74
+ })
75
+ server.on('connection', this.onConnection)
76
+ server.on('listening', () => resolve())
77
+ server.on('error', (err) => reject(err))
78
+ server.listen(options.port, options.hostName)
79
+ this.servers.set(url, { server, apis })
80
+ })
81
+ }
82
+ } finally {
83
+ this.listenLock.release()
84
+ }
85
+ }
86
+ return this.servers.get(url) as ServerRecord
87
+ }
88
+ }
@@ -0,0 +1,53 @@
1
+ import { Socket } from 'net'
2
+ import { IncomingMessage, ServerResponse } from 'http'
3
+ import './server-response-extensions'
4
+ import { BypassResult, JsonResult, PlainTextResult } from './request-action-implementation'
5
+
6
+ describe('ServerResponse extensions', () => {
7
+ describe('sendActionResult', () => {
8
+ it('Should be extended', () => {
9
+ const socket = new Socket()
10
+ const msg = new ServerResponse(new IncomingMessage(socket))
11
+ expect(typeof msg.sendActionResult).toBe('function')
12
+ })
13
+
14
+ it('Should send the JSON response with the correct Content Type header and default status', (done) => {
15
+ const jsonValue = { value: Math.random() }
16
+ const socket = new Socket()
17
+ const msg = new ServerResponse(new IncomingMessage(socket))
18
+ msg.writeHead = jest.fn()
19
+ msg.end = (chunk?: any) => {
20
+ expect(chunk).toBe(JSON.stringify(jsonValue))
21
+ expect(msg.writeHead).toBeCalledWith(200, { 'Content-Type': 'application/json' })
22
+ done()
23
+ }
24
+
25
+ msg.sendActionResult(JsonResult(jsonValue))
26
+ })
27
+
28
+ it('Should send the plain TEXT response with the correct Content Type header and default status', (done) => {
29
+ const textValue = `${Math.random()}`
30
+ const socket = new Socket()
31
+ const msg = new ServerResponse(new IncomingMessage(socket))
32
+ msg.writeHead = jest.fn()
33
+ msg.end = (chunk?: any) => {
34
+ expect(chunk).toBe(textValue)
35
+ expect(msg.writeHead).toBeCalledWith(200, { 'Content-Type': 'plain/text' })
36
+ done()
37
+ }
38
+
39
+ msg.sendActionResult(PlainTextResult(textValue))
40
+ })
41
+
42
+ it('Should skip sending on BypassResult', () => {
43
+ const socket = new Socket()
44
+ const msg = new ServerResponse(new IncomingMessage(socket))
45
+ msg.writeHead = jest.fn()
46
+ msg.end = jest.fn()
47
+
48
+ msg.sendActionResult(BypassResult())
49
+ expect(msg.writeHead).not.toBeCalled()
50
+ expect(msg.end).not.toBeCalled()
51
+ })
52
+ })
53
+ })
@@ -0,0 +1,30 @@
1
+ import http from 'http'
2
+ import { ActionResult } from './request-action-implementation'
3
+
4
+ export interface SendJsonOptions<T> {
5
+ statusCode?: number
6
+ json: T
7
+ headers?: { [K: string]: string }
8
+ }
9
+ export interface SendPlainTextOptions {
10
+ statusCode?: number
11
+ text: string
12
+ headers?: { [K: string]: string }
13
+ }
14
+
15
+ declare module 'http' {
16
+ export interface ServerResponse {
17
+ sendActionResult: <T>(result: ActionResult<T>) => void
18
+ }
19
+ }
20
+
21
+ http.ServerResponse.prototype.sendActionResult = function <T>(options: ActionResult<T>) {
22
+ if (typeof options.chunk === 'object') {
23
+ options.chunk = JSON.stringify(options.chunk) as any
24
+ }
25
+ if (typeof options.chunk === 'string' && options.chunk === 'BypassResult') {
26
+ return
27
+ }
28
+ this.writeHead(options.statusCode, options.headers)
29
+ this.end(options.chunk)
30
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,65 @@
1
+ import { IncomingMessage, ServerResponse } from 'http'
2
+ import { Injectable } from '@furystack/inject'
3
+ import { CorsOptions } from './models/cors-options'
4
+
5
+ /**
6
+ * A collection of various HTTP API related utilities
7
+ */
8
+ @Injectable({ lifetime: 'transient' })
9
+ export class Utils {
10
+ public async readPostBodyRaw(incomingMessage: IncomingMessage) {
11
+ let body = ''
12
+ await new Promise<void>((resolve, reject) => {
13
+ incomingMessage.on('readable', () => {
14
+ const data = incomingMessage.read()
15
+ if (data) {
16
+ body += data
17
+ }
18
+ })
19
+ incomingMessage.on('end', () => {
20
+ resolve()
21
+ })
22
+ incomingMessage.on('error', (err) => {
23
+ reject(err)
24
+ })
25
+ })
26
+ return body
27
+ }
28
+
29
+ /**
30
+ * Reads the post's body and returns a promise with a parsed value
31
+ *
32
+ * @param incomingMessage The incoming message instance
33
+ * @returns the parsed object from the post body
34
+ */
35
+ public async readPostBody<T>(incomingMessage: IncomingMessage): Promise<T> {
36
+ const body = await this.readPostBodyRaw(incomingMessage)
37
+ return JSON.parse(body) as T
38
+ }
39
+
40
+ /**
41
+ * Adds the specified CORS headers to the response
42
+ *
43
+ * @param options The CORS Options object
44
+ * @param incomingMessage The incoming message instance
45
+ * @param serverResponse The outgoing response instance
46
+ */
47
+ public addCorsHeaders(options: CorsOptions, incomingMessage: IncomingMessage, serverResponse: ServerResponse) {
48
+ if (
49
+ incomingMessage.headers &&
50
+ incomingMessage.headers.origin !== incomingMessage.headers.host &&
51
+ options.origins.some((origin) => origin === incomingMessage.headers.origin)
52
+ ) {
53
+ serverResponse.setHeader('Access-Control-Allow-Origin', incomingMessage.headers.origin as string)
54
+ if (options.credentials) {
55
+ serverResponse.setHeader('Access-Control-Allow-Credentials', 'true')
56
+ }
57
+ if (options.headers && options.headers.length) {
58
+ serverResponse.setHeader('Access-Control-Allow-Headers', options.headers.join(', '))
59
+ }
60
+ if (options.methods && options.methods.length) {
61
+ serverResponse.setHeader('Access-Control-Allow-Methods', options.methods.join(', '))
62
+ }
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,50 @@
1
+ import {
2
+ DeleteEndpoint,
3
+ GetCollectionEndpoint,
4
+ GetEntityEndpoint,
5
+ PatchEndpoint,
6
+ PostEndpoint,
7
+ RestApi,
8
+ } from '@furystack/rest'
9
+
10
+ export interface Mock {
11
+ id: string
12
+ value: string
13
+ }
14
+
15
+ export interface ValidateQuery {
16
+ query: { foo: string; bar: number; baz: boolean }
17
+ result: { foo: string; bar: number; baz: boolean }
18
+ }
19
+ export interface ValidateUrl {
20
+ url: { id: number }
21
+ result: { id: number }
22
+ }
23
+ export interface ValidateHeaders {
24
+ headers: { foo: string; bar: number; baz: boolean }
25
+ result: { foo: string; bar: number; baz: boolean }
26
+ }
27
+ export interface ValidateBody {
28
+ body: { foo: string; bar: number; baz: boolean }
29
+ result: { foo: string; bar: number; baz: boolean }
30
+ }
31
+
32
+ export interface ValidationApi extends RestApi {
33
+ GET: {
34
+ '/validate-query': ValidateQuery
35
+ '/validate-url/:id': ValidateUrl
36
+ '/validate-headers': ValidateHeaders
37
+ '/mock': GetCollectionEndpoint<Mock>
38
+ '/mock/:id': GetEntityEndpoint<Mock, 'id'>
39
+ }
40
+ POST: {
41
+ '/validate-body': ValidateBody
42
+ '/mock': PostEndpoint<Mock, 'id'>
43
+ }
44
+ PATCH: {
45
+ '/mock/:id': PatchEndpoint<Mock, 'id'>
46
+ }
47
+ DELETE: {
48
+ '/mock/:id': DeleteEndpoint<Mock, 'id'>
49
+ }
50
+ }