@schafevormfenster/rest-commons 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/CONTRIBUTING.md +1190 -0
  2. package/README.md +275 -0
  3. package/bin/setup.js +10 -0
  4. package/dist/api-schemas/error.schema.d.ts +20 -0
  5. package/dist/api-schemas/error.schema.d.ts.map +1 -0
  6. package/dist/api-schemas/error.schema.js +17 -0
  7. package/dist/api-schemas/health.schema.d.ts +497 -0
  8. package/dist/api-schemas/health.schema.d.ts.map +1 -0
  9. package/dist/api-schemas/health.schema.js +33 -0
  10. package/dist/api-schemas/okay.schema.d.ts +13 -0
  11. package/dist/api-schemas/okay.schema.d.ts.map +1 -0
  12. package/dist/api-schemas/okay.schema.js +5 -0
  13. package/dist/api-schemas/paginated-results.schema.d.ts +59 -0
  14. package/dist/api-schemas/paginated-results.schema.d.ts.map +1 -0
  15. package/dist/api-schemas/paginated-results.schema.js +10 -0
  16. package/dist/api-schemas/partial-results.schema.d.ts +30 -0
  17. package/dist/api-schemas/partial-results.schema.d.ts.map +1 -0
  18. package/dist/api-schemas/partial-results.schema.js +10 -0
  19. package/dist/api-schemas/result.schema.d.ts +17 -0
  20. package/dist/api-schemas/result.schema.d.ts.map +1 -0
  21. package/dist/api-schemas/result.schema.js +5 -0
  22. package/dist/api-schemas/results.schema.d.ts +21 -0
  23. package/dist/api-schemas/results.schema.d.ts.map +1 -0
  24. package/dist/api-schemas/results.schema.js +5 -0
  25. package/dist/helpers/correlation/get-correlation-id.d.ts +7 -0
  26. package/dist/helpers/correlation/get-correlation-id.d.ts.map +1 -0
  27. package/dist/helpers/correlation/get-correlation-id.js +16 -0
  28. package/dist/helpers/correlation/get-header.d.ts +7 -0
  29. package/dist/helpers/correlation/get-header.d.ts.map +1 -0
  30. package/dist/helpers/correlation/get-header.js +11 -0
  31. package/dist/helpers/detect-mime-type.d.ts +11 -0
  32. package/dist/helpers/detect-mime-type.d.ts.map +1 -0
  33. package/dist/helpers/detect-mime-type.js +40 -0
  34. package/dist/helpers/detect-suspicious-patterns.d.ts +8 -0
  35. package/dist/helpers/detect-suspicious-patterns.d.ts.map +1 -0
  36. package/dist/helpers/detect-suspicious-patterns.js +55 -0
  37. package/dist/helpers/eventify-constants.types.d.ts +32 -0
  38. package/dist/helpers/eventify-constants.types.d.ts.map +1 -0
  39. package/dist/helpers/eventify-constants.types.js +40 -0
  40. package/dist/helpers/hash-binary.d.ts +21 -0
  41. package/dist/helpers/hash-binary.d.ts.map +1 -0
  42. package/dist/helpers/hash-binary.js +28 -0
  43. package/dist/helpers/mime-types/detect-image-mime-type.d.ts +5 -0
  44. package/dist/helpers/mime-types/detect-image-mime-type.d.ts.map +1 -0
  45. package/dist/helpers/mime-types/detect-image-mime-type.js +41 -0
  46. package/dist/helpers/mime-types/detect-ole-mime-type.d.ts +6 -0
  47. package/dist/helpers/mime-types/detect-ole-mime-type.d.ts.map +1 -0
  48. package/dist/helpers/mime-types/detect-ole-mime-type.js +34 -0
  49. package/dist/helpers/mime-types/detect-pdf-mime-type.d.ts +5 -0
  50. package/dist/helpers/mime-types/detect-pdf-mime-type.d.ts.map +1 -0
  51. package/dist/helpers/mime-types/detect-pdf-mime-type.js +13 -0
  52. package/dist/helpers/mime-types/detect-zip-mime-type.d.ts +6 -0
  53. package/dist/helpers/mime-types/detect-zip-mime-type.d.ts.map +1 -0
  54. package/dist/helpers/mime-types/detect-zip-mime-type.js +23 -0
  55. package/dist/helpers/parameter-validation.d.ts +6 -0
  56. package/dist/helpers/parameter-validation.d.ts.map +1 -0
  57. package/dist/helpers/parameter-validation.js +19 -0
  58. package/dist/helpers/parameter-validation.types.d.ts +16 -0
  59. package/dist/helpers/parameter-validation.types.d.ts.map +1 -0
  60. package/dist/helpers/parameter-validation.types.js +38 -0
  61. package/dist/helpers/response-headers/build-api-unauthorized-headers.d.ts +6 -0
  62. package/dist/helpers/response-headers/build-api-unauthorized-headers.d.ts.map +1 -0
  63. package/dist/helpers/response-headers/build-api-unauthorized-headers.js +23 -0
  64. package/dist/helpers/response-headers/environment.types.d.ts +2 -0
  65. package/dist/helpers/response-headers/environment.types.d.ts.map +1 -0
  66. package/dist/helpers/response-headers/environment.types.js +1 -0
  67. package/dist/helpers/response-headers/resolve-environment.d.ts +8 -0
  68. package/dist/helpers/response-headers/resolve-environment.d.ts.map +1 -0
  69. package/dist/helpers/response-headers/resolve-environment.js +18 -0
  70. package/dist/helpers/slugify.d.ts +15 -0
  71. package/dist/helpers/slugify.d.ts.map +1 -0
  72. package/dist/helpers/slugify.js +32 -0
  73. package/dist/index.d.ts +36 -0
  74. package/dist/index.d.ts.map +1 -0
  75. package/dist/index.js +41 -0
  76. package/dist/normalization/normalize-list.d.ts +11 -0
  77. package/dist/normalization/normalize-list.d.ts.map +1 -0
  78. package/dist/normalization/normalize-list.js +19 -0
  79. package/dist/normalization/normalize-location.d.ts +16 -0
  80. package/dist/normalization/normalize-location.d.ts.map +1 -0
  81. package/dist/normalization/normalize-location.js +26 -0
  82. package/dist/primitives/coordinate-precision.d.ts +10 -0
  83. package/dist/primitives/coordinate-precision.d.ts.map +1 -0
  84. package/dist/primitives/coordinate-precision.js +27 -0
  85. package/dist/primitives/geo-point.schema.d.ts +8 -0
  86. package/dist/primitives/geo-point.schema.d.ts.map +1 -0
  87. package/dist/primitives/geo-point.schema.js +10 -0
  88. package/dist/primitives/geoname-id.schema.d.ts +8 -0
  89. package/dist/primitives/geoname-id.schema.d.ts.map +1 -0
  90. package/dist/primitives/geoname-id.schema.js +9 -0
  91. package/dist/primitives/international-zip.schema.d.ts +76 -0
  92. package/dist/primitives/international-zip.schema.d.ts.map +1 -0
  93. package/dist/primitives/international-zip.schema.js +81 -0
  94. package/dist/primitives/latitude.schema.d.ts +9 -0
  95. package/dist/primitives/latitude.schema.d.ts.map +1 -0
  96. package/dist/primitives/latitude.schema.js +13 -0
  97. package/dist/primitives/location.schema.d.ts +8 -0
  98. package/dist/primitives/location.schema.d.ts.map +1 -0
  99. package/dist/primitives/location.schema.js +15 -0
  100. package/dist/primitives/longitude.schema.d.ts +9 -0
  101. package/dist/primitives/longitude.schema.d.ts.map +1 -0
  102. package/dist/primitives/longitude.schema.js +13 -0
  103. package/dist/primitives/numeric-id.schema.d.ts +8 -0
  104. package/dist/primitives/numeric-id.schema.d.ts.map +1 -0
  105. package/dist/primitives/numeric-id.schema.js +10 -0
  106. package/dist/primitives/slug.schema.d.ts +17 -0
  107. package/dist/primitives/slug.schema.d.ts.map +1 -0
  108. package/dist/primitives/slug.schema.js +30 -0
  109. package/dist/primitives/uuid.schema.d.ts +8 -0
  110. package/dist/primitives/uuid.schema.d.ts.map +1 -0
  111. package/dist/primitives/uuid.schema.js +9 -0
  112. package/dist/primitives/wikidata-id.schema.d.ts +9 -0
  113. package/dist/primitives/wikidata-id.schema.d.ts.map +1 -0
  114. package/dist/primitives/wikidata-id.schema.js +10 -0
  115. package/dist/time/boundary-enforcement.d.ts +11 -0
  116. package/dist/time/boundary-enforcement.d.ts.map +1 -0
  117. package/dist/time/boundary-enforcement.js +43 -0
  118. package/dist/time/bounded-time.schema.d.ts +31 -0
  119. package/dist/time/bounded-time.schema.d.ts.map +1 -0
  120. package/dist/time/bounded-time.schema.js +77 -0
  121. package/dist/time/flexible-time-parser.d.ts +12 -0
  122. package/dist/time/flexible-time-parser.d.ts.map +1 -0
  123. package/dist/time/flexible-time-parser.js +94 -0
  124. package/dist/time/flexible-time.schema.d.ts +31 -0
  125. package/dist/time/flexible-time.schema.d.ts.map +1 -0
  126. package/dist/time/flexible-time.schema.js +31 -0
  127. package/dist/time/get-week-end.d.ts +10 -0
  128. package/dist/time/get-week-end.d.ts.map +1 -0
  129. package/dist/time/get-week-end.js +25 -0
  130. package/dist/time/get-week-start.d.ts +10 -0
  131. package/dist/time/get-week-start.d.ts.map +1 -0
  132. package/dist/time/get-week-start.js +25 -0
  133. package/dist/time/is-relative-time.d.ts +8 -0
  134. package/dist/time/is-relative-time.d.ts.map +1 -0
  135. package/dist/time/is-relative-time.js +9 -0
  136. package/dist/time/iso8601.schema.d.ts +14 -0
  137. package/dist/time/iso8601.schema.d.ts.map +1 -0
  138. package/dist/time/iso8601.schema.js +17 -0
  139. package/dist/time/iso8601.types.d.ts +6 -0
  140. package/dist/time/iso8601.types.d.ts.map +1 -0
  141. package/dist/time/iso8601.types.js +11 -0
  142. package/dist/time/parse-relative-time.d.ts +9 -0
  143. package/dist/time/parse-relative-time.d.ts.map +1 -0
  144. package/dist/time/parse-relative-time.js +36 -0
  145. package/dist/time/relative-time.schema.d.ts +23 -0
  146. package/dist/time/relative-time.schema.d.ts.map +1 -0
  147. package/dist/time/relative-time.schema.js +25 -0
  148. package/dist/time/since-parameter.schema.d.ts +8 -0
  149. package/dist/time/since-parameter.schema.d.ts.map +1 -0
  150. package/dist/time/since-parameter.schema.js +56 -0
  151. package/dist/time/time-helpers.d.ts +19 -0
  152. package/dist/time/time-helpers.d.ts.map +1 -0
  153. package/dist/time/time-helpers.js +56 -0
  154. package/dist/time/time-schemas.d.ts +20 -0
  155. package/dist/time/time-schemas.d.ts.map +1 -0
  156. package/dist/time/time-schemas.js +25 -0
  157. package/dist/time/timezone.types.d.ts +17 -0
  158. package/dist/time/timezone.types.d.ts.map +1 -0
  159. package/dist/time/timezone.types.js +15 -0
  160. package/dist/validation/zod-error-handler.d.ts +3 -0
  161. package/dist/validation/zod-error-handler.d.ts.map +1 -0
  162. package/dist/validation/zod-error-handler.js +189 -0
  163. package/dist/validation/zod-utils.d.ts +9 -0
  164. package/dist/validation/zod-utils.d.ts.map +1 -0
  165. package/dist/validation/zod-utils.js +23 -0
  166. package/eslint.config.mjs +16 -0
  167. package/package.json +44 -0
  168. package/src/api-schemas/error.schema.test.ts +27 -0
  169. package/src/api-schemas/error.schema.ts +23 -0
  170. package/src/api-schemas/health.schema.test.ts +104 -0
  171. package/src/api-schemas/health.schema.ts +63 -0
  172. package/src/api-schemas/okay.schema.test.ts +15 -0
  173. package/src/api-schemas/okay.schema.ts +8 -0
  174. package/src/api-schemas/paginated-results.schema.ts +17 -0
  175. package/src/api-schemas/partial-results.schema.ts +13 -0
  176. package/src/api-schemas/result.schema.test.ts +19 -0
  177. package/src/api-schemas/result.schema.ts +9 -0
  178. package/src/api-schemas/results.schema.test.ts +15 -0
  179. package/src/api-schemas/results.schema.ts +9 -0
  180. package/src/helpers/correlation/get-correlation-id.test.ts +126 -0
  181. package/src/helpers/correlation/get-correlation-id.ts +22 -0
  182. package/src/helpers/correlation/get-header.test.ts +179 -0
  183. package/src/helpers/correlation/get-header.ts +21 -0
  184. package/src/helpers/detect-mime-type.test.ts +100 -0
  185. package/src/helpers/detect-mime-type.ts +46 -0
  186. package/src/helpers/detect-suspicious-patterns.test.ts +45 -0
  187. package/src/helpers/detect-suspicious-patterns.ts +57 -0
  188. package/src/helpers/eventify-constants.test.ts +52 -0
  189. package/src/helpers/eventify-constants.types.test.ts +52 -0
  190. package/src/helpers/eventify-constants.types.ts +51 -0
  191. package/src/helpers/hash-binary.test.ts +60 -0
  192. package/src/helpers/hash-binary.ts +30 -0
  193. package/src/helpers/mime-types/detect-image-mime-type.test.ts +73 -0
  194. package/src/helpers/mime-types/detect-image-mime-type.ts +50 -0
  195. package/src/helpers/mime-types/detect-ole-mime-type.test.ts +86 -0
  196. package/src/helpers/mime-types/detect-ole-mime-type.ts +44 -0
  197. package/src/helpers/mime-types/detect-pdf-mime-type.test.ts +39 -0
  198. package/src/helpers/mime-types/detect-pdf-mime-type.ts +15 -0
  199. package/src/helpers/mime-types/detect-zip-mime-type.test.ts +88 -0
  200. package/src/helpers/mime-types/detect-zip-mime-type.ts +28 -0
  201. package/src/helpers/parameter-validation.test.ts +35 -0
  202. package/src/helpers/parameter-validation.ts +32 -0
  203. package/src/helpers/process-eventify-request.ts +146 -0
  204. package/src/helpers/response-headers/build-api-unauthorized-headers.ts +30 -0
  205. package/src/helpers/response-headers/environment.types.ts +1 -0
  206. package/src/helpers/response-headers/resolve-environment.ts +17 -0
  207. package/src/helpers/slugify.test.ts +77 -0
  208. package/src/helpers/slugify.ts +34 -0
  209. package/src/index.ts +46 -0
  210. package/src/normalization/normalize-list.test.ts +43 -0
  211. package/src/normalization/normalize-list.ts +21 -0
  212. package/src/normalization/normalize-location.test.ts +91 -0
  213. package/src/normalization/normalize-location.ts +29 -0
  214. package/src/primitives/coordinate-precision.test.ts +46 -0
  215. package/src/primitives/coordinate-precision.ts +30 -0
  216. package/src/primitives/geo-point.schema.test.ts +70 -0
  217. package/src/primitives/geo-point.schema.ts +14 -0
  218. package/src/primitives/geoname-id.schema.test.ts +60 -0
  219. package/src/primitives/geoname-id.schema.ts +12 -0
  220. package/src/primitives/international-zip.schema.test.ts +212 -0
  221. package/src/primitives/international-zip.schema.ts +103 -0
  222. package/src/primitives/latitude.schema.test.ts +77 -0
  223. package/src/primitives/latitude.schema.ts +20 -0
  224. package/src/primitives/location.schema.test.ts +21 -0
  225. package/src/primitives/location.schema.ts +22 -0
  226. package/src/primitives/longitude.schema.test.ts +77 -0
  227. package/src/primitives/longitude.schema.ts +20 -0
  228. package/src/primitives/numeric-id.schema.test.ts +32 -0
  229. package/src/primitives/numeric-id.schema.ts +13 -0
  230. package/src/primitives/slug.schema.test.ts +101 -0
  231. package/src/primitives/slug.schema.ts +41 -0
  232. package/src/primitives/uuid.schema.test.ts +45 -0
  233. package/src/primitives/uuid.schema.ts +12 -0
  234. package/src/primitives/wikidata-id.schema.test.ts +51 -0
  235. package/src/primitives/wikidata-id.schema.ts +16 -0
  236. package/src/time/README.md +220 -0
  237. package/src/time/boundary-enforcement.test.ts +130 -0
  238. package/src/time/boundary-enforcement.ts +59 -0
  239. package/src/time/bounded-time.schema.test.ts +294 -0
  240. package/src/time/bounded-time.schema.ts +111 -0
  241. package/src/time/flexible-time-parser.test.ts +586 -0
  242. package/src/time/flexible-time-parser.ts +122 -0
  243. package/src/time/flexible-time.schema.test.ts +243 -0
  244. package/src/time/flexible-time.schema.ts +43 -0
  245. package/src/time/is-relative-time.test.ts +23 -0
  246. package/src/time/is-relative-time.ts +9 -0
  247. package/src/time/iso8601.schema.ts +29 -0
  248. package/src/time/iso8601.types.test.ts +112 -0
  249. package/src/time/iso8601.types.ts +21 -0
  250. package/src/time/parse-relative-time.test.ts +49 -0
  251. package/src/time/parse-relative-time.ts +50 -0
  252. package/src/time/relative-time.schema.test.ts +23 -0
  253. package/src/time/relative-time.schema.ts +38 -0
  254. package/src/time/since-parameter.schema.test.ts +59 -0
  255. package/src/time/since-parameter.schema.ts +69 -0
  256. package/src/time/time-helpers.test.ts +263 -0
  257. package/src/time/time-helpers.ts +78 -0
  258. package/src/time/time-schemas.test.ts +181 -0
  259. package/src/time/time-schemas.ts +42 -0
  260. package/src/time/time.schema.test.ts +237 -0
  261. package/src/time/timezone-independence.test.ts +188 -0
  262. package/src/time/timezone.types.test.ts +55 -0
  263. package/src/time/timezone.types.ts +22 -0
  264. package/tsconfig.json +26 -0
