adorn-api 1.0.6 → 1.0.8

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/express.js CHANGED
@@ -249,7 +249,8 @@ function bootstrap(options) {
249
249
  swaggerPath = "/docs",
250
250
  swaggerJsonPath = "/docs/openapi.json",
251
251
  middleware,
252
- auth
252
+ auth,
253
+ coerce
253
254
  } = options;
254
255
  if (controllers.length === 0) {
255
256
  reject(new Error("At least one controller must be provided to bootstrap()."));
@@ -269,7 +270,8 @@ function bootstrap(options) {
269
270
  controllers,
270
271
  artifactsDir: absoluteArtifactsDir,
271
272
  middleware,
272
- auth
273
+ auth,
274
+ coerce
273
275
  });
274
276
  app.use(router);
275
277
  if (enableSwagger) {
@@ -324,6 +326,17 @@ function bootstrap(options) {
324
326
  }
325
327
 
326
328
  // src/adapter/express/index.ts
329
+ function normalizeCoerceOptions(coerce) {
330
+ return {
331
+ body: coerce?.body ?? false,
332
+ query: coerce?.query ?? false,
333
+ path: coerce?.path ?? false,
334
+ header: coerce?.header ?? false,
335
+ cookie: coerce?.cookie ?? false,
336
+ dateTime: coerce?.dateTime ?? false,
337
+ date: coerce?.date ?? false
338
+ };
339
+ }
327
340
  async function createExpressRouter(options) {
328
341
  const { controllers, artifactsDir = ".adorn", middleware = {} } = options;
329
342
  let manifest;
@@ -350,6 +363,7 @@ async function createExpressRouter(options) {
350
363
  const validator = precompiledValidators ? null : createValidator();
351
364
  const router = Router();
352
365
  const instanceCache = /* @__PURE__ */ new Map();
366
+ const coerce = normalizeCoerceOptions(options.coerce);
353
367
  function getInstance(Ctor) {
354
368
  if (!instanceCache.has(Ctor)) {
355
369
  instanceCache.set(Ctor, new Ctor());
@@ -412,6 +426,14 @@ async function createExpressRouter(options) {
412
426
  }
413
427
  for (const route of routes) {
414
428
  const method = route.httpMethod.toLowerCase();
429
+ const openapiOperation = getOpenApiOperation(openapi, route);
430
+ const paramSchemaIndex = buildParamSchemaIndex(openapiOperation);
431
+ const bodySchema = getRequestBodySchema(openapiOperation, route.args.body?.contentType) ?? (route.args.body ? getSchemaByRef(route.args.body.schemaRef) : null);
432
+ const coerceBodyDates = getDateCoercionOptions(coerce, "body");
433
+ const coerceQueryDates = getDateCoercionOptions(coerce, "query");
434
+ const coercePathDates = getDateCoercionOptions(coerce, "path");
435
+ const coerceHeaderDates = getDateCoercionOptions(coerce, "header");
436
+ const coerceCookieDates = getDateCoercionOptions(coerce, "cookie");
415
437
  const middlewareChain = [];
416
438
  if (middleware.global) {
417
439
  middlewareChain.push(...resolveMiddleware(middleware.global, middleware.named || {}));
@@ -439,29 +461,50 @@ async function createExpressRouter(options) {
439
461
  }
440
462
  const args = [];
441
463
  if (route.args.body) {
442
- args[route.args.body.index] = req.body;
464
+ const coercedBody = (coerceBodyDates.date || coerceBodyDates.dateTime) && bodySchema ? coerceDatesWithSchema(req.body, bodySchema, coerceBodyDates, openapi.components.schemas) : req.body;
465
+ args[route.args.body.index] = coercedBody;
443
466
  }
444
467
  for (const pathArg of route.args.path) {
445
- const coerced = coerceValue(req.params[pathArg.name], pathArg.schemaType);
468
+ const rawValue = req.params[pathArg.name];
469
+ const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "path", pathArg.name) ?? (pathArg.schemaRef ? getSchemaByRef(pathArg.schemaRef) : null) ?? schemaFromType(pathArg.schemaType);
470
+ const coerced = coerceParamValue(rawValue, paramSchema, coercePathDates, openapi.components.schemas);
446
471
  args[pathArg.index] = coerced;
447
472
  }
448
473
  if (route.args.query.length > 0) {
449
- const firstQueryIndex = route.args.query[0].index;
450
- const allSameIndex = route.args.query.every((q) => q.index === firstQueryIndex);
451
- if (allSameIndex) {
452
- args[firstQueryIndex] = {};
453
- for (const q of route.args.query) {
454
- const parsed = parseQueryValue(req.query[q.name], q);
455
- const coerced = coerceValue(parsed, q.schemaType);
456
- args[firstQueryIndex][q.name] = coerced;
457
- }
458
- } else {
459
- for (const q of route.args.query) {
460
- const parsed = parseQueryValue(req.query[q.name], q);
461
- const coerced = coerceValue(parsed, q.schemaType);
474
+ const deepObjectArgs = route.args.query.filter((q) => q.serialization?.style === "deepObject");
475
+ const standardArgs = route.args.query.filter((q) => q.serialization?.style !== "deepObject");
476
+ if (deepObjectArgs.length > 0) {
477
+ const rawQuery = getRawQueryString(req);
478
+ const names = new Set(deepObjectArgs.map((q) => q.name));
479
+ const parsedDeep = parseDeepObjectParams(rawQuery, names);
480
+ for (const q of deepObjectArgs) {
481
+ const rawValue = parsedDeep[q.name];
482
+ const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "query", q.name) ?? (q.schemaRef ? getSchemaByRef(q.schemaRef) : null) ?? schemaFromType(q.schemaType);
483
+ const baseValue = rawValue === void 0 ? {} : rawValue;
484
+ const coerced = coerceParamValue(baseValue, paramSchema, coerceQueryDates, openapi.components.schemas);
462
485
  args[q.index] = coerced;
463
486
  }
464
487
  }
488
+ if (standardArgs.length > 0) {
489
+ const firstQueryIndex = standardArgs[0].index;
490
+ const allSameIndex = standardArgs.every((q) => q.index === firstQueryIndex);
491
+ if (allSameIndex) {
492
+ args[firstQueryIndex] = {};
493
+ for (const q of standardArgs) {
494
+ const parsed = parseQueryValue(req.query[q.name], q);
495
+ const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "query", q.name) ?? (q.schemaRef ? getSchemaByRef(q.schemaRef) : null) ?? schemaFromType(q.schemaType);
496
+ const coerced = coerceParamValue(parsed, paramSchema, coerceQueryDates, openapi.components.schemas);
497
+ args[firstQueryIndex][q.name] = coerced;
498
+ }
499
+ } else {
500
+ for (const q of standardArgs) {
501
+ const parsed = parseQueryValue(req.query[q.name], q);
502
+ const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "query", q.name) ?? (q.schemaRef ? getSchemaByRef(q.schemaRef) : null) ?? schemaFromType(q.schemaType);
503
+ const coerced = coerceParamValue(parsed, paramSchema, coerceQueryDates, openapi.components.schemas);
504
+ args[q.index] = coerced;
505
+ }
506
+ }
507
+ }
465
508
  }
466
509
  if (route.args.headers.length > 0) {
467
510
  const firstHeaderIndex = route.args.headers[0].index;
@@ -470,12 +513,16 @@ async function createExpressRouter(options) {
470
513
  args[firstHeaderIndex] = {};
471
514
  for (const h of route.args.headers) {
472
515
  const headerValue = req.headers[h.name.toLowerCase()];
473
- args[firstHeaderIndex][h.name] = headerValue ?? void 0;
516
+ const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "header", h.name) ?? (h.schemaRef ? getSchemaByRef(h.schemaRef) : null) ?? schemaFromType(h.schemaType);
517
+ const coerced = coerceParamValue(headerValue, paramSchema, coerceHeaderDates, openapi.components.schemas);
518
+ args[firstHeaderIndex][h.name] = coerced ?? void 0;
474
519
  }
475
520
  } else {
476
521
  for (const h of route.args.headers) {
477
522
  const headerValue = req.headers[h.name.toLowerCase()];
478
- args[h.index] = headerValue ?? void 0;
523
+ const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "header", h.name) ?? (h.schemaRef ? getSchemaByRef(h.schemaRef) : null) ?? schemaFromType(h.schemaType);
524
+ const coerced = coerceParamValue(headerValue, paramSchema, coerceHeaderDates, openapi.components.schemas);
525
+ args[h.index] = coerced ?? void 0;
479
526
  }
480
527
  }
481
528
  }
@@ -487,13 +534,15 @@ async function createExpressRouter(options) {
487
534
  args[firstCookieIndex] = {};
488
535
  for (const c of route.args.cookies) {
489
536
  const cookieValue = cookies[c.name];
490
- const coerced = coerceValue(cookieValue, c.schemaType);
537
+ const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "cookie", c.name) ?? (c.schemaRef ? getSchemaByRef(c.schemaRef) : null) ?? schemaFromType(c.schemaType);
538
+ const coerced = coerceParamValue(cookieValue, paramSchema, coerceCookieDates, openapi.components.schemas);
491
539
  args[firstCookieIndex][c.name] = coerced;
492
540
  }
493
541
  } else {
494
542
  for (const c of route.args.cookies) {
495
543
  const cookieValue = cookies[c.name];
496
- const coerced = coerceValue(cookieValue, c.schemaType);
544
+ const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "cookie", c.name) ?? (c.schemaRef ? getSchemaByRef(c.schemaRef) : null) ?? schemaFromType(c.schemaType);
545
+ const coerced = coerceParamValue(cookieValue, paramSchema, coerceCookieDates, openapi.components.schemas);
497
546
  args[c.index] = coerced;
498
547
  }
499
548
  }
@@ -512,8 +561,53 @@ async function createExpressRouter(options) {
512
561
  }
513
562
  return router;
514
563
  }
