moonflower 1.4.3 → 1.4.5
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.
- package/dist/openapi/analyzerModule/nodeParsers.cjs +1 -1
- package/dist/openapi/analyzerModule/nodeParsers.cjs.map +1 -1
- package/dist/openapi/analyzerModule/nodeParsers.d.ts.map +1 -1
- package/dist/openapi/analyzerModule/nodeParsers.mjs +101 -92
- package/dist/openapi/analyzerModule/nodeParsers.mjs.map +1 -1
- package/dist/openapi/analyzerModule/test/TestCase.d.ts +4 -0
- package/dist/openapi/analyzerModule/test/TestCase.d.ts.map +1 -1
- package/dist/validators/validateParam.cjs +1 -1
- package/dist/validators/validateParam.cjs.map +1 -1
- package/dist/validators/validateParam.mjs +33 -18
- package/dist/validators/validateParam.mjs.map +1 -1
- package/package.json +1 -1
- package/src/hooks/useQueryParams.spec.ts +271 -0
- package/src/openapi/analyzerModule/nodeParsers.ts +24 -12
- package/src/openapi/analyzerModule/test/TestCase.ts +4 -0
- package/src/openapi/analyzerModule/test/openApiAnalyzer.zod.spec.data.ts +31 -0
- package/src/openapi/analyzerModule/test/openApiAnalyzer.zod.spec.ts +78 -0
- package/src/validators/validateParam.ts +28 -1
|
@@ -321,4 +321,275 @@ describe('useQueryParams', () => {
|
|
|
321
321
|
expectTypeOf(params.numberParam).toEqualTypeOf<number | undefined>()
|
|
322
322
|
})
|
|
323
323
|
})
|
|
324
|
+
|
|
325
|
+
describe('legacy array validators', () => {
|
|
326
|
+
it('parses JSON string array with legacy validator', () => {
|
|
327
|
+
const ctx = mockContextQuery(mockContext(), {
|
|
328
|
+
nodes: JSON.stringify(['node1', 'node2']),
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
const params = useQueryParams(ctx, {
|
|
332
|
+
nodes: RequiredParam<string[]>({
|
|
333
|
+
parse: (v) => JSON.parse(String(v)),
|
|
334
|
+
}),
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
expect(params.nodes).toEqual(['node1', 'node2'])
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
it('parses comma-separated array with legacy validator', () => {
|
|
341
|
+
const ctx = mockContextQuery(mockContext(), {
|
|
342
|
+
nodes: 'node1,node2,node3',
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
const params = useQueryParams(ctx, {
|
|
346
|
+
nodes: RequiredParam<string[]>({
|
|
347
|
+
parse: (v) => String(v).split(','),
|
|
348
|
+
}),
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
expect(params.nodes).toEqual(['node1', 'node2', 'node3'])
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
it('fails validation on invalid legacy array param', () => {
|
|
355
|
+
const test = () => {
|
|
356
|
+
const ctx = mockContextQuery(mockContext(), {
|
|
357
|
+
nodes: 'not valid json array',
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
useQueryParams(ctx, {
|
|
361
|
+
nodes: RequiredParam<string[]>({
|
|
362
|
+
parse: (v) => JSON.parse(String(v)),
|
|
363
|
+
}),
|
|
364
|
+
})
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
expect(test).toThrow(ValidationError)
|
|
368
|
+
expect(test).toThrow("Failed query param validation: 'nodes'")
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
it('allows missing optional legacy array param', () => {
|
|
372
|
+
const ctx = mockContextQuery(mockContext(), {})
|
|
373
|
+
|
|
374
|
+
const params = useQueryParams(ctx, {
|
|
375
|
+
nodes: OptionalParam<string[]>({
|
|
376
|
+
parse: (v) => JSON.parse(String(v)),
|
|
377
|
+
}),
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
expect(params.nodes).toEqual(undefined)
|
|
381
|
+
})
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
describe('zod array validators', () => {
|
|
385
|
+
it('parses JSON string array', () => {
|
|
386
|
+
const ctx = mockContextQuery(mockContext(), {
|
|
387
|
+
nodes: JSON.stringify(['node1', 'node2']),
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
const params = useQueryParams(ctx, {
|
|
391
|
+
nodes: z.array(z.string()),
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
expect(params.nodes).toEqual(['node1', 'node2'])
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
it('parses JSON number array', () => {
|
|
398
|
+
const ctx = mockContextQuery(mockContext(), {
|
|
399
|
+
ids: JSON.stringify([1, 2, 3]),
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
const params = useQueryParams(ctx, {
|
|
403
|
+
ids: z.array(z.number()),
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
expect(params.ids).toEqual([1, 2, 3])
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
it('parses JSON object array', () => {
|
|
410
|
+
const ctx = mockContextQuery(mockContext(), {
|
|
411
|
+
items: JSON.stringify([
|
|
412
|
+
{ name: 'first', value: 1 },
|
|
413
|
+
{ name: 'second', value: 2 },
|
|
414
|
+
]),
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
const params = useQueryParams(ctx, {
|
|
418
|
+
items: z.array(z.object({ name: z.string(), value: z.number() })),
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
expect(params.items).toEqual([
|
|
422
|
+
{ name: 'first', value: 1 },
|
|
423
|
+
{ name: 'second', value: 2 },
|
|
424
|
+
])
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
it('parses comma-separated string array', () => {
|
|
428
|
+
const ctx = mockContextQuery(mockContext(), {
|
|
429
|
+
nodes: 'node1,node2,node3',
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
const params = useQueryParams(ctx, {
|
|
433
|
+
nodes: z.array(z.string()),
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
expect(params.nodes).toEqual(['node1', 'node2', 'node3'])
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
it('parses comma-separated number array', () => {
|
|
440
|
+
const ctx = mockContextQuery(mockContext(), {
|
|
441
|
+
ids: '1,2,3',
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
const params = useQueryParams(ctx, {
|
|
445
|
+
ids: z.array(z.number()),
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
expect(params.ids).toEqual([1, 2, 3])
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
it('parses comma-separated boolean array', () => {
|
|
452
|
+
const ctx = mockContextQuery(mockContext(), {
|
|
453
|
+
flags: 'true,false,true',
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
const params = useQueryParams(ctx, {
|
|
457
|
+
flags: z.array(z.boolean()),
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
expect(params.flags).toEqual([true, false, true])
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
it('parses comma-separated values with whitespace', () => {
|
|
464
|
+
const ctx = mockContextQuery(mockContext(), {
|
|
465
|
+
nodes: 'node1, node2 , node3',
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
const params = useQueryParams(ctx, {
|
|
469
|
+
nodes: z.array(z.string()),
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
expect(params.nodes).toEqual(['node1', 'node2', 'node3'])
|
|
473
|
+
})
|
|
474
|
+
|
|
475
|
+
it('parses single value as string array', () => {
|
|
476
|
+
const ctx = mockContextQuery(mockContext(), {
|
|
477
|
+
nodes: 'single',
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
const params = useQueryParams(ctx, {
|
|
481
|
+
nodes: z.array(z.string()),
|
|
482
|
+
})
|
|
483
|
+
|
|
484
|
+
expect(params.nodes).toEqual(['single'])
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
it('parses single value as number array', () => {
|
|
488
|
+
const ctx = mockContextQuery(mockContext(), {
|
|
489
|
+
ids: '42',
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
const params = useQueryParams(ctx, {
|
|
493
|
+
ids: z.array(z.number()),
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
expect(params.ids).toEqual([42])
|
|
497
|
+
})
|
|
498
|
+
|
|
499
|
+
it('fails validation on invalid array element', () => {
|
|
500
|
+
const test = () => {
|
|
501
|
+
const ctx = mockContextQuery(mockContext(), {
|
|
502
|
+
ids: 'abc,def',
|
|
503
|
+
})
|
|
504
|
+
|
|
505
|
+
useQueryParams(ctx, {
|
|
506
|
+
ids: z.array(z.number()),
|
|
507
|
+
})
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
expect(test).toThrow(ValidationError)
|
|
511
|
+
expect(test).toThrow("Failed query param validation: 'ids'")
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
it('fails when required array is missing', () => {
|
|
515
|
+
const test = () => {
|
|
516
|
+
const ctx = mockContextQuery(mockContext(), {})
|
|
517
|
+
|
|
518
|
+
useQueryParams(ctx, {
|
|
519
|
+
nodes: z.array(z.string()),
|
|
520
|
+
})
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
expect(test).toThrow(ValidationError)
|
|
524
|
+
expect(test).toThrow("Missing query params: 'nodes'")
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
it('parses optional array when missing', () => {
|
|
528
|
+
const ctx = mockContextQuery(mockContext(), {})
|
|
529
|
+
|
|
530
|
+
const params = useQueryParams(ctx, {
|
|
531
|
+
nodes: z.array(z.string()).optional(),
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
expect(params.nodes).toEqual(undefined)
|
|
535
|
+
})
|
|
536
|
+
|
|
537
|
+
it('parses optional array when present', () => {
|
|
538
|
+
const ctx = mockContextQuery(mockContext(), {
|
|
539
|
+
nodes: 'node1,node2',
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
const params = useQueryParams(ctx, {
|
|
543
|
+
nodes: z.array(z.string()).optional(),
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
expect(params.nodes).toEqual(['node1', 'node2'])
|
|
547
|
+
})
|
|
548
|
+
|
|
549
|
+
it('infers return type of string array', () => {
|
|
550
|
+
const ctx = mockContextQuery(mockContext(), {
|
|
551
|
+
nodes: 'a,b',
|
|
552
|
+
})
|
|
553
|
+
|
|
554
|
+
const params = useQueryParams(ctx, {
|
|
555
|
+
nodes: z.array(z.string()),
|
|
556
|
+
})
|
|
557
|
+
|
|
558
|
+
expectTypeOf(params.nodes).toEqualTypeOf<string[]>()
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
it('infers return type of number array', () => {
|
|
562
|
+
const ctx = mockContextQuery(mockContext(), {
|
|
563
|
+
ids: '1,2',
|
|
564
|
+
})
|
|
565
|
+
|
|
566
|
+
const params = useQueryParams(ctx, {
|
|
567
|
+
ids: z.array(z.number()),
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
expectTypeOf(params.ids).toEqualTypeOf<number[]>()
|
|
571
|
+
})
|
|
572
|
+
|
|
573
|
+
it('infers return type of object array', () => {
|
|
574
|
+
const ctx = mockContextQuery(mockContext(), {
|
|
575
|
+
items: JSON.stringify([{ name: 'a' }]),
|
|
576
|
+
})
|
|
577
|
+
|
|
578
|
+
const params = useQueryParams(ctx, {
|
|
579
|
+
items: z.array(z.object({ name: z.string() })),
|
|
580
|
+
})
|
|
581
|
+
|
|
582
|
+
expectTypeOf(params.items).toEqualTypeOf<{ name: string }[]>()
|
|
583
|
+
})
|
|
584
|
+
|
|
585
|
+
it('infers return type of optional array', () => {
|
|
586
|
+
const ctx = mockContextQuery(mockContext(), {})
|
|
587
|
+
|
|
588
|
+
const params = useQueryParams(ctx, {
|
|
589
|
+
nodes: z.array(z.string()).optional(),
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
expectTypeOf(params.nodes).toEqualTypeOf<string[] | undefined>()
|
|
593
|
+
})
|
|
594
|
+
})
|
|
324
595
|
})
|
|
@@ -308,19 +308,31 @@ const getZodCallShape = (node: Node): ShapeOfType['shape'] => {
|
|
|
308
308
|
|
|
309
309
|
if (typeName === 'ZodArray') {
|
|
310
310
|
const argNode = callExpression.getFirstChildByKind(SyntaxKind.SyntaxList)?.getFirstChild()
|
|
311
|
-
if (
|
|
312
|
-
|
|
311
|
+
if (argNode) {
|
|
312
|
+
const elementShape = isZodCallExpression(argNode)
|
|
313
|
+
? getZodCallShape(argNode)
|
|
314
|
+
: getValidatorPropertyShape(argNode)
|
|
315
|
+
return [
|
|
316
|
+
{
|
|
317
|
+
role: 'array' as const,
|
|
318
|
+
shape: elementShape,
|
|
319
|
+
optional: false,
|
|
320
|
+
},
|
|
321
|
+
]
|
|
313
322
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
323
|
+
// Handle chained form: z.string().array()
|
|
324
|
+
const propertyAccess = callExpression.getFirstChildByKind(SyntaxKind.PropertyAccessExpression)
|
|
325
|
+
const receiverCall = propertyAccess?.getFirstChildByKind(SyntaxKind.CallExpression)
|
|
326
|
+
if (receiverCall && isZodCallExpression(receiverCall)) {
|
|
327
|
+
return [
|
|
328
|
+
{
|
|
329
|
+
role: 'array' as const,
|
|
330
|
+
shape: getZodCallShape(receiverCall),
|
|
331
|
+
optional: false,
|
|
332
|
+
},
|
|
333
|
+
]
|
|
334
|
+
}
|
|
335
|
+
return 'unknown_zod_array'
|
|
324
336
|
}
|
|
325
337
|
|
|
326
338
|
if (typeName === 'ZodEnum') {
|
|
@@ -7,6 +7,10 @@ export const TestCase = {
|
|
|
7
7
|
parsesInlineZodEnum: 'parses-inline-zod-enum',
|
|
8
8
|
parsesZodOptional: 'parses-zod-optional',
|
|
9
9
|
parsesAliasedZodSchema: 'parses-aliased-zod-schema',
|
|
10
|
+
parsesZodQueryStringArray: 'parses-zod-query-string-array',
|
|
11
|
+
parsesZodQueryNumberArray: 'parses-zod-query-number-array',
|
|
12
|
+
parsesZodQueryObjectArray: 'parses-zod-query-object-array',
|
|
13
|
+
parsesZodQueryOptionalArray: 'parses-zod-query-optional-array',
|
|
10
14
|
parsesReturnRecordStringUnknown: 'parses-return-record-string-unknown',
|
|
11
15
|
parsesReturnObjectWithRecordProperty: 'parses-return-object-with-record-property',
|
|
12
16
|
parsesBufferReturnedFromFunction: 'parses-buffer-returned-from-function',
|
|
@@ -3,6 +3,7 @@ import { z } from 'zod'
|
|
|
3
3
|
import { z as valibot } from 'zod'
|
|
4
4
|
|
|
5
5
|
import { usePathParams } from '../../../hooks/usePathParams'
|
|
6
|
+
import { useQueryParams } from '../../../hooks/useQueryParams'
|
|
6
7
|
import { useRequestBody } from '../../../hooks/useRequestBody'
|
|
7
8
|
import { Router } from '../../../router/Router'
|
|
8
9
|
import { OptionalParam } from '../../../validators/ParamWrappers'
|
|
@@ -116,3 +117,33 @@ router.post(`/test/${TestCase.parsesZodOptional}`, (ctx) => {
|
|
|
116
117
|
optionalNumber: z.number().optional(),
|
|
117
118
|
})
|
|
118
119
|
})
|
|
120
|
+
|
|
121
|
+
router.get(`/test/${TestCase.parsesZodQueryStringArray}`, (ctx) => {
|
|
122
|
+
useQueryParams(ctx, {
|
|
123
|
+
tags: z.array(z.string()),
|
|
124
|
+
otherTags: z.string().array(),
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
router.get(`/test/${TestCase.parsesZodQueryNumberArray}`, (ctx) => {
|
|
129
|
+
useQueryParams(ctx, {
|
|
130
|
+
ids: z.array(z.number()),
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
router.get(`/test/${TestCase.parsesZodQueryObjectArray}`, (ctx) => {
|
|
135
|
+
useQueryParams(ctx, {
|
|
136
|
+
items: z.array(
|
|
137
|
+
z.object({
|
|
138
|
+
name: z.string(),
|
|
139
|
+
value: z.number(),
|
|
140
|
+
}),
|
|
141
|
+
),
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
router.get(`/test/${TestCase.parsesZodQueryOptionalArray}`, (ctx) => {
|
|
146
|
+
useQueryParams(ctx, {
|
|
147
|
+
tags: z.array(z.string()).optional(),
|
|
148
|
+
})
|
|
149
|
+
})
|
|
@@ -237,6 +237,84 @@ describe('OpenApi Analyzer (Zod Validator)', () => {
|
|
|
237
237
|
])
|
|
238
238
|
expect(endpoint.objectBody[0].optional).toEqual(false)
|
|
239
239
|
})
|
|
240
|
+
|
|
241
|
+
it('parses zod query string array validators', () => {
|
|
242
|
+
const endpoint = analyzeEndpointById(TestCase.parsesZodQueryStringArray)
|
|
243
|
+
|
|
244
|
+
expect(endpoint.requestQuery[0].identifier).toEqual('tags')
|
|
245
|
+
expect(endpoint.requestQuery[0].signature).toEqual([
|
|
246
|
+
{
|
|
247
|
+
role: 'array',
|
|
248
|
+
shape: 'string',
|
|
249
|
+
optional: false,
|
|
250
|
+
},
|
|
251
|
+
])
|
|
252
|
+
expect(endpoint.requestQuery[0].optional).toEqual(false)
|
|
253
|
+
expect(endpoint.requestQuery[1].identifier).toEqual('otherTags')
|
|
254
|
+
expect(endpoint.requestQuery[1].signature).toEqual([
|
|
255
|
+
{
|
|
256
|
+
role: 'array',
|
|
257
|
+
shape: 'string',
|
|
258
|
+
optional: false,
|
|
259
|
+
},
|
|
260
|
+
])
|
|
261
|
+
expect(endpoint.requestQuery[1].optional).toEqual(false)
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
it('parses zod query number array validators', () => {
|
|
265
|
+
const endpoint = analyzeEndpointById(TestCase.parsesZodQueryNumberArray)
|
|
266
|
+
|
|
267
|
+
expect(endpoint.requestQuery[0].identifier).toEqual('ids')
|
|
268
|
+
expect(endpoint.requestQuery[0].signature).toEqual([
|
|
269
|
+
{
|
|
270
|
+
role: 'array',
|
|
271
|
+
shape: 'number',
|
|
272
|
+
optional: false,
|
|
273
|
+
},
|
|
274
|
+
])
|
|
275
|
+
expect(endpoint.requestQuery[0].optional).toEqual(false)
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
it('parses zod query object array validators', () => {
|
|
279
|
+
const endpoint = analyzeEndpointById(TestCase.parsesZodQueryObjectArray)
|
|
280
|
+
|
|
281
|
+
expect(endpoint.requestQuery[0].identifier).toEqual('items')
|
|
282
|
+
expect(endpoint.requestQuery[0].signature).toEqual([
|
|
283
|
+
{
|
|
284
|
+
role: 'array',
|
|
285
|
+
shape: [
|
|
286
|
+
{
|
|
287
|
+
identifier: 'name',
|
|
288
|
+
optional: false,
|
|
289
|
+
role: 'property',
|
|
290
|
+
shape: 'string',
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
identifier: 'value',
|
|
294
|
+
optional: false,
|
|
295
|
+
role: 'property',
|
|
296
|
+
shape: 'number',
|
|
297
|
+
},
|
|
298
|
+
],
|
|
299
|
+
optional: false,
|
|
300
|
+
},
|
|
301
|
+
])
|
|
302
|
+
expect(endpoint.requestQuery[0].optional).toEqual(false)
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
it('parses zod query optional array validators', () => {
|
|
306
|
+
const endpoint = analyzeEndpointById(TestCase.parsesZodQueryOptionalArray)
|
|
307
|
+
|
|
308
|
+
expect(endpoint.requestQuery[0].identifier).toEqual('tags')
|
|
309
|
+
expect(endpoint.requestQuery[0].signature).toEqual([
|
|
310
|
+
{
|
|
311
|
+
role: 'array',
|
|
312
|
+
shape: 'string',
|
|
313
|
+
optional: false,
|
|
314
|
+
},
|
|
315
|
+
])
|
|
316
|
+
expect(endpoint.requestQuery[0].optional).toEqual(true)
|
|
317
|
+
})
|
|
240
318
|
})
|
|
241
319
|
})
|
|
242
320
|
})
|
|
@@ -36,13 +36,29 @@ function runPrevalidator(validator: ValidatorUnion, value: string | number | boo
|
|
|
36
36
|
return true
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
function isZodArrayValidator(validator: z.ZodType): boolean {
|
|
40
|
+
if (validator instanceof z.ZodArray) return true
|
|
41
|
+
if (validator instanceof z.ZodOptional) return validator.unwrap() instanceof z.ZodArray
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
|
|
39
45
|
function runParser(validator: ValidatorUnion, value: string | number | boolean | object | null) {
|
|
40
46
|
// Zod validator
|
|
41
47
|
if (validator instanceof z.ZodType) {
|
|
48
|
+
const isArrayValidator = isZodArrayValidator(validator)
|
|
42
49
|
const coercedValue = (() => {
|
|
50
|
+
if (typeof value !== 'string') return value
|
|
51
|
+
|
|
43
52
|
try {
|
|
44
|
-
|
|
53
|
+
const parsed = JSON.parse(value)
|
|
54
|
+
if (isArrayValidator && !Array.isArray(parsed)) {
|
|
55
|
+
return coerceCommaSeparatedToArray(value)
|
|
56
|
+
}
|
|
57
|
+
return parsed
|
|
45
58
|
} catch {
|
|
59
|
+
if (isArrayValidator) {
|
|
60
|
+
return coerceCommaSeparatedToArray(value)
|
|
61
|
+
}
|
|
46
62
|
return value
|
|
47
63
|
}
|
|
48
64
|
})()
|
|
@@ -52,6 +68,17 @@ function runParser(validator: ValidatorUnion, value: string | number | boolean |
|
|
|
52
68
|
return validator.parse(getValueAsNullableString(value))
|
|
53
69
|
}
|
|
54
70
|
|
|
71
|
+
function coerceCommaSeparatedToArray(value: string): unknown[] {
|
|
72
|
+
return value.split(',').map((element) => {
|
|
73
|
+
const trimmed = element.trim()
|
|
74
|
+
try {
|
|
75
|
+
return JSON.parse(trimmed)
|
|
76
|
+
} catch {
|
|
77
|
+
return trimmed
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
55
82
|
function runValidator(validator: ValidatorUnion, parsedValue: unknown) {
|
|
56
83
|
// Legacy validator
|
|
57
84
|
if ('validate' in validator && typeof validator.validate === 'function') {
|