@kravc/schema 2.7.5 → 2.8.0-alpha.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 (170) hide show
  1. package/README.md +19 -14
  2. package/dist/CredentialFactory.d.ts +345 -0
  3. package/dist/CredentialFactory.d.ts.map +1 -0
  4. package/dist/CredentialFactory.js +381 -0
  5. package/dist/CredentialFactory.js.map +1 -0
  6. package/dist/Schema.d.ts +448 -0
  7. package/dist/Schema.d.ts.map +1 -0
  8. package/dist/Schema.js +506 -0
  9. package/dist/Schema.js.map +1 -0
  10. package/dist/ValidationError.d.ts +70 -0
  11. package/dist/ValidationError.d.ts.map +1 -0
  12. package/dist/ValidationError.js +78 -0
  13. package/dist/ValidationError.js.map +1 -0
  14. package/dist/Validator.d.ts +483 -0
  15. package/dist/Validator.d.ts.map +1 -0
  16. package/dist/Validator.js +570 -0
  17. package/dist/Validator.js.map +1 -0
  18. package/dist/helpers/JsonSchema.d.ts +99 -0
  19. package/dist/helpers/JsonSchema.d.ts.map +1 -0
  20. package/dist/helpers/JsonSchema.js +3 -0
  21. package/dist/helpers/JsonSchema.js.map +1 -0
  22. package/dist/helpers/cleanupAttributes.d.ts +34 -0
  23. package/dist/helpers/cleanupAttributes.d.ts.map +1 -0
  24. package/dist/helpers/cleanupAttributes.js +113 -0
  25. package/dist/helpers/cleanupAttributes.js.map +1 -0
  26. package/dist/helpers/cleanupNulls.d.ts +27 -0
  27. package/dist/helpers/cleanupNulls.d.ts.map +1 -0
  28. package/dist/helpers/cleanupNulls.js +96 -0
  29. package/dist/helpers/cleanupNulls.js.map +1 -0
  30. package/dist/helpers/getReferenceIds.d.ts +169 -0
  31. package/dist/helpers/getReferenceIds.d.ts.map +1 -0
  32. package/dist/helpers/getReferenceIds.js +241 -0
  33. package/dist/helpers/getReferenceIds.js.map +1 -0
  34. package/dist/helpers/got.d.ts +60 -0
  35. package/dist/helpers/got.d.ts.map +1 -0
  36. package/dist/helpers/got.js +72 -0
  37. package/dist/helpers/got.js.map +1 -0
  38. package/dist/helpers/mapObjectProperties.d.ts +150 -0
  39. package/dist/helpers/mapObjectProperties.d.ts.map +1 -0
  40. package/dist/helpers/mapObjectProperties.js +229 -0
  41. package/dist/helpers/mapObjectProperties.js.map +1 -0
  42. package/dist/helpers/normalizeAttributes.d.ts +213 -0
  43. package/dist/helpers/normalizeAttributes.d.ts.map +1 -0
  44. package/dist/helpers/normalizeAttributes.js +243 -0
  45. package/dist/helpers/normalizeAttributes.js.map +1 -0
  46. package/dist/helpers/normalizeProperties.d.ts +168 -0
  47. package/dist/helpers/normalizeProperties.d.ts.map +1 -0
  48. package/dist/helpers/normalizeProperties.js +223 -0
  49. package/dist/helpers/normalizeProperties.js.map +1 -0
  50. package/dist/helpers/normalizeRequired.d.ts +159 -0
  51. package/dist/helpers/normalizeRequired.d.ts.map +1 -0
  52. package/dist/helpers/normalizeRequired.js +206 -0
  53. package/dist/helpers/normalizeRequired.js.map +1 -0
  54. package/dist/helpers/normalizeType.d.ts +81 -0
  55. package/dist/helpers/normalizeType.d.ts.map +1 -0
  56. package/dist/helpers/normalizeType.js +210 -0
  57. package/dist/helpers/normalizeType.js.map +1 -0
  58. package/dist/helpers/nullifyEmptyValues.d.ts +139 -0
  59. package/dist/helpers/nullifyEmptyValues.d.ts.map +1 -0
  60. package/dist/helpers/nullifyEmptyValues.js +191 -0
  61. package/dist/helpers/nullifyEmptyValues.js.map +1 -0
  62. package/dist/helpers/removeRequiredAndDefault.d.ts +106 -0
  63. package/dist/helpers/removeRequiredAndDefault.d.ts.map +1 -0
  64. package/dist/helpers/removeRequiredAndDefault.js +138 -0
  65. package/dist/helpers/removeRequiredAndDefault.js.map +1 -0
  66. package/dist/helpers/validateId.d.ts +39 -0
  67. package/dist/helpers/validateId.d.ts.map +1 -0
  68. package/dist/helpers/validateId.js +51 -0
  69. package/dist/helpers/validateId.js.map +1 -0
  70. package/dist/index.d.ts +7 -0
  71. package/dist/index.d.ts.map +1 -0
  72. package/dist/index.js +17 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/ld/documentLoader.d.ts +8 -0
  75. package/dist/ld/documentLoader.d.ts.map +1 -0
  76. package/dist/ld/documentLoader.js +24 -0
  77. package/dist/ld/documentLoader.js.map +1 -0
  78. package/dist/ld/getLinkedDataAttributeType.d.ts +10 -0
  79. package/dist/ld/getLinkedDataAttributeType.d.ts.map +1 -0
  80. package/dist/ld/getLinkedDataAttributeType.js +32 -0
  81. package/dist/ld/getLinkedDataAttributeType.js.map +1 -0
  82. package/dist/ld/getLinkedDataContext.d.ts +19 -0
  83. package/dist/ld/getLinkedDataContext.d.ts.map +1 -0
  84. package/dist/ld/getLinkedDataContext.js +50 -0
  85. package/dist/ld/getLinkedDataContext.js.map +1 -0
  86. package/eslint.config.mjs +32 -52
  87. package/examples/credentials/createAccountCredential.ts +27 -0
  88. package/examples/credentials/createMineSweeperScoreCredential.ts +115 -0
  89. package/examples/index.ts +7 -0
  90. package/examples/schemas/FavoriteItemSchema.ts +27 -0
  91. package/examples/{Preferences.yaml → schemas/Preferences.yaml} +2 -0
  92. package/examples/schemas/PreferencesSchema.ts +29 -0
  93. package/examples/schemas/ProfileSchema.ts +91 -0
  94. package/examples/schemas/Status.yaml +3 -0
  95. package/examples/schemas/StatusSchema.ts +12 -0
  96. package/jest.config.mjs +5 -0
  97. package/package.json +28 -21
  98. package/src/CredentialFactory.ts +392 -0
  99. package/src/Schema.ts +583 -0
  100. package/src/ValidationError.ts +90 -0
  101. package/src/Validator.ts +603 -0
  102. package/src/__tests__/CredentialFactory.test.ts +588 -0
  103. package/src/__tests__/Schema.test.ts +371 -0
  104. package/src/__tests__/ValidationError.test.ts +235 -0
  105. package/src/__tests__/Validator.test.ts +787 -0
  106. package/src/helpers/JsonSchema.ts +119 -0
  107. package/src/helpers/__tests__/cleanupAttributes.test.ts +943 -0
  108. package/src/helpers/__tests__/cleanupNulls.test.ts +772 -0
  109. package/src/helpers/__tests__/getReferenceIds.test.ts +975 -0
  110. package/src/helpers/__tests__/got.test.ts +193 -0
  111. package/src/helpers/__tests__/mapObjectProperties.test.ts +1126 -0
  112. package/src/helpers/__tests__/normalizeAttributes.test.ts +1435 -0
  113. package/src/helpers/__tests__/normalizeProperties.test.ts +727 -0
  114. package/src/helpers/__tests__/normalizeRequired.test.ts +669 -0
  115. package/src/helpers/__tests__/normalizeType.test.ts +772 -0
  116. package/src/helpers/__tests__/nullifyEmptyValues.test.ts +735 -0
  117. package/src/helpers/__tests__/removeRequiredAndDefault.test.ts +734 -0
  118. package/src/helpers/__tests__/validateId.test.ts +118 -0
  119. package/src/helpers/cleanupAttributes.ts +151 -0
  120. package/src/helpers/cleanupNulls.ts +106 -0
  121. package/src/helpers/getReferenceIds.ts +273 -0
  122. package/src/helpers/got.ts +73 -0
  123. package/src/helpers/mapObjectProperties.ts +272 -0
  124. package/src/helpers/normalizeAttributes.ts +247 -0
  125. package/src/helpers/normalizeProperties.ts +249 -0
  126. package/src/helpers/normalizeRequired.ts +233 -0
  127. package/src/helpers/normalizeType.ts +235 -0
  128. package/src/helpers/nullifyEmptyValues.ts +207 -0
  129. package/src/helpers/removeRequiredAndDefault.ts +151 -0
  130. package/src/helpers/validateId.ts +53 -0
  131. package/src/index.ts +13 -0
  132. package/src/ld/__tests__/documentLoader.test.ts +57 -0
  133. package/src/ld/__tests__/getLinkedDataAttributeType.test.ts +212 -0
  134. package/src/ld/__tests__/getLinkedDataContext.test.ts +378 -0
  135. package/src/ld/documentLoader.ts +28 -0
  136. package/src/ld/getLinkedDataAttributeType.ts +46 -0
  137. package/src/ld/getLinkedDataContext.ts +80 -0
  138. package/tsconfig.json +27 -0
  139. package/types/credentials-context.d.ts +14 -0
  140. package/types/security-context.d.ts +6 -0
  141. package/examples/Status.yaml +0 -3
  142. package/examples/createAccountCredential.js +0 -27
  143. package/examples/createMineSweeperScoreCredential.js +0 -63
  144. package/examples/index.js +0 -9
  145. package/src/CredentialFactory.js +0 -67
  146. package/src/CredentialFactory.spec.js +0 -131
  147. package/src/Schema.js +0 -104
  148. package/src/Schema.spec.js +0 -172
  149. package/src/ValidationError.js +0 -31
  150. package/src/Validator.js +0 -128
  151. package/src/Validator.spec.js +0 -355
  152. package/src/helpers/cleanupAttributes.js +0 -71
  153. package/src/helpers/cleanupNulls.js +0 -42
  154. package/src/helpers/getReferenceIds.js +0 -71
  155. package/src/helpers/mapObject.js +0 -65
  156. package/src/helpers/normalizeAttributes.js +0 -28
  157. package/src/helpers/normalizeProperties.js +0 -61
  158. package/src/helpers/normalizeRequired.js +0 -37
  159. package/src/helpers/normalizeType.js +0 -41
  160. package/src/helpers/nullifyEmptyValues.js +0 -57
  161. package/src/helpers/removeRequiredAndDefault.js +0 -30
  162. package/src/helpers/validateId.js +0 -19
  163. package/src/index.d.ts +0 -25
  164. package/src/index.js +0 -8
  165. package/src/ld/documentLoader.js +0 -25
  166. package/src/ld/documentLoader.spec.js +0 -12
  167. package/src/ld/getLinkedDataContext.js +0 -63
  168. package/src/ld/getLinkedDataType.js +0 -38
  169. /package/examples/{FavoriteItem.yaml → schemas/FavoriteItem.yaml} +0 -0
  170. /package/examples/{Profile.yaml → schemas/Profile.yaml} +0 -0