564
+ function getDateCoercionOptions(coerce, location) {
565
+ const enabled = coerce[location];
566
+ return {
567
+ dateTime: enabled && coerce.dateTime,
568
+ date: enabled && coerce.date
569
+ };
570
+ }
571
+ function toOpenApiPath(path3) {
572
+ return path3.replace(/:([^/]+)/g, "{$1}");
573
+ }
574
+ function getOpenApiOperation(openapi, route) {
575
+ const pathKey = toOpenApiPath(route.fullPath);
576
+ const pathItem = openapi.paths?.[pathKey];
577
+ if (!pathItem) return null;
578
+ return pathItem[route.httpMethod.toLowerCase()] ?? null;
579
+ }
580
+ function buildParamSchemaIndex(operation) {
581
+ const index = /* @__PURE__ */ new Map();
582
+ const params = operation?.parameters ?? [];
583
+ for (const param of params) {
584
+ if (!param?.name || !param?.in) continue;
585
+ if (param.schema) {
586
+ index.set(`${param.in}:${param.name}`, param.schema);
587
+ }
588
+ }
589
+ return index;
590
+ }
591
+ function getParamSchemaFromIndex(index, location, name) {
592
+ return index.get(`${location}:${name}`) ?? null;
593
+ }
594
+ function getRequestBodySchema(operation, contentType) {
595
+ const content = operation?.requestBody?.content;
596
+ if (!content) return null;
597
+ if (contentType && content[contentType]?.schema) {
598
+ return content[contentType].schema;
599
+ }
600
+ const first = Object.values(content)[0];
601
+ return first?.schema ?? null;
602
+ }
603
+ function schemaFromType(schemaType) {
604
+ if (!schemaType) return null;
605
+ return { type: schemaType };
606
+ }
515
607
  function validateRequestWithPrecompiled(route, req, validators) {
516
608
  const errors = [];
609
+ const deepNames = new Set(route.args.query.filter((q) => q.serialization?.style === "deepObject").map((q) => q.name));
610
+ const deepValues = deepNames.size > 0 ? parseDeepObjectParams(getRawQueryString(req), deepNames) : {};
517
611
  if (route.args.body) {
518
612
  const validator = validators[route.operationId]?.body;
519
613
  if (validator) {
@@ -532,14 +626,10 @@ function validateRequestWithPrecompiled(route, req, validators) {
532
626
  }
533
627
  }
534
628
  for (const q of route.args.query) {
535
- const value = req.query[q.name];
629
+ const value = q.serialization?.style === "deepObject" ? deepValues[q.name] : req.query[q.name];
536
630
  if (value === void 0) continue;
537
- const schema = {};
538
- if (q.schemaType) {
539
- const type = Array.isArray(q.schemaType) ? q.schemaType[0] : q.schemaType;
540
- schema.type = type;
541
- }
542
- const coerced = coerceValue(value, q.schemaType);
631
+ const schema = schemaFromType(q.schemaType) ?? {};
632
+ const coerced = coerceParamValue(value, schema, { dateTime: false, date: false }, {});
543
633
  if (Object.keys(schema).length > 0 && coerced !== void 0) {
544
634
  errors.push({
545
635
  path: `#/query/${q.name}`,
@@ -552,12 +642,8 @@ function validateRequestWithPrecompiled(route, req, validators) {
552
642
  for (const p of route.args.path) {
553
643
  const value = req.params[p.name];
554
644
  if (value === void 0) continue;
555
- const schema = {};
556
- if (p.schemaType) {
557
- const type = Array.isArray(p.schemaType) ? p.schemaType[0] : p.schemaType;
558
- schema.type = type;
559
- }
560
- const coerced = coerceValue(value, p.schemaType);
645
+ const schema = schemaFromType(p.schemaType) ?? {};
646
+ const coerced = coerceParamValue(value, schema, { dateTime: false, date: false }, {});
561
647
  if (Object.keys(schema).length > 0 && coerced !== void 0) {
562
648
  errors.push({
563
649
  path: `#/path/${p.name}`,
@@ -575,6 +661,10 @@ function validateRequest(route, req, openapi, validator) {
575
661
  const schemaName = ref.replace("#/components/schemas/", "");
576
662
  return openapi.components.schemas[schemaName] || null;
577
663
  }
664
+ const openapiOperation = getOpenApiOperation(openapi, route);
665
+ const paramSchemaIndex = buildParamSchemaIndex(openapiOperation);
666
+ const deepNames = new Set(route.args.query.filter((q) => q.serialization?.style === "deepObject").map((q) => q.name));
667
+ const deepValues = deepNames.size > 0 ? parseDeepObjectParams(getRawQueryString(req), deepNames) : {};
578
668
  const errors = [];
579
669
  if (route.args.body) {
580
670
  const bodySchema = getSchemaByRef(route.args.body.schemaRef);
@@ -594,19 +684,24 @@ function validateRequest(route, req, openapi, validator) {
594
684
  }
595
685
  }
596
686
  for (const q of route.args.query) {
597
- const value = req.query[q.name];
598
- const schema = {};
599
- if (q.schemaType) {
600
- const type = Array.isArray(q.schemaType) ? q.schemaType[0] : q.schemaType;
601
- schema.type = type;
602
- }
603
- if (q.schemaRef && q.schemaRef.includes("Inline")) {
604
- const inlineSchema = getSchemaByRef(q.schemaRef);
605
- if (inlineSchema) {
606
- Object.assign(schema, inlineSchema);
687
+ const value = q.serialization?.style === "deepObject" ? deepValues[q.name] : req.query[q.name];
688
+ const openapiSchema = getParamSchemaFromIndex(paramSchemaIndex, "query", q.name);
689
+ let schema = {};
690
+ if (openapiSchema) {
691
+ schema = resolveSchema(openapiSchema, openapi.components.schemas);
692
+ } else {
693
+ if (q.schemaType) {
694
+ const type = Array.isArray(q.schemaType) ? q.schemaType[0] : q.schemaType;
695
+ schema.type = type;
696
+ }
697
+ if (q.schemaRef && q.schemaRef.includes("Inline")) {
698
+ const inlineSchema = getSchemaByRef(q.schemaRef);
699
+ if (inlineSchema) {
700
+ Object.assign(schema, inlineSchema);
701
+ }
607
702
  }
608
703
  }
609
- const coerced = coerceValue(value, q.schemaType);
704
+ const coerced = coerceParamValue(value, schema, { dateTime: false, date: false }, openapi.components.schemas);
610
705
  if (Object.keys(schema).length > 0 && coerced !== void 0) {
611
706
  const validate = validator.compile(schema);
612
707
  const valid = validate(coerced);
@@ -624,18 +719,23 @@ function validateRequest(route, req, openapi, validator) {
624
719
  }
625
720
  for (const p of route.args.path) {
626
721
  const value = req.params[p.name];
627
- const schema = {};
628
- if (p.schemaType) {
629
- const type = Array.isArray(p.schemaType) ? p.schemaType[0] : p.schemaType;
630
- schema.type = type;
631
- }
632
- if (p.schemaRef && p.schemaRef.includes("Inline")) {
633
- const inlineSchema = getSchemaByRef(p.schemaRef);
634
- if (inlineSchema) {
635
- Object.assign(schema, inlineSchema);
722
+ const openapiSchema = getParamSchemaFromIndex(paramSchemaIndex, "path", p.name);
723
+ let schema = {};
724
+ if (openapiSchema) {
725
+ schema = resolveSchema(openapiSchema, openapi.components.schemas);
726
+ } else {
727
+ if (p.schemaType) {
728
+ const type = Array.isArray(p.schemaType) ? p.schemaType[0] : p.schemaType;
729
+ schema.type = type;
730
+ }
731
+ if (p.schemaRef && p.schemaRef.includes("Inline")) {
732
+ const inlineSchema = getSchemaByRef(p.schemaRef);
733
+ if (inlineSchema) {
734
+ Object.assign(schema, inlineSchema);
735
+ }
636
736
  }
637
737
  }
638
- const coerced = coerceValue(value, p.schemaType);
738
+ const coerced = coerceParamValue(value, schema, { dateTime: false, date: false }, openapi.components.schemas);
639
739
  if (Object.keys(schema).length > 0 && coerced !== void 0) {
640
740
  const validate = validator.compile(schema);
641
741
  const valid = validate(coerced);
@@ -653,23 +753,127 @@ function validateRequest(route, req, openapi, validator) {
653
753
  }
654
754
  return errors.length > 0 ? errors : null;
655
755
  }
656
- function coerceValue(value, schemaType) {
756
+ function resolveSchema(schema, components, seen = /* @__PURE__ */ new Set()) {
757
+ const ref = schema.$ref;
758
+ if (typeof ref !== "string" || !ref.startsWith("#/components/schemas/")) {
759
+ return schema;
760
+ }
761
+ const name = ref.replace("#/components/schemas/", "");
762
+ if (seen.has(name)) return schema;
763
+ const next = components[name];
764
+ if (!next) return schema;
765
+ seen.add(name);
766
+ return resolveSchema(next, components, seen);
767
+ }
768
+ function coerceDatesWithSchema(value, schema, dateCoercion, components) {
769
+ if (!schema || !dateCoercion.date && !dateCoercion.dateTime) return value;
770
+ return coerceWithSchema(value, schema, dateCoercion, components, { coercePrimitives: false });
771
+ }
772
+ function coerceParamValue(value, schema, dateCoercion, components) {
773
+ if (!schema) return value;
774
+ return coerceWithSchema(value, schema, dateCoercion, components, { coercePrimitives: true });
775
+ }
776
+ function coerceWithSchema(value, schema, dateCoercion, components, options) {
657
777
  if (value === void 0 || value === null) return value;
658
- const type = Array.isArray(schemaType) ? schemaType[0] : schemaType;
659
- if (type === "number" || type === "integer") {
778
+ if (value instanceof Date) return value;
779
+ const resolved = resolveSchema(schema, components);
780
+ const override = resolved["x-adorn-coerce"];
781
+ const allowDateTime = override === true ? true : override === false ? false : dateCoercion.dateTime;
782
+ const allowDate = override === true ? true : override === false ? false : dateCoercion.date;
783
+ const byFormat = coerceDateString(value, resolved, allowDateTime, allowDate);
784
+ if (byFormat !== value) return byFormat;
785
+ const allOf = resolved.allOf;
786
+ if (Array.isArray(allOf)) {
787
+ let out = value;
788
+ for (const entry of allOf) {
789
+ out = coerceWithSchema(out, entry, { dateTime: allowDateTime, date: allowDate }, components, options);
790
+ }
791
+ return out;
792
+ }
793
+ const variants = resolved.oneOf ?? resolved.anyOf;
794
+ if (Array.isArray(variants)) {
795
+ for (const entry of variants) {
796
+ const out = coerceWithSchema(value, entry, { dateTime: allowDateTime, date: allowDate }, components, options);
797
+ if (out !== value) return out;
798
+ }
799
+ }
800
+ const schemaType = resolved.type;
801
+ const types = Array.isArray(schemaType) ? schemaType : schemaType ? [schemaType] : [];
802
+ if ((types.includes("array") || resolved.items) && Array.isArray(value)) {
803
+ const itemSchema = resolved.items ?? {};
804
+ return value.map((item) => coerceWithSchema(item, itemSchema, { dateTime: allowDateTime, date: allowDate }, components, options));
805
+ }
806
+ if ((types.includes("object") || resolved.properties || resolved.additionalProperties) && isPlainObject(value)) {
807
+ const props = resolved.properties;
808
+ const out = { ...value };
809
+ if (props) {
810
+ for (const [key, propSchema] of Object.entries(props)) {
811
+ if (Object.prototype.hasOwnProperty.call(value, key)) {
812
+ out[key] = coerceWithSchema(value[key], propSchema, { dateTime: allowDateTime, date: allowDate }, components, options);
813
+ }
814
+ }
815
+ }
816
+ const additional = resolved.additionalProperties;
817
+ if (additional && typeof additional === "object") {
818
+ for (const [key, entry] of Object.entries(value)) {
819
+ if (props && Object.prototype.hasOwnProperty.call(props, key)) continue;
820
+ out[key] = coerceWithSchema(entry, additional, { dateTime: allowDateTime, date: allowDate }, components, options);
821
+ }
822
+ }
823
+ return out;
824
+ }
825
+ if (options.coercePrimitives) {
826
+ return coercePrimitiveValue(value, types);
827
+ }
828
+ return value;
829
+ }
830
+ function coerceDateString(value, schema, allowDateTime, allowDate) {
831
+ if (typeof value !== "string") return value;
832
+ const format = schema.format;
833
+ const schemaType = schema.type;
834
+ const types = Array.isArray(schemaType) ? schemaType : schemaType ? [schemaType] : [];
835
+ const allowsString = types.length === 0 || types.includes("string");
836
+ if (format === "date-time" && allowDateTime && allowsString) {
837
+ const parsed = new Date(value);
838
+ if (Number.isNaN(parsed.getTime())) {
839
+ throw new Error(`Invalid date-time: ${value}`);
840
+ }
841
+ return parsed;
842
+ }
843
+ if (format === "date" && allowDate && allowsString) {
844
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
845
+ throw new Error(`Invalid date: ${value}`);
846
+ }
847
+ const parsed = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
848
+ if (Number.isNaN(parsed.getTime())) {
849
+ throw new Error(`Invalid date: ${value}`);
850
+ }
851
+ return parsed;
852
+ }
853
+ return value;
854
+ }
855
+ function coercePrimitiveValue(value, types) {
856
+ if (value === void 0 || value === null) return value;
857
+ if (types.includes("number") || types.includes("integer")) {
660
858
  const num = Number(value);
661
- if (isNaN(num)) {
859
+ if (Number.isNaN(num)) {
662
860
  throw new Error(`Invalid number: ${value}`);
663
861
  }
664
862
  return num;
665
863
  }
666
- if (type === "boolean") {
864
+ if (types.includes("boolean")) {
667
865
  if (value === "true") return true;
668
866
  if (value === "false") return false;
867
+ if (typeof value === "boolean") return value;
669
868
  throw new Error(`Invalid boolean: ${value}`);
670
869
  }
671
870
  return value;
672
871
  }
872
+ function isPlainObject(value) {
873
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
874
+ const proto = Object.getPrototypeOf(value);
875
+ return proto === Object.prototype || proto === null;
876
+ }
673
877
  function parseQueryValue(value, param) {
674
878
  if (value === void 0 || value === null) return value;
675
879
  const isArray = Array.isArray(param.schemaType) ? param.schemaType.includes("array") : param.schemaType === "array";
@@ -693,6 +897,69 @@ function parseQueryValue(value, param) {
693
897
  }
694
898
  return value;
695
899
  }
900
+ function getRawQueryString(req) {
901
+ const url = req.originalUrl ?? req.url ?? "";
902
+ const index = url.indexOf("?");
903
+ if (index === -1) return "";
904
+ return url.slice(index + 1);
905
+ }
906
+ function parseDeepObjectParams(rawQuery, names) {
907
+ const out = {};
908
+ if (!rawQuery || names.size === 0) return out;
909
+ const params = new URLSearchParams(rawQuery);
910
+ for (const [key, value] of params.entries()) {
911
+ const path3 = parseBracketPath(key);
912
+ if (path3.length === 0) continue;
913
+ const root = path3[0];
914
+ if (!names.has(root)) continue;
915
+ assignDeepValue(out, path3, value);
916
+ }
917
+ return out;
918
+ }
919
+ function parseBracketPath(key) {
920
+ const parts = [];
921
+ let current = "";
922
+ for (let i = 0; i < key.length; i++) {
923
+ const ch = key[i];
924
+ if (ch === "[") {
925
+ if (current) parts.push(current);
926
+ current = "";
927
+ continue;
928
+ }
929
+ if (ch === "]") {
930
+ if (current) parts.push(current);
931
+ current = "";
932
+ continue;
933
+ }
934
+ current += ch;
935
+ }
936
+ if (current) parts.push(current);
937
+ return parts;
938
+ }
939
+ function assignDeepValue(target, path3, value) {
940
+ let cursor = target;
941
+ for (let i = 0; i < path3.length; i++) {
942
+ const key = path3[i];
943
+ if (!key) continue;
944
+ const isLast = i === path3.length - 1;
945
+ if (isLast) {
946
+ const existing = cursor[key];
947
+ if (existing === void 0) {
948
+ cursor[key] = value;
949
+ } else if (Array.isArray(existing)) {
950
+ existing.push(value);
951
+ } else {
952
+ cursor[key] = [existing, value];
953
+ }
954
+ return;
955
+ }
956
+ const next = cursor[key];
957
+ if (!isPlainObject(next)) {
958
+ cursor[key] = {};
959
+ }
960
+ cursor = cursor[key];
961
+ }
962
+ }
696
963
  function parseCookies(cookieHeader) {
697
964
  if (!cookieHeader) return {};
698
965
  const cookies = {};