@@ -0,0 +1,212 @@
1
+ import { describe, it, expect } from "vitest";
2
+
3
+ import {
4
+ InternationalZipSchema,
5
+ ZipSchemaByCountry,
6
+ getZipSchemaForCountry,
7
+ } from "./international-zip.schema";
8
+
9
+ describe("InternationalZipSchema", () => {
10
+ it("should accept various international postal code formats", () => {
11
+ expect(InternationalZipSchema.parse("12345")).toBe("12345");
12
+ expect(InternationalZipSchema.parse("12-345")).toBe("12-345");
13
+ expect(InternationalZipSchema.parse("1234 AB")).toBe("1234 AB");
14
+ expect(InternationalZipSchema.parse("SW1A 1AA")).toBe("SW1A 1AA");
15
+ });
16
+
17
+ it("should reject codes that are too short", () => {
18
+ expect(() => InternationalZipSchema.parse("12")).toThrow(
19
+ "Postal code must be at least 3 characters long"
20
+ );
21
+ });
22
+
23
+ it("should reject codes that are too long", () => {
24
+ expect(() => InternationalZipSchema.parse("1234567890123")).toThrow(
25
+ "Postal code must not exceed 12 characters"
26
+ );
27
+ });
28
+
29
+ it("should reject codes with invalid characters", () => {
30
+ expect(() => InternationalZipSchema.parse("12345@")).toThrow(
31
+ "Postal code must contain only alphanumeric characters, spaces, and hyphens"
32
+ );
33
+ expect(() => InternationalZipSchema.parse("ABC.DEF")).toThrow(
34
+ "Postal code must contain only alphanumeric characters, spaces, and hyphens"
35
+ );
36
+ });
37
+ });
38
+
39
+ // Central European Countries (Priority)
40
+ describe("ZipSchemaByCountry.DE (Germany)", () => {
41
+ it("should accept valid German postal codes", () => {
42
+ expect(ZipSchemaByCountry.DE.parse("12345")).toBe("12345");
43
+ expect(ZipSchemaByCountry.DE.parse("80331")).toBe("80331"); // Munich
44
+ expect(ZipSchemaByCountry.DE.parse("10115")).toBe("10115"); // Berlin
45
+ });
46
+
47
+ it("should reject invalid German postal codes", () => {
48
+ expect(() => ZipSchemaByCountry.DE.parse("1234")).toThrow();
49
+ expect(() => ZipSchemaByCountry.DE.parse("123456")).toThrow();
50
+ expect(() => ZipSchemaByCountry.DE.parse("ABCDE")).toThrow();
51
+ });
52
+ });
53
+
54
+ describe("ZipSchemaByCountry.PL (Poland)", () => {
55
+ it("should accept valid Polish postal codes", () => {
56
+ expect(ZipSchemaByCountry.PL.parse("00-001")).toBe("00-001"); // Warsaw
57
+ expect(ZipSchemaByCountry.PL.parse("30-001")).toBe("30-001"); // Krakow
58
+ expect(ZipSchemaByCountry.PL.parse("80-001")).toBe("80-001"); // Gdansk
59
+ });
60
+
61
+ it("should reject invalid Polish postal codes", () => {
62
+ expect(() => ZipSchemaByCountry.PL.parse("12345")).toThrow();
63
+ expect(() => ZipSchemaByCountry.PL.parse("1-234")).toThrow();
64
+ expect(() => ZipSchemaByCountry.PL.parse("123-45")).toThrow();
65
+ });
66
+ });
67
+
68
+ describe("ZipSchemaByCountry.FR (France)", () => {
69
+ it("should accept valid French postal codes", () => {
70
+ expect(ZipSchemaByCountry.FR.parse("75001")).toBe("75001"); // Paris
71
+ expect(ZipSchemaByCountry.FR.parse("69001")).toBe("69001"); // Lyon
72
+ expect(ZipSchemaByCountry.FR.parse("13001")).toBe("13001"); // Marseille
73
+ });
74
+
75
+ it("should reject invalid French postal codes", () => {
76
+ expect(() => ZipSchemaByCountry.FR.parse("1234")).toThrow();
77
+ expect(() => ZipSchemaByCountry.FR.parse("123456")).toThrow();
78
+ });
79
+ });
80
+
81
+ describe("ZipSchemaByCountry.AT (Austria)", () => {
82
+ it("should accept valid Austrian postal codes", () => {
83
+ expect(ZipSchemaByCountry.AT.parse("1010")).toBe("1010"); // Vienna
84
+ expect(ZipSchemaByCountry.AT.parse("5020")).toBe("5020"); // Salzburg
85
+ expect(ZipSchemaByCountry.AT.parse("6020")).toBe("6020"); // Innsbruck
86
+ });
87
+
88
+ it("should reject invalid Austrian postal codes", () => {
89
+ expect(() => ZipSchemaByCountry.AT.parse("123")).toThrow();
90
+ expect(() => ZipSchemaByCountry.AT.parse("12345")).toThrow();
91
+ });
92
+ });
93
+
94
+ describe("ZipSchemaByCountry.NL (Netherlands)", () => {
95
+ it("should accept valid Dutch postal codes", () => {
96
+ expect(ZipSchemaByCountry.NL.parse("1012 AB")).toBe("1012 AB"); // Amsterdam
97
+ expect(ZipSchemaByCountry.NL.parse("1012AB")).toBe("1012AB");
98
+ expect(ZipSchemaByCountry.NL.parse("3011 AB")).toBe("3011 AB"); // Rotterdam
99
+ });
100
+
101
+ it("should reject invalid Dutch postal codes", () => {
102
+ expect(() => ZipSchemaByCountry.NL.parse("12345")).toThrow();
103
+ expect(() => ZipSchemaByCountry.NL.parse("ABCD EF")).toThrow();
104
+ });
105
+ });
106
+
107
+ // Other European Countries
108
+ describe("ZipSchemaByCountry.CH (Switzerland)", () => {
109
+ it("should accept valid Swiss postal codes", () => {
110
+ expect(ZipSchemaByCountry.CH.parse("8001")).toBe("8001"); // Zurich
111
+ expect(ZipSchemaByCountry.CH.parse("1200")).toBe("1200"); // Geneva
112
+ });
113
+
114
+ it("should reject invalid Swiss postal codes", () => {
115
+ expect(() => ZipSchemaByCountry.CH.parse("123")).toThrow();
116
+ expect(() => ZipSchemaByCountry.CH.parse("12345")).toThrow();
117
+ });
118
+ });
119
+
120
+ describe("ZipSchemaByCountry.IT (Italy)", () => {
121
+ it("should accept valid Italian CAP codes", () => {
122
+ expect(ZipSchemaByCountry.IT.parse("00100")).toBe("00100"); // Rome
123
+ expect(ZipSchemaByCountry.IT.parse("20121")).toBe("20121"); // Milan
124
+ });
125
+
126
+ it("should reject invalid Italian CAP codes", () => {
127
+ expect(() => ZipSchemaByCountry.IT.parse("1234")).toThrow();
128
+ expect(() => ZipSchemaByCountry.IT.parse("123456")).toThrow();
129
+ });
130
+ });
131
+
132
+ describe("ZipSchemaByCountry.ES (Spain)", () => {
133
+ it("should accept valid Spanish postal codes", () => {
134
+ expect(ZipSchemaByCountry.ES.parse("28001")).toBe("28001"); // Madrid
135
+ expect(ZipSchemaByCountry.ES.parse("08001")).toBe("08001"); // Barcelona
136
+ });
137
+
138
+ it("should reject invalid Spanish postal codes", () => {
139
+ expect(() => ZipSchemaByCountry.ES.parse("1234")).toThrow();
140
+ expect(() => ZipSchemaByCountry.ES.parse("123456")).toThrow();
141
+ });
142
+ });
143
+
144
+ describe("ZipSchemaByCountry.BE (Belgium)", () => {
145
+ it("should accept valid Belgian postal codes", () => {
146
+ expect(ZipSchemaByCountry.BE.parse("1000")).toBe("1000"); // Brussels
147
+ expect(ZipSchemaByCountry.BE.parse("2000")).toBe("2000"); // Antwerp
148
+ });
149
+
150
+ it("should reject invalid Belgian postal codes", () => {
151
+ expect(() => ZipSchemaByCountry.BE.parse("123")).toThrow();
152
+ expect(() => ZipSchemaByCountry.BE.parse("12345")).toThrow();
153
+ });
154
+ });
155
+
156
+ describe("ZipSchemaByCountry.CZ (Czech Republic)", () => {
157
+ it("should accept valid Czech postal codes", () => {
158
+ expect(ZipSchemaByCountry.CZ.parse("110 00")).toBe("110 00"); // Prague
159
+ expect(ZipSchemaByCountry.CZ.parse("11000")).toBe("11000");
160
+ expect(ZipSchemaByCountry.CZ.parse("602 00")).toBe("602 00"); // Brno
161
+ });
162
+
163
+ it("should reject invalid Czech postal codes", () => {
164
+ expect(() => ZipSchemaByCountry.CZ.parse("123456")).toThrow();
165
+ expect(() => ZipSchemaByCountry.CZ.parse("1234")).toThrow();
166
+ expect(() => ZipSchemaByCountry.CZ.parse("12 345")).toThrow();
167
+ });
168
+ });
169
+
170
+ describe("ZipSchemaByCountry.UK (United Kingdom)", () => {
171
+ it("should accept valid UK postcodes", () => {
172
+ expect(ZipSchemaByCountry.UK.parse("SW1A 1AA")).toBe("SW1A 1AA");
173
+ expect(ZipSchemaByCountry.UK.parse("SW1A1AA")).toBe("SW1A1AA");
174
+ expect(ZipSchemaByCountry.UK.parse("M1 1AA")).toBe("M1 1AA");
175
+ expect(ZipSchemaByCountry.UK.parse("EC1A 1BB")).toBe("EC1A 1BB");
176
+ });
177
+
178
+ it("should reject invalid UK postcodes", () => {
179
+ expect(() => ZipSchemaByCountry.UK.parse("12345")).toThrow();
180
+ expect(() => ZipSchemaByCountry.UK.parse("AAA")).toThrow();
181
+ });
182
+ });
183
+
184
+ describe("getZipSchemaForCountry", () => {
185
+ it("should return correct schema for Central European countries", () => {
186
+ expect(getZipSchemaForCountry("DE")).toBe(ZipSchemaByCountry.DE);
187
+ expect(getZipSchemaForCountry("de")).toBe(ZipSchemaByCountry.DE);
188
+ expect(getZipSchemaForCountry("PL")).toBe(ZipSchemaByCountry.PL);
189
+ expect(getZipSchemaForCountry("FR")).toBe(ZipSchemaByCountry.FR);
190
+ expect(getZipSchemaForCountry("AT")).toBe(ZipSchemaByCountry.AT);
191
+ expect(getZipSchemaForCountry("NL")).toBe(ZipSchemaByCountry.NL);
192
+ });
193
+
194
+ it("should return InternationalZipSchema for unknown countries", () => {
195
+ expect(getZipSchemaForCountry("XX")).toBe(InternationalZipSchema);
196
+ expect(getZipSchemaForCountry("ZZ")).toBe(InternationalZipSchema);
197
+ });
198
+
199
+ it("should validate postal codes using returned schema", () => {
200
+ const deSchema = getZipSchemaForCountry("DE");
201
+ expect(deSchema.parse("12345")).toBe("12345");
202
+ expect(() => deSchema.parse("ABCDE")).toThrow();
203
+
204
+ const plSchema = getZipSchemaForCountry("PL");
205
+ expect(plSchema.parse("00-001")).toBe("00-001");
206
+ expect(() => plSchema.parse("12345")).toThrow();
207
+
208
+ const unknownSchema = getZipSchemaForCountry("XX");
209
+ expect(unknownSchema.parse("12345")).toBe("12345");
210
+ expect(unknownSchema.parse("ABCDE")).toBe("ABCDE");
211
+ });
212
+ });
@@ -0,0 +1,103 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Zod schema for validating international postal/ZIP codes.
5
+ * Focuses on Central European formats with support for other common formats.
6
+ * - Germany: 12345
7
+ * - Poland: 12-345
8
+ * - France: 12345
9
+ * - Austria: 1234
10
+ * - Netherlands: 1234 AB
11
+ * - And more
12
+ */
13
+ export const InternationalZipSchema = z
14
+ .string()
15
+ .min(3, "Postal code must be at least 3 characters long")
16
+ .max(12, "Postal code must not exceed 12 characters")
17
+ .regex(
18
+ /^[A-Z0-9\s-]+$/i,
19
+ "Postal code must contain only alphanumeric characters, spaces, and hyphens"
20
+ )
21
+ .describe("International postal/ZIP code");
22
+
23
+ /**
24
+ * Country-specific ZIP code schemas for stricter validation.
25
+ * Focuses on Central European countries (DE, PL, FR, AT, NL) with additional support for common formats.
26
+ */
27
+ export const ZipSchemaByCountry = {
28
+ /**
29
+ * Germany PLZ: 12345 (5 digits)
30
+ * Most important for Central Europe
31
+ */
32
+ DE: z.string().regex(/^\d{5}$/, "Must be a valid German postal code (12345)"),
33
+
34
+ /**
35
+ * Poland kod pocztowy: 12-345 (2 digits, hyphen, 3 digits)
36
+ * Most important for Central Europe
37
+ */
38
+ PL: z.string().regex(/^\d{2}-\d{3}$/, "Must be a valid Polish postal code (12-345)"),
39
+
40
+ /**
41
+ * France code postal: 12345 (5 digits)
42
+ * Most important for Central Europe
43
+ */
44
+ FR: z.string().regex(/^\d{5}$/, "Must be a valid French postal code (12345)"),
45
+
46
+ /**
47
+ * Austria PLZ: 1234 (4 digits)
48
+ * Most important for Central Europe
49
+ */
50
+ AT: z.string().regex(/^\d{4}$/, "Must be a valid Austrian postal code (1234)"),
51
+
52
+ /**
53
+ * Netherlands postcode: 1234 AB (4 digits, space, 2 letters)
54
+ * Most important for Central Europe
55
+ */
56
+ NL: z.string().regex(/^\d{4}\s?[A-Z]{2}$/i, "Must be a valid Dutch postal code (1234 AB)"),
57
+
58
+ /**
59
+ * Switzerland PLZ: 1234 (4 digits)
60
+ */
61
+ CH: z.string().regex(/^\d{4}$/, "Must be a valid Swiss postal code (1234)"),
62
+
63
+ /**
64
+ * Italy CAP: 12345 (5 digits)
65
+ */
66
+ IT: z.string().regex(/^\d{5}$/, "Must be a valid Italian CAP (12345)"),
67
+
68
+ /**
69
+ * Spain código postal: 12345 (5 digits)
70
+ */
71
+ ES: z.string().regex(/^\d{5}$/, "Must be a valid Spanish postal code (12345)"),
72
+
73
+ /**
74
+ * Belgium postcode: 1234 (4 digits)
75
+ */
76
+ BE: z.string().regex(/^\d{4}$/, "Must be a valid Belgian postal code (1234)"),
77
+
78
+ /**
79
+ * Czech Republic PSČ: 123 45 (3 digits, space, 2 digits)
80
+ */
81
+ CZ: z.string().regex(/^\d{3}\s?\d{2}$/, "Must be a valid Czech postal code (123 45)"),
82
+
83
+ /**
84
+ * UK postcode: SW1A 1AA
85
+ */
86
+ UK: z.string().regex(
87
+ /^[A-Z]{1,2}\d[A-Z\d]?\s?\d[A-Z]{2}$/i,
88
+ "Must be a valid UK postcode (SW1A 1AA)"
89
+ ),
90
+ } as const;
91
+
92
+ export type InternationalZip = z.infer<typeof InternationalZipSchema>;
93
+ export type CountryCode = keyof typeof ZipSchemaByCountry;
94
+
95
+ /**
96
+ * Helper function to get the appropriate ZIP schema for a country
97
+ * @param countryCode - Two-letter ISO country code
98
+ * @returns Zod schema for the country's postal code format, or InternationalZipSchema as fallback
99
+ */
100
+ export function getZipSchemaForCountry(countryCode: string): z.ZodString {
101
+ const upperCode = countryCode.toUpperCase() as CountryCode;
102
+ return ZipSchemaByCountry[upperCode] || InternationalZipSchema;
103
+ }
@@ -0,0 +1,77 @@
1
+ import { describe, it, expect } from "vitest";
2
+
3
+ import { LatitudeSchema } from "./latitude.schema";
4
+
5
+ describe("LatitudeSchema", () => {
6
+ it("should accept valid latitude values", () => {
7
+ expect(LatitudeSchema.parse(0)).toBe(0);
8
+ expect(LatitudeSchema.parse(45)).toBe(45);
9
+ expect(LatitudeSchema.parse(-45)).toBe(-45);
10
+ expect(LatitudeSchema.parse(52.52)).toBe(52.52); // Berlin
11
+ expect(LatitudeSchema.parse(40.7128)).toBe(40.7128); // New York
12
+ });
13
+
14
+ it("should accept boundary values", () => {
15
+ expect(LatitudeSchema.parse(90)).toBe(90); // North Pole
16
+ expect(LatitudeSchema.parse(-90)).toBe(-90); // South Pole
17
+ });
18
+
19
+ it("should reject values above 90", () => {
20
+ expect(() => LatitudeSchema.parse(90.1)).toThrow(
21
+ "Latitude must be less than or equal to 90"
22
+ );
23
+ expect(() => LatitudeSchema.parse(100)).toThrow(
24
+ "Latitude must be less than or equal to 90"
25
+ );
26
+ expect(() => LatitudeSchema.parse(180)).toThrow(
27
+ "Latitude must be less than or equal to 90"
28
+ );
29
+ });
30
+
31
+ it("should reject values below -90", () => {
32
+ expect(() => LatitudeSchema.parse(-90.1)).toThrow(
33
+ "Latitude must be greater than or equal to -90"
34
+ );
35
+ expect(() => LatitudeSchema.parse(-100)).toThrow(
36
+ "Latitude must be greater than or equal to -90"
37
+ );
38
+ expect(() => LatitudeSchema.parse(-180)).toThrow(
39
+ "Latitude must be greater than or equal to -90"
40
+ );
41
+ });
42
+
43
+ it("should reject non-numeric values", () => {
44
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
+ expect(() => LatitudeSchema.parse("45" as any)).toThrow();
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, unicorn/no-null
47
+ expect(() => LatitudeSchema.parse(null as any)).toThrow();
48
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
+ expect(() => LatitudeSchema.parse(undefined as any)).toThrow();
50
+ });
51
+
52
+ it("should accept values with up to 8 decimal places", () => {
53
+ expect(LatitudeSchema.parse(52.52)).toBe(52.52); // 2 decimal places
54
+ expect(LatitudeSchema.parse(40.7128)).toBe(40.7128); // 4 decimal places
55
+ expect(LatitudeSchema.parse(52.520_008)).toBe(52.520_008); // 6 decimal places
56
+ expect(LatitudeSchema.parse(52.520_008_07)).toBe(52.520_008_07); // 8 decimal places
57
+ });
58
+
59
+ it("should reject values with more than 8 decimal places", () => {
60
+ expect(() => LatitudeSchema.parse(52.520_008_071)).toThrow(
61
+ "Latitude must have at most 8 decimal places"
62
+ );
63
+ expect(() => LatitudeSchema.parse(40.712_812_345_678)).toThrow(
64
+ "Latitude must have at most 8 decimal places"
65
+ );
66
+ });
67
+
68
+ it("should handle scientific notation correctly", () => {
69
+ // Small numbers with few actual decimal places should pass
70
+ expect(LatitudeSchema.parse(1.23e-4)).toBe(1.23e-4); // 0.000123 = 6 decimals
71
+
72
+ // Numbers with more than 8 decimal places should fail
73
+ expect(() => LatitudeSchema.parse(1.234_567_89e-1)).toThrow(
74
+ "Latitude must have at most 8 decimal places"
75
+ );
76
+ });
77
+ });
@@ -0,0 +1,20 @@
1
+ import { z } from "zod";
2
+
3
+ import { hasValidDecimalPrecision } from "./coordinate-precision";
4
+
5
+ /**
6
+ * Zod schema for validating latitude coordinates.
7
+ * Latitude must be a number between -90 and 90 degrees inclusive.
8
+ * Maximum precision is 8 decimal places (~1.1mm accuracy).
9
+ */
10
+ export const LatitudeSchema = z
11
+ .number()
12
+ .min(-90, "Latitude must be greater than or equal to -90")
13
+ .max(90, "Latitude must be less than or equal to 90")
14
+ .refine(
15
+ (value) => hasValidDecimalPrecision(value, 8),
16
+ { message: "Latitude must have at most 8 decimal places" }
17
+ )
18
+ .describe("Latitude coordinate in degrees");
19
+
20
+ export type Latitude = z.infer<typeof LatitudeSchema>;
@@ -0,0 +1,21 @@
1
+ import { describe, it, expect } from "vitest";
2
+
3
+ import { LocationSchema } from "./location.schema";
4
+
5
+ describe("LocationSchema", () => {
6
+ it("should accept valid location strings", () => {
7
+ expect(LocationSchema.parse("Berlin")).toBe("Berlin");
8
+ expect(LocationSchema.parse("New York, NY")).toBe("New York, NY");
9
+ expect(LocationSchema.parse("123 Main Street, Anytown")).toBe("123 Main Street, Anytown");
10
+ });
11
+
12
+ it("should reject strings shorter than 2 characters", () => {
13
+ expect(() => LocationSchema.parse("a")).toThrow("Location must be at least 2 characters long");
14
+ });
15
+
16
+ it("should reject strings with suspicious patterns", () => {
17
+ expect(() => LocationSchema.parse("<script>alert(1)</script>")).toThrow(
18
+ "Parameter 'location' contains suspicious patterns: html.script_tag"
19
+ );
20
+ });
21
+ });
@@ -0,0 +1,22 @@
1
+ import { z } from "zod";
2
+
3
+ import { validateNoSuspiciousPatterns } from "../helpers/parameter-validation";
4
+
5
+ /**
6
+ * Zod schema for validating location parameter in findbyaddress endpoint.
7
+ * Checks for minimum length and suspicious patterns.
8
+ */
9
+ export const LocationSchema = z
10
+ .string()
11
+ .min(2, "Location must be at least 2 characters long")
12
+ .refine(
13
+ (value) => {
14
+ validateNoSuspiciousPatterns(value, "location");
15
+ return true;
16
+ },
17
+ {
18
+ message: "Location parameter contains suspicious patterns that are not allowed",
19
+ }
20
+ );
21
+
22
+ export type Location = z.infer<typeof LocationSchema>;
@@ -0,0 +1,77 @@
1
+ import { describe, it, expect } from "vitest";
2
+
3
+ import { LongitudeSchema } from "./longitude.schema";
4
+
5
+ describe("LongitudeSchema", () => {
6
+ it("should accept valid longitude values", () => {
7
+ expect(LongitudeSchema.parse(0)).toBe(0);
8
+ expect(LongitudeSchema.parse(90)).toBe(90);
9
+ expect(LongitudeSchema.parse(-90)).toBe(-90);
10
+ expect(LongitudeSchema.parse(13.405)).toBe(13.405); // Berlin
11
+ expect(LongitudeSchema.parse(-74.006)).toBe(-74.006); // New York
12
+ });
13
+
14
+ it("should accept boundary values", () => {
15
+ expect(LongitudeSchema.parse(180)).toBe(180); // International Date Line
16
+ expect(LongitudeSchema.parse(-180)).toBe(-180); // International Date Line
17
+ });
18
+
19
+ it("should reject values above 180", () => {
20
+ expect(() => LongitudeSchema.parse(180.1)).toThrow(
21
+ "Longitude must be less than or equal to 180"
22
+ );
23
+ expect(() => LongitudeSchema.parse(200)).toThrow(
24
+ "Longitude must be less than or equal to 180"
25
+ );
26
+ expect(() => LongitudeSchema.parse(360)).toThrow(
27
+ "Longitude must be less than or equal to 180"
28
+ );
29
+ });
30
+
31
+ it("should reject values below -180", () => {
32
+ expect(() => LongitudeSchema.parse(-180.1)).toThrow(
33
+ "Longitude must be greater than or equal to -180"
34
+ );
35
+ expect(() => LongitudeSchema.parse(-200)).toThrow(
36
+ "Longitude must be greater than or equal to -180"
37
+ );
38
+ expect(() => LongitudeSchema.parse(-360)).toThrow(
39
+ "Longitude must be greater than or equal to -180"
40
+ );
41
+ });
42
+
43
+ it("should reject non-numeric values", () => {
44
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
+ expect(() => LongitudeSchema.parse("45" as any)).toThrow();
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, unicorn/no-null
47
+ expect(() => LongitudeSchema.parse(null as any)).toThrow();
48
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
+ expect(() => LongitudeSchema.parse(undefined as any)).toThrow();
50
+ });
51
+
52
+ it("should accept values with up to 8 decimal places", () => {
53
+ expect(LongitudeSchema.parse(13.405)).toBe(13.405); // 3 decimal places
54
+ expect(LongitudeSchema.parse(-74.006)).toBe(-74.006); // 3 decimal places
55
+ expect(LongitudeSchema.parse(151.2093)).toBe(151.2093); // 4 decimal places
56
+ expect(LongitudeSchema.parse(13.405_000_01)).toBe(13.405_000_01); // 8 decimal places
57
+ });
58
+
59
+ it("should reject values with more than 8 decimal places", () => {
60
+ expect(() => LongitudeSchema.parse(13.405_000_012)).toThrow(
61
+ "Longitude must have at most 8 decimal places"
62
+ );
63
+ expect(() => LongitudeSchema.parse(-74.006_123_456_789)).toThrow(
64
+ "Longitude must have at most 8 decimal places"
65
+ );
66
+ });
67
+
68
+ it("should handle scientific notation correctly", () => {
69
+ // Small numbers with few actual decimal places should pass
70
+ expect(LongitudeSchema.parse(1.23e-4)).toBe(1.23e-4); // 0.000123 = 6 decimals
71
+
72
+ // Numbers with more than 8 decimal places should fail
73
+ expect(() => LongitudeSchema.parse(1.234_567_89e-1)).toThrow(
74
+ "Longitude must have at most 8 decimal places"
75
+ );
76
+ });
77
+ });
@@ -0,0 +1,20 @@
1
+ import { z } from "zod";
2
+
3
+ import { hasValidDecimalPrecision } from "./coordinate-precision";
4
+
5
+ /**
6
+ * Zod schema for validating longitude coordinates.
7
+ * Longitude must be a number between -180 and 180 degrees inclusive.
8
+ * Maximum precision is 8 decimal places (~1.1mm accuracy).
9
+ */
10
+ export const LongitudeSchema = z
11
+ .number()
12
+ .min(-180, "Longitude must be greater than or equal to -180")
13
+ .max(180, "Longitude must be less than or equal to 180")
14
+ .refine(
15
+ (value) => hasValidDecimalPrecision(value, 8),
16
+ { message: "Longitude must have at most 8 decimal places" }
17
+ )
18
+ .describe("Longitude coordinate in degrees");
19
+
20
+ export type Longitude = z.infer<typeof LongitudeSchema>;
@@ -0,0 +1,32 @@
1
+ import { describe, it, expect } from "vitest";
2
+
3
+ import { NumericIdSchema } from "./numeric-id.schema";
4
+
5
+ describe("NumericIdSchema", () => {
6
+ it("should accept valid numeric ID strings", () => {
7
+ expect(NumericIdSchema.parse("123")).toBe("123");
8
+ expect(NumericIdSchema.parse("1234567890")).toBe("1234567890");
9
+ expect(NumericIdSchema.parse("12345678901234567890")).toBe("12345678901234567890");
10
+ });
11
+
12
+ it("should reject strings shorter than 3 characters", () => {
13
+ expect(() => NumericIdSchema.parse("12")).toThrow("ID must be at least 3 characters long");
14
+ });
15
+
16
+ it("should reject strings longer than 20 characters", () => {
17
+ expect(() => NumericIdSchema.parse("123456789012345678901")).toThrow("ID must not exceed 20 characters");
18
+ });
19
+
20
+ it("should reject non-numeric strings", () => {
21
+ expect(() => NumericIdSchema.parse("123abc")).toThrow("ID must contain only numeric characters");
22
+ expect(() => NumericIdSchema.parse("abc123")).toThrow("ID must contain only numeric characters");
23
+ expect(() => NumericIdSchema.parse("12-34")).toThrow("ID must contain only numeric characters");
24
+ });
25
+
26
+ it("should reject numeric strings with suspicious patterns", () => {
27
+ // This test ensures regex validation happens first for non-numeric characters
28
+ expect(() => NumericIdSchema.parse("123<script>")).toThrow(
29
+ "ID must contain only numeric characters"
30
+ );
31
+ });
32
+ });
@@ -0,0 +1,13 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Zod schema for validating ID parameter in community ID endpoint.
5
+ * Checks that ID contains only numbers and is between 3-20 characters long.
6
+ */
7
+ export const NumericIdSchema = z
8
+ .string()
9
+ .min(3, "ID must be at least 3 characters long")
10
+ .max(20, "ID must not exceed 20 characters")
11
+ .regex(/^\d+$/, "ID must contain only numeric characters");
12
+
13
+ export type NumericId = z.infer<typeof NumericIdSchema>;