@@ -0,0 +1,735 @@
1
+ import nullifyEmptyValues from '../nullifyEmptyValues';
2
+ import { schemaSymbol, jsonSymbol } from 'z-schema';
3
+ import type { SchemaErrorDetail } from 'z-schema';
4
+
5
+ describe('nullifyEmptyValues(object, validationErrors)', () => {
6
+ // eslint-disable-next-line jsdoc/require-jsdoc
7
+ const createMockError = (
8
+ code: string,
9
+ path: string,
10
+ json: unknown,
11
+ schema: unknown = {}
12
+ ): SchemaErrorDetail => {
13
+ const error = {
14
+ code,
15
+ path,
16
+ message: `Error at ${path}`,
17
+ params: [],
18
+ description: `Error at ${path}`,
19
+ inner: [],
20
+ } as SchemaErrorDetail;
21
+
22
+ // Attach symbols to the error object
23
+ (error as SchemaErrorDetail)[schemaSymbol] = schema;
24
+ (error as SchemaErrorDetail)[jsonSymbol] = json;
25
+
26
+ return error;
27
+ };
28
+
29
+ describe('format errors with empty strings', () => {
30
+ it('should replace empty string with null for PATTERN error', () => {
31
+ const object = { field: '' };
32
+ const error = createMockError('PATTERN', '#/field', object);
33
+ const validationErrors = [error];
34
+
35
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
36
+
37
+ expect(result.field).toBeNull();
38
+ expect(remainingErrors).toHaveLength(0);
39
+ // Original object should not be modified
40
+ expect(object.field).toBe('');
41
+ });
42
+
43
+ it('should replace empty string with null for ENUM_MISMATCH error', () => {
44
+ const object = { field: '' };
45
+ const error = createMockError('ENUM_MISMATCH', '#/field', object);
46
+ const validationErrors = [error];
47
+
48
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
49
+
50
+ expect(result.field).toBeNull();
51
+ expect(remainingErrors).toHaveLength(0);
52
+ });
53
+
54
+ it('should replace empty string with null for INVALID_FORMAT error', () => {
55
+ const object = { field: '' };
56
+ const error = createMockError('INVALID_FORMAT', '#/field', object);
57
+ const validationErrors = [error];
58
+
59
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
60
+
61
+ expect(result.field).toBeNull();
62
+ expect(remainingErrors).toHaveLength(0);
63
+ });
64
+
65
+ it('should handle nested paths', () => {
66
+ const object = { nested: { field: '' } };
67
+ const error = createMockError('PATTERN', '#/nested/field', object);
68
+ const validationErrors = [error];
69
+
70
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
71
+
72
+ expect(result.nested.field).toBeNull();
73
+ expect(remainingErrors).toHaveLength(0);
74
+ expect(object.nested.field).toBe('');
75
+ });
76
+
77
+ it('should handle deeply nested paths', () => {
78
+ const object = { level1: { level2: { level3: { field: '' } } } };
79
+ const error = createMockError('PATTERN', '#/level1/level2/level3/field', object);
80
+ const validationErrors = [error];
81
+
82
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
83
+
84
+ expect(result.level1.level2.level3.field).toBeNull();
85
+ expect(remainingErrors).toHaveLength(0);
86
+ });
87
+
88
+ it('should handle multiple empty string fields', () => {
89
+ const object = { field1: '', field2: '', field3: 'value' };
90
+ const error1 = createMockError('PATTERN', '#/field1', object);
91
+ const error2 = createMockError('ENUM_MISMATCH', '#/field2', object);
92
+ const validationErrors = [error1, error2];
93
+
94
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
95
+
96
+ expect(result.field1).toBeNull();
97
+ expect(result.field2).toBeNull();
98
+ expect(result.field3).toBe('value');
99
+ expect(remainingErrors).toHaveLength(0);
100
+ });
101
+
102
+ it('should handle array paths', () => {
103
+ const object = { items: ['', 'value', ''] };
104
+ const error1 = createMockError('PATTERN', '#/items/0', object);
105
+ const error2 = createMockError('PATTERN', '#/items/2', object);
106
+ const validationErrors = [error1, error2];
107
+
108
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
109
+
110
+ expect(result.items[0]).toBeNull();
111
+ expect(result.items[1]).toBe('value');
112
+ expect(result.items[2]).toBeNull();
113
+ expect(remainingErrors).toHaveLength(0);
114
+ });
115
+ });
116
+
117
+ describe('required attributes', () => {
118
+ it('should not nullify empty string if attribute is required', () => {
119
+ const object = { field: '' };
120
+ const schema = { 'x-required': true };
121
+ const error = createMockError('PATTERN', '#/field', object, schema);
122
+ const validationErrors = [error];
123
+
124
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
125
+
126
+ expect(result.field).toBe('');
127
+ expect(remainingErrors).toHaveLength(1);
128
+ expect(remainingErrors[0]).toBe(error);
129
+ });
130
+
131
+ it('should not nullify if x-required is false', () => {
132
+ const object = { field: '' };
133
+ const schema = { 'x-required': false };
134
+ const error = createMockError('PATTERN', '#/field', object, schema);
135
+ const validationErrors = [error];
136
+
137
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
138
+
139
+ expect(result.field).toBeNull();
140
+ expect(remainingErrors).toHaveLength(0);
141
+ });
142
+
143
+ it('should not nullify if x-required is undefined', () => {
144
+ const object = { field: '' };
145
+ const error = createMockError('PATTERN', '#/field', object);
146
+ const validationErrors = [error];
147
+
148
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
149
+
150
+ expect(result.field).toBeNull();
151
+ expect(remainingErrors).toHaveLength(0);
152
+ });
153
+ });
154
+
155
+ describe('non-format errors', () => {
156
+ it('should not nullify for INVALID_TYPE error', () => {
157
+ const object = { field: '' };
158
+ const error = createMockError('INVALID_TYPE', '#/field', object);
159
+ const validationErrors = [error];
160
+
161
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
162
+
163
+ expect(result.field).toBe('');
164
+ expect(remainingErrors).toHaveLength(1);
165
+ expect(remainingErrors[0]).toBe(error);
166
+ });
167
+
168
+ it('should not nullify for MIN_LENGTH error', () => {
169
+ const object = { field: '' };
170
+ const error = createMockError('MIN_LENGTH', '#/field', object);
171
+ const validationErrors = [error];
172
+
173
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
174
+
175
+ expect(result.field).toBe('');
176
+ expect(remainingErrors).toHaveLength(1);
177
+ expect(remainingErrors[0]).toBe(error);
178
+ });
179
+
180
+ it('should not nullify for MAX_LENGTH error', () => {
181
+ const object = { field: '' };
182
+ const error = createMockError('MAX_LENGTH', '#/field', object);
183
+ const validationErrors = [error];
184
+
185
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
186
+
187
+ expect(result.field).toBe('');
188
+ expect(remainingErrors).toHaveLength(1);
189
+ expect(remainingErrors[0]).toBe(error);
190
+ });
191
+ });
192
+
193
+ describe('non-empty values', () => {
194
+ it('should not nullify non-empty string values', () => {
195
+ const object = { field: 'value' };
196
+ const error = createMockError('PATTERN', '#/field', object);
197
+ const validationErrors = [error];
198
+
199
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
200
+
201
+ expect(result.field).toBe('value');
202
+ expect(remainingErrors).toHaveLength(1);
203
+ expect(remainingErrors[0]).toBe(error);
204
+ });
205
+
206
+ it('should not nullify number values', () => {
207
+ const object = { field: 0 };
208
+ const error = createMockError('PATTERN', '#/field', object);
209
+ const validationErrors = [error];
210
+
211
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
212
+
213
+ expect(result.field).toBe(0);
214
+ expect(remainingErrors).toHaveLength(1);
215
+ });
216
+
217
+ it('should not nullify null values', () => {
218
+ const object = { field: null };
219
+ const error = createMockError('PATTERN', '#/field', object);
220
+ const validationErrors = [error];
221
+
222
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
223
+
224
+ expect(result.field).toBeNull();
225
+ expect(remainingErrors).toHaveLength(1);
226
+ });
227
+
228
+ it('should not nullify boolean values', () => {
229
+ const object = { field: false };
230
+ const error = createMockError('PATTERN', '#/field', object);
231
+ const validationErrors = [error];
232
+
233
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
234
+
235
+ expect(result.field).toBe(false);
236
+ expect(remainingErrors).toHaveLength(1);
237
+ });
238
+ });
239
+
240
+ describe('mixed scenarios', () => {
241
+ it('should handle mix of nullifiable and non-nullifiable errors', () => {
242
+ const object = { field1: '', field2: '', field3: 'value' };
243
+ const error1 = createMockError('PATTERN', '#/field1', object);
244
+ const error2 = createMockError('INVALID_TYPE', '#/field2', object);
245
+ const error3 = createMockError('PATTERN', '#/field3', object);
246
+ const validationErrors = [error1, error2, error3];
247
+
248
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
249
+
250
+ expect(result.field1).toBeNull();
251
+ expect(result.field2).toBe('');
252
+ expect(result.field3).toBe('value');
253
+ expect(remainingErrors).toHaveLength(2);
254
+ expect(remainingErrors).toContain(error2);
255
+ expect(remainingErrors).toContain(error3);
256
+ });
257
+
258
+ it('should handle required and non-required errors', () => {
259
+ const object = { requiredField: '', optionalField: '' };
260
+ const schema1 = { 'x-required': true };
261
+ const schema2 = {};
262
+ const error1 = createMockError('PATTERN', '#/requiredField', object, schema1);
263
+ const error2 = createMockError('PATTERN', '#/optionalField', object, schema2);
264
+ const validationErrors = [error1, error2];
265
+
266
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
267
+
268
+ expect(result.requiredField).toBe('');
269
+ expect(result.optionalField).toBeNull();
270
+ expect(remainingErrors).toHaveLength(1);
271
+ expect(remainingErrors[0]).toBe(error1);
272
+ });
273
+
274
+ it('should handle format errors with empty and non-empty values', () => {
275
+ const object = { emptyField: '', nonEmptyField: 'invalid' };
276
+ const error1 = createMockError('PATTERN', '#/emptyField', object);
277
+ const error2 = createMockError('PATTERN', '#/nonEmptyField', object);
278
+ const validationErrors = [error1, error2];
279
+
280
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
281
+
282
+ expect(result.emptyField).toBeNull();
283
+ expect(result.nonEmptyField).toBe('invalid');
284
+ expect(remainingErrors).toHaveLength(1);
285
+ expect(remainingErrors[0]).toBe(error2);
286
+ });
287
+ });
288
+
289
+ describe('edge cases', () => {
290
+ it('should return deep copy of object', () => {
291
+ const object = { field: 'value', nested: { deep: 'value' } };
292
+ const validationErrors: SchemaErrorDetail[] = [];
293
+
294
+ const [result] = nullifyEmptyValues(object, validationErrors);
295
+
296
+ expect(result).not.toBe(object);
297
+ expect(result).toEqual(object);
298
+ expect(result.nested).not.toBe(object.nested);
299
+ });
300
+
301
+ it('should handle empty validation errors array', () => {
302
+ const object = { field: 'value' };
303
+ const validationErrors: SchemaErrorDetail[] = [];
304
+
305
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
306
+
307
+ expect(result).toEqual(object);
308
+ expect(remainingErrors).toHaveLength(0);
309
+ });
310
+
311
+ it('should handle empty object', () => {
312
+ const object = {};
313
+ const validationErrors: SchemaErrorDetail[] = [];
314
+
315
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
316
+
317
+ expect(result).toEqual({});
318
+ expect(remainingErrors).toHaveLength(0);
319
+ });
320
+
321
+ it('should handle complex nested structures', () => {
322
+ const object = {
323
+ level1: {
324
+ level2: {
325
+ empty: '',
326
+ nonEmpty: 'value',
327
+ array: ['', 'value', ''],
328
+ },
329
+ },
330
+ };
331
+ const error1 = createMockError('PATTERN', '#/level1/level2/empty', object);
332
+ const error2 = createMockError('PATTERN', '#/level1/level2/array/0', object);
333
+ const error3 = createMockError('PATTERN', '#/level1/level2/array/2', object);
334
+ const validationErrors = [error1, error2, error3];
335
+
336
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
337
+
338
+ expect(result.level1.level2.empty).toBeNull();
339
+ expect(result.level1.level2.nonEmpty).toBe('value');
340
+ expect(result.level1.level2.array[0]).toBeNull();
341
+ expect(result.level1.level2.array[1]).toBe('value');
342
+ expect(result.level1.level2.array[2]).toBeNull();
343
+ expect(remainingErrors).toHaveLength(0);
344
+ });
345
+
346
+ it('should handle path without #/ prefix', () => {
347
+ const object = { field: '' };
348
+ const error = createMockError('PATTERN', 'field', object);
349
+ const validationErrors = [error];
350
+
351
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
352
+
353
+ // The function replaces '#/' with '', so 'field' becomes ['field']
354
+ expect(result.field).toBeNull();
355
+ expect(remainingErrors).toHaveLength(0);
356
+ });
357
+
358
+ it('should preserve other object properties', () => {
359
+ const object = {
360
+ emptyField: '',
361
+ numberField: 42,
362
+ booleanField: true,
363
+ nullField: null,
364
+ objectField: { nested: 'value' },
365
+ };
366
+ const error = createMockError('PATTERN', '#/emptyField', object);
367
+ const validationErrors = [error];
368
+
369
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
370
+
371
+ expect(result.emptyField).toBeNull();
372
+ expect(result.numberField).toBe(42);
373
+ expect(result.booleanField).toBe(true);
374
+ expect(result.nullField).toBeNull();
375
+ expect(result.objectField).toEqual({ nested: 'value' });
376
+ expect(remainingErrors).toHaveLength(0);
377
+ });
378
+
379
+ it('should handle empty path string', () => {
380
+ const object = { field: '' };
381
+ const error = createMockError('PATTERN', '', object);
382
+ const validationErrors = [error];
383
+
384
+ const [, remainingErrors] = nullifyEmptyValues(object, validationErrors);
385
+
386
+ // Empty path should result in empty array after filter, which lodash.set handles
387
+ expect(remainingErrors).toHaveLength(1);
388
+ expect(remainingErrors[0]).toBe(error);
389
+ });
390
+
391
+ it('should handle root level path #/', () => {
392
+ const object = '';
393
+ const error = createMockError('PATTERN', '#/', object);
394
+ const validationErrors = [error];
395
+
396
+ const [, remainingErrors] = nullifyEmptyValues(object, validationErrors);
397
+
398
+ // Root level path should result in empty array after filter
399
+ expect(remainingErrors).toHaveLength(1);
400
+ expect(remainingErrors[0]).toBe(error);
401
+ });
402
+
403
+ it('should handle path that does not exist in object', () => {
404
+ const object = { field: 'value' };
405
+ const error = createMockError('PATTERN', '#/nonexistent', object);
406
+ const validationErrors = [error];
407
+
408
+ const [, remainingErrors] = nullifyEmptyValues(object, validationErrors);
409
+
410
+ // Path doesn't exist, value is undefined, should not nullify
411
+ expect(remainingErrors).toHaveLength(1);
412
+ expect(remainingErrors[0]).toBe(error);
413
+ });
414
+
415
+ it('should handle multiple errors for the same path', () => {
416
+ const object = { field: '' };
417
+ const error1 = createMockError('PATTERN', '#/field', object);
418
+ const error2 = createMockError('ENUM_MISMATCH', '#/field', object);
419
+ const validationErrors = [error1, error2];
420
+
421
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
422
+
423
+ // Both errors should be processed and field should be nullified
424
+ expect(result.field).toBeNull();
425
+ expect(remainingErrors).toHaveLength(0);
426
+ });
427
+
428
+ it('should handle array index out of bounds', () => {
429
+ const object = { items: ['value'] };
430
+ const error = createMockError('PATTERN', '#/items/5', object);
431
+ const validationErrors = [error];
432
+
433
+ const [, remainingErrors] = nullifyEmptyValues(object, validationErrors);
434
+
435
+ // Index out of bounds, value is undefined, should not nullify
436
+ expect(remainingErrors).toHaveLength(1);
437
+ expect(remainingErrors[0]).toBe(error);
438
+ });
439
+
440
+ it('should handle whitespace-only strings', () => {
441
+ const object = { field: ' ' };
442
+ const error = createMockError('PATTERN', '#/field', object);
443
+ const validationErrors = [error];
444
+
445
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
446
+
447
+ // Whitespace is not empty string, should not nullify
448
+ expect(result.field).toBe(' ');
449
+ expect(remainingErrors).toHaveLength(1);
450
+ expect(remainingErrors[0]).toBe(error);
451
+ });
452
+
453
+ it('should handle empty array', () => {
454
+ const object = { items: [] };
455
+ const error = createMockError('PATTERN', '#/items', object);
456
+ const validationErrors = [error];
457
+
458
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
459
+
460
+ // Empty array is not empty string, should not nullify
461
+ expect(result.items).toEqual([]);
462
+ expect(remainingErrors).toHaveLength(1);
463
+ expect(remainingErrors[0]).toBe(error);
464
+ });
465
+
466
+ it('should handle empty object', () => {
467
+ const object = { field: {} };
468
+ const error = createMockError('PATTERN', '#/field', object);
469
+ const validationErrors = [error];
470
+
471
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
472
+
473
+ // Empty object is not empty string, should not nullify
474
+ expect(result.field).toEqual({});
475
+ expect(remainingErrors).toHaveLength(1);
476
+ expect(remainingErrors[0]).toBe(error);
477
+ });
478
+
479
+ it('should handle x-required as string "true"', () => {
480
+ const object = { field: '' };
481
+ const schema = { 'x-required': 'true' };
482
+ const error = createMockError('PATTERN', '#/field', object, schema);
483
+ const validationErrors = [error];
484
+
485
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
486
+
487
+ // String 'true' is not boolean true, should nullify
488
+ expect(result.field).toBeNull();
489
+ expect(remainingErrors).toHaveLength(0);
490
+ });
491
+
492
+ it('should handle x-required as number 1', () => {
493
+ const object = { field: '' };
494
+ const schema = { 'x-required': 1 };
495
+ const error = createMockError('PATTERN', '#/field', object, schema);
496
+ const validationErrors = [error];
497
+
498
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
499
+
500
+ // Number 1 is not boolean true, should nullify
501
+ expect(result.field).toBeNull();
502
+ expect(remainingErrors).toHaveLength(0);
503
+ });
504
+
505
+ it('should handle x-required as null', () => {
506
+ const object = { field: '' };
507
+ const schema = { 'x-required': null };
508
+ const error = createMockError('PATTERN', '#/field', object, schema);
509
+ const validationErrors = [error];
510
+
511
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
512
+
513
+ // null is not boolean true, should nullify
514
+ expect(result.field).toBeNull();
515
+ expect(remainingErrors).toHaveLength(0);
516
+ });
517
+
518
+ it('should handle missing schema symbol', () => {
519
+ const object = { field: '' };
520
+ const error = {
521
+ code: 'PATTERN',
522
+ path: '#/field',
523
+ message: 'Error at #/field',
524
+ params: [],
525
+ description: 'Error at #/field',
526
+ inner: [],
527
+ } as SchemaErrorDetail;
528
+
529
+ // Don't attach schemaSymbol
530
+ (error as SchemaErrorDetail)[jsonSymbol] = object;
531
+ const validationErrors = [error];
532
+
533
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
534
+
535
+ // Missing schema should be treated as not required, should nullify
536
+ expect(result.field).toBeNull();
537
+ expect(remainingErrors).toHaveLength(0);
538
+ });
539
+
540
+ it('should handle missing json symbol', () => {
541
+ const object = { field: '' };
542
+ const error = {
543
+ code: 'PATTERN',
544
+ path: '#/field',
545
+ message: 'Error at #/field',
546
+ params: [],
547
+ description: 'Error at #/field',
548
+ inner: [],
549
+ } as SchemaErrorDetail;
550
+
551
+ // Don't attach jsonSymbol
552
+ (error as SchemaErrorDetail)[schemaSymbol] = {};
553
+ const validationErrors = [error];
554
+
555
+ const [, remainingErrors] = nullifyEmptyValues(object, validationErrors);
556
+
557
+ // Missing json symbol means value is undefined, should not nullify
558
+ expect(remainingErrors).toHaveLength(1);
559
+ expect(remainingErrors[0]).toBe(error);
560
+ });
561
+
562
+ it('should handle all format error codes together', () => {
563
+ const object = { field1: '', field2: '', field3: '' };
564
+ const error1 = createMockError('PATTERN', '#/field1', object);
565
+ const error2 = createMockError('ENUM_MISMATCH', '#/field2', object);
566
+ const error3 = createMockError('INVALID_FORMAT', '#/field3', object);
567
+ const validationErrors = [error1, error2, error3];
568
+
569
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
570
+
571
+ expect(result.field1).toBeNull();
572
+ expect(result.field2).toBeNull();
573
+ expect(result.field3).toBeNull();
574
+ expect(remainingErrors).toHaveLength(0);
575
+ });
576
+
577
+ it('should handle nested arrays with empty strings', () => {
578
+ const object = {
579
+ items: [
580
+ { name: '', value: 'test' },
581
+ { name: 'test', value: '' },
582
+ ],
583
+ };
584
+ const error1 = createMockError('PATTERN', '#/items/0/name', object);
585
+ const error2 = createMockError('PATTERN', '#/items/1/value', object);
586
+ const validationErrors = [error1, error2];
587
+
588
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
589
+
590
+ expect(result.items[0].name).toBeNull();
591
+ expect(result.items[0].value).toBe('test');
592
+ expect(result.items[1].name).toBe('test');
593
+ expect(result.items[1].value).toBeNull();
594
+ expect(remainingErrors).toHaveLength(0);
595
+ });
596
+
597
+ it('should handle path with multiple consecutive slashes', () => {
598
+ const object = { field: '' };
599
+ const error = createMockError('PATTERN', '#//field', object);
600
+ const validationErrors = [error];
601
+
602
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
603
+
604
+ // Multiple slashes should be filtered out, path becomes ['field']
605
+ expect(result.field).toBeNull();
606
+ expect(remainingErrors).toHaveLength(0);
607
+ });
608
+
609
+ it('should handle path with trailing slash', () => {
610
+ const object = { field: '' };
611
+ const error = createMockError('PATTERN', '#/field/', object);
612
+ const validationErrors = [error];
613
+
614
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
615
+
616
+ // Trailing slash should be filtered out
617
+ expect(result.field).toBeNull();
618
+ expect(remainingErrors).toHaveLength(0);
619
+ });
620
+
621
+ it('should handle undefined value in object', () => {
622
+ const object: Record<string, unknown> = { field: undefined };
623
+ const error = createMockError('PATTERN', '#/field', object);
624
+ const validationErrors = [error];
625
+
626
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
627
+
628
+ // undefined is not empty string, should not nullify
629
+ expect(result.field).toBeUndefined();
630
+ expect(remainingErrors).toHaveLength(1);
631
+ expect(remainingErrors[0]).toBe(error);
632
+ });
633
+
634
+ it('should handle value 0 (falsy but not empty)', () => {
635
+ const object = { field: 0 };
636
+ const error = createMockError('PATTERN', '#/field', object);
637
+ const validationErrors = [error];
638
+
639
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
640
+
641
+ // 0 is not empty string, should not nullify
642
+ expect(result.field).toBe(0);
643
+ expect(remainingErrors).toHaveLength(1);
644
+ expect(remainingErrors[0]).toBe(error);
645
+ });
646
+
647
+ it('should handle value false (falsy but not empty)', () => {
648
+ const object = { field: false };
649
+ const error = createMockError('PATTERN', '#/field', object);
650
+ const validationErrors = [error];
651
+
652
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
653
+
654
+ // false is not empty string, should not nullify
655
+ expect(result.field).toBe(false);
656
+ expect(remainingErrors).toHaveLength(1);
657
+ expect(remainingErrors[0]).toBe(error);
658
+ });
659
+
660
+ it('should handle very long nested paths', () => {
661
+ const object = {
662
+ level1: {
663
+ level2: {
664
+ level3: {
665
+ level4: {
666
+ level5: {
667
+ level6: {
668
+ level7: {
669
+ level8: {
670
+ level9: {
671
+ level10: { field: '' },
672
+ },
673
+ },
674
+ },
675
+ },
676
+ },
677
+ },
678
+ },
679
+ },
680
+ },
681
+ };
682
+ const error = createMockError(
683
+ 'PATTERN',
684
+ '#/level1/level2/level3/level4/level5/level6/level7/level8/level9/level10/field',
685
+ object
686
+ );
687
+ const validationErrors = [error];
688
+
689
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
690
+
691
+ expect(result.level1.level2.level3.level4.level5.level6.level7.level8.level9.level10.field).toBeNull();
692
+ expect(remainingErrors).toHaveLength(0);
693
+ });
694
+
695
+ it('should handle mixed format and non-format errors for same field', () => {
696
+ const object = { field: '' };
697
+ const formatError = createMockError('PATTERN', '#/field', object);
698
+ const typeError = createMockError('INVALID_TYPE', '#/field', object);
699
+ const validationErrors = [formatError, typeError];
700
+
701
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
702
+
703
+ // Format error should nullify, type error should remain
704
+ expect(result.field).toBeNull();
705
+ expect(remainingErrors).toHaveLength(1);
706
+ expect(remainingErrors[0]).toBe(typeError);
707
+ });
708
+
709
+ it('should handle schema with x-required as empty object', () => {
710
+ const object = { field: '' };
711
+ const schema = {};
712
+ const error = createMockError('PATTERN', '#/field', object, schema);
713
+ const validationErrors = [error];
714
+
715
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
716
+
717
+ // Empty schema object means not required, should nullify
718
+ expect(result.field).toBeNull();
719
+ expect(remainingErrors).toHaveLength(0);
720
+ });
721
+
722
+ it('should handle schema with x-required as undefined property', () => {
723
+ const object = { field: '' };
724
+ const schema = { 'x-required': undefined };
725
+ const error = createMockError('PATTERN', '#/field', object, schema);
726
+ const validationErrors = [error];
727
+
728
+ const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
729
+
730
+ // undefined is not boolean true, should nullify
731
+ expect(result.field).toBeNull();
732
+ expect(remainingErrors).toHaveLength(0);
733
+ });
734
+ });
735
+ });