@tahminator/sapling 2.0.5-beta.c70dc62b → 2.0.5-beta.e0403942

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/index.mjs CHANGED
@@ -23,6 +23,7 @@ const Html404ErrorPage = (error) => `<!DOCTYPE html>
23
23
  * You can either return `new RedirectView(url)` or `RedirectView.redirect(url)` inside of a controller method.
24
24
  */
25
25
  var RedirectView = class RedirectView {
26
+ _url;
26
27
  constructor(url) {
27
28
  this._url = url;
28
29
  }
@@ -118,8 +119,10 @@ let HttpStatus = /* @__PURE__ */ function(HttpStatus) {
118
119
  * @typeParam T - the type of the response body
119
120
  */
120
121
  var ResponseEntity = class {
122
+ _statusCode;
123
+ _headers = {};
124
+ _body;
121
125
  constructor(body, headers = {}, statusCode = 200) {
122
- this._headers = {};
123
126
  this._body = body;
124
127
  this._headers = headers;
125
128
  this._statusCode = statusCode;
@@ -172,8 +175,9 @@ var ResponseEntity = class {
172
175
  * ensuring type safety when constructing the response.
173
176
  */
174
177
  var ResponseEntityBuilder = class {
178
+ _statusCode;
179
+ _headers = {};
175
180
  constructor(statusCode) {
176
- this._headers = {};
177
181
  this._statusCode = statusCode;
178
182
  }
179
183
  /**
@@ -205,6 +209,7 @@ var ResponseEntityBuilder = class {
205
209
  * @see {@link Sapling.loadResponseStatusErrorMiddleware}
206
210
  */
207
211
  var ResponseStatusError = class ResponseStatusError extends Error {
212
+ status;
208
213
  constructor(status, message) {
209
214
  super(message ?? "Something went wrong.");
210
215
  this.status = status;
@@ -216,26 +221,27 @@ var ResponseStatusError = class ResponseStatusError extends Error {
216
221
  //#endregion
217
222
  //#region src/helper/error/parse.ts
218
223
  /**
219
- * This error should be thrown when some data cannot be parsed by a given schema.
224
+ * This error should be thrown when some data cannot be parsed by a given Standard Schema compatible schema.
220
225
  */
221
226
  var ParserError = class ParserError extends ResponseStatusError {
222
- constructor(location, issues, vendor) {
223
- super(400, ParserError.formatMessage(location, issues, vendor));
227
+ constructor(location, issues, vendor, functionName) {
228
+ super(400, ParserError.formatMessage(location, issues, vendor, functionName));
224
229
  Object.setPrototypeOf(this, new.target.prototype);
225
230
  }
226
- static formatMessage(location, issues, vendor) {
231
+ static formatMessage(location, issues, vendor, functionName) {
227
232
  const formatted = issues.map((i) => {
228
233
  const path = Array.isArray(i.path) ? i.path.map((seg) => typeof seg === "object" && seg ? String(seg.key) : String(seg)).join(".") : "";
229
234
  return path ? `${path}: ${i.message}` : i.message;
230
235
  }).join("; ");
231
- return `${vendor} failed to parse ${(() => {
232
- switch (location) {
233
- case "reqbody": return "request body";
234
- case "reqparams": return "request params";
235
- case "reqquery": return "request query";
236
- case "resbody": return "response body";
237
- }
238
- })()}: ${formatted}`;
236
+ return `Failed to parse ${this.getPrettyLocationString(location)} with ${vendor} on ${functionName}: ${formatted}`;
237
+ }
238
+ static getPrettyLocationString(location) {
239
+ switch (location) {
240
+ case "reqbody": return "request body";
241
+ case "reqparams": return "request params";
242
+ case "reqquery": return "request query";
243
+ case "resbody": return "response body";
244
+ }
239
245
  }
240
246
  };
241
247
  //#endregion
@@ -245,7 +251,11 @@ const _settings = {
245
251
  deserialize: JSON.parse,
246
252
  doc: {
247
253
  openApiPath: "/openapi.json",
248
- swaggerPath: "/swagger.html"
254
+ swaggerPath: "/swagger.html",
255
+ metadata: {
256
+ title: "API",
257
+ version: "1.0.0"
258
+ }
249
259
  }
250
260
  };
251
261
  /**
@@ -351,12 +361,59 @@ var Sapling = class Sapling {
351
361
  static setDeserializeFn(fn) {
352
362
  _settings.deserialize = fn;
353
363
  }
354
- static setOpenApiPath(path) {
355
- _settings.doc.openApiPath = path;
356
- }
357
- static setSwaggerPath(path) {
358
- _settings.doc.swaggerPath = path;
359
- }
364
+ /**
365
+ * Modify extra settings
366
+ */
367
+ static Extras = {
368
+ /**
369
+ * Modify default settings applied to OpenAPI & Swagger
370
+ */
371
+ swaggerAndOpenApi: {
372
+ /**
373
+ * Set base OpenAPI metadata values.
374
+ *
375
+ * @default { title: "API", version: "1.0.0" }
376
+ */
377
+ setMetadata(metadata) {
378
+ _settings.doc.metadata = metadata;
379
+ },
380
+ /**
381
+ * change default endpoint that will serve OpenAPI spec.
382
+ * Swagger will also load this endpoint on load.
383
+ *
384
+ * @default `/openapi.json`
385
+ */
386
+ setOpenApiPath(path) {
387
+ _settings.doc.openApiPath = path;
388
+ },
389
+ /**
390
+ * change Swagger endpoint.
391
+ *
392
+ * @default `/swagger.html`
393
+ */
394
+ setSwaggerPath(path) {
395
+ _settings.doc.swaggerPath = path;
396
+ }
397
+ } };
398
+ /**
399
+ * This method can be used in a `@MiddlewareClass` to register any libraries
400
+ * that expect you to register multiple registers at once. An example is `swagger-ui-express`
401
+ *
402
+ * @example
403
+ * ```ts
404
+ * ⠀@MiddlewareClass()
405
+ * class Serve {
406
+ * // `swagger.serve` returns multiple Express handlers for all the assets and routes
407
+ * // that will be served
408
+ * private readonly handlers: RequestHandler[] = swagger.serve;
409
+ *
410
+ * ⠀@Middleware(_settings.doc.swaggerPath)
411
+ * handle(request: Request, response: Response, next: NextFunction) {
412
+ * return Sapling.chainHandlers(this.handlers, request, response, next);
413
+ * }
414
+ * }
415
+ * ```
416
+ */
360
417
  static chainHandlers(handlers, request, response, next, index = 0) {
361
418
  if (index >= handlers.length) {
362
419
  next();
@@ -453,18 +510,33 @@ function _getRoutes(ctor) {
453
510
  return _routeStore.get(ctor) ?? [];
454
511
  }
455
512
  //#endregion
456
- //#region src/utils.ts
457
- function _getOrCreateMap(store, ctor) {
513
+ //#region src/annotation/schema.ts
514
+ function ControllerSchema(options) {
515
+ return (target) => {
516
+ _setControllerSchema(target, options);
517
+ };
518
+ }
519
+ function RouteSchema(options) {
520
+ return (target, propertyKey) => {
521
+ const ctor = target.constructor;
522
+ _setRouteSchema(ctor, String(propertyKey), options);
523
+ };
524
+ }
525
+ const _routeSchemaStore = /* @__PURE__ */ new WeakMap();
526
+ const _controllerSchemaStore = /* @__PURE__ */ new WeakMap();
527
+ function getOrCreateRouteSchemaStore(store, ctor) {
458
528
  const existing = store.get(ctor);
459
529
  if (existing) return existing;
460
530
  const created = /* @__PURE__ */ new Map();
461
531
  store.set(ctor, created);
462
532
  return created;
463
533
  }
464
- //#endregion
465
- //#region src/annotation/schema.ts
466
- const _routeSchemaStore = /* @__PURE__ */ new WeakMap();
467
- const _controllerSchemaStore = /* @__PURE__ */ new WeakMap();
534
+ function _setRouteSchema(ctor, fnName, options) {
535
+ getOrCreateRouteSchemaStore(_routeSchemaStore, ctor).set(fnName, options);
536
+ }
537
+ function _setControllerSchema(ctor, options) {
538
+ _controllerSchemaStore.set(ctor, options);
539
+ }
468
540
  function _getRouteSchema(ctor, fnName) {
469
541
  return _routeSchemaStore.get(ctor)?.get(fnName);
470
542
  }
@@ -472,26 +544,78 @@ function _getControllerSchema(ctor) {
472
544
  return _controllerSchemaStore.get(ctor);
473
545
  }
474
546
  //#endregion
547
+ //#region src/annotation/validator.ts
548
+ const _validatorSchemaStore = /* @__PURE__ */ new WeakMap();
549
+ function ResponseBody(schema) {
550
+ return (target, propertyKey) => {
551
+ const ctor = target.constructor;
552
+ const fnName = String(propertyKey);
553
+ _saveValidatorSchema(_getOrCreateSchemaDefinition(ctor, fnName), "responseBody", schema, fnName);
554
+ };
555
+ }
556
+ function RequestBody(schema) {
557
+ return (target, propertyKey) => {
558
+ const ctor = target.constructor;
559
+ const fnName = String(propertyKey);
560
+ _saveValidatorSchema(_getOrCreateSchemaDefinition(ctor, fnName), "requestBody", schema, fnName);
561
+ };
562
+ }
563
+ function RequestParam(schema) {
564
+ return (target, propertyKey) => {
565
+ const ctor = target.constructor;
566
+ const fnName = String(propertyKey);
567
+ _saveValidatorSchema(_getOrCreateSchemaDefinition(ctor, fnName), "requestParam", schema, fnName);
568
+ };
569
+ }
570
+ function RequestQuery(schema) {
571
+ return (target, propertyKey) => {
572
+ const ctor = target.constructor;
573
+ const fnName = String(propertyKey);
574
+ _saveValidatorSchema(_getOrCreateSchemaDefinition(ctor, fnName), "requestQuery", schema, fnName);
575
+ };
576
+ }
577
+ function getOrCreateValidatorSchemaStore(store, ctor) {
578
+ const existing = store.get(ctor);
579
+ if (existing) return existing;
580
+ const created = /* @__PURE__ */ new Map();
581
+ store.set(ctor, created);
582
+ return created;
583
+ }
584
+ function _getOrCreateSchemaDefinition(ctor, fnName) {
585
+ const byFn = getOrCreateValidatorSchemaStore(_validatorSchemaStore, ctor);
586
+ const existing = byFn.get(fnName);
587
+ if (existing) return existing;
588
+ const created = {};
589
+ byFn.set(fnName, created);
590
+ return created;
591
+ }
592
+ async function _parseOrThrow(schema, input, location, fnName) {
593
+ const result = await schema["~standard"].validate(input);
594
+ if (result.issues) throw new ParserError(location, result.issues, schema["~standard"].vendor, fnName);
595
+ return result.value;
596
+ }
597
+ function _saveValidatorSchema(def, key, schema, fnName) {
598
+ if (def[key]) throw new Error(`Duplicate schema for "${String(key)}" on method "${fnName}"`);
599
+ def[key] = schema;
600
+ }
601
+ function _getValidatorSchema(ctor, fnName) {
602
+ return _validatorSchemaStore.get(ctor)?.get(fnName);
603
+ }
604
+ //#endregion
475
605
  //#region src/helper/openapi.ts
476
606
  var OpenAPIGenerator = class {
477
- constructor() {
478
- this.controllers = /* @__PURE__ */ new Set();
479
- this.config = {
480
- title: "API",
481
- version: "1.0.0"
482
- };
483
- }
484
- setConfig(config) {
485
- this.config = config;
486
- }
607
+ controllers = /* @__PURE__ */ new Set();
487
608
  registerController(controllerClass, prefix) {
488
609
  this.controllers.add({
489
610
  class: controllerClass,
490
611
  prefix
491
612
  });
492
613
  }
614
+ get metadata() {
615
+ return _settings.doc.metadata;
616
+ }
493
617
  generateSpec() {
494
- const config = this.config;
618
+ const metadata = this.metadata;
495
619
  const paths = {};
496
620
  const tags = [];
497
621
  for (const { class: controllerClass, prefix } of this.controllers) {
@@ -506,50 +630,70 @@ var OpenAPIGenerator = class {
506
630
  const schemas = _getValidatorSchema(controllerClass, route.fnName);
507
631
  const routeSchema = _getRouteSchema(controllerClass, route.fnName);
508
632
  if (route.path instanceof RegExp) throw new Error(`You have a route with a regex path of ${route.path.source}. This is not compatible with OpenAPI.`);
509
- const openApiPath = prefix + route.path;
633
+ const openApiPath = (prefix + route.path).replace(/:([A-Za-z0-9_]+)/g, "{$1}");
510
634
  if (!paths[openApiPath]) paths[openApiPath] = {};
511
635
  const responses = {};
512
- if (schemas?.responseBody) responses["200"] = {
513
- description: "Successful response",
514
- content: { "application/json": { schema: this.toJsonSchema(schemas.responseBody) } }
515
- };
516
- else responses["200"] = { description: "Successful response" };
517
- if (routeSchema?.responses) for (const resp of routeSchema.responses) responses[String(resp.statusCode)] = {
518
- description: `Response ${resp.statusCode}`,
519
- content: { "application/json": { schema: this.toJsonSchema(resp.schema) } }
520
- };
636
+ if (schemas?.responseBody) {
637
+ const responseSchema = this.toJsonSchema(schemas.responseBody, "output");
638
+ responses["200"] = {
639
+ description: responseSchema.description ?? "Successful response",
640
+ content: { "application/json": { schema: responseSchema } }
641
+ };
642
+ } else responses["200"] = { description: "Successful response" };
643
+ if (routeSchema?.responses) for (const resp of routeSchema.responses) {
644
+ const statusCode = String(resp.statusCode);
645
+ const existingResponse = responses[statusCode];
646
+ const existingSchema = existingResponse && "content" in existingResponse ? existingResponse.content?.["application/json"]?.schema : void 0;
647
+ const responseSchema = resp.schema ? this.toJsonSchema(resp.schema, "output") : statusCode === "200" ? existingSchema : void 0;
648
+ responses[statusCode] = {
649
+ ...existingResponse,
650
+ description: resp.description ?? responseSchema?.description ?? existingResponse?.description ?? `Response ${resp.statusCode}`,
651
+ ...responseSchema ? { content: { "application/json": { schema: responseSchema } } } : {}
652
+ };
653
+ }
521
654
  const operation = {
522
655
  responses,
656
+ summary: routeSchema?.summary,
523
657
  description: routeSchema?.description,
524
658
  tags: controllerSchema?.title ? [controllerSchema.title] : void 0
525
659
  };
526
660
  const parameters = [];
527
661
  if (schemas?.requestParam) {
528
- const paramSchema = this.toJsonSchema(schemas.requestParam);
529
- if (paramSchema.type === "object" && paramSchema.properties) for (const [name, schema] of Object.entries(paramSchema.properties)) parameters.push({
530
- name,
531
- in: "path",
532
- required: true,
533
- schema
534
- });
662
+ const paramSchema = this.toJsonSchema(schemas.requestParam, "input");
663
+ if (paramSchema.type === "object" && paramSchema.properties) for (const [name, schema] of Object.entries(paramSchema.properties)) {
664
+ const parameterSchema = schema;
665
+ parameters.push({
666
+ name,
667
+ in: "path",
668
+ required: true,
669
+ description: parameterSchema.description,
670
+ schema: parameterSchema
671
+ });
672
+ }
535
673
  }
536
674
  if (schemas?.requestQuery) {
537
- const querySchema = this.toJsonSchema(schemas.requestQuery);
675
+ const querySchema = this.toJsonSchema(schemas.requestQuery, "input");
538
676
  if (querySchema.type === "object" && querySchema.properties) for (const [name, schema] of Object.entries(querySchema.properties)) {
539
677
  const isRequired = Array.isArray(querySchema.required) && querySchema.required.includes(name);
678
+ const parameterSchema = schema;
540
679
  parameters.push({
541
680
  name,
542
681
  in: "query",
543
682
  required: isRequired,
544
- schema
683
+ description: parameterSchema.description,
684
+ schema: parameterSchema
545
685
  });
546
686
  }
547
687
  }
548
688
  if (parameters.length > 0) operation.parameters = parameters;
549
- if (schemas?.requestBody) operation.requestBody = {
550
- required: true,
551
- content: { "application/json": { schema: this.toJsonSchema(schemas.requestBody) } }
552
- };
689
+ if (schemas?.requestBody) {
690
+ const requestSchema = this.toJsonSchema(schemas.requestBody, "input");
691
+ operation.requestBody = {
692
+ required: true,
693
+ description: requestSchema.description,
694
+ content: { "application/json": { schema: requestSchema } }
695
+ };
696
+ }
553
697
  const method = route.method.toLowerCase();
554
698
  paths[openApiPath][method] = operation;
555
699
  }
@@ -557,30 +701,28 @@ var OpenAPIGenerator = class {
557
701
  return {
558
702
  openapi: "3.0.0",
559
703
  info: {
560
- title: config.title,
561
- version: config.version,
562
- description: config.description
704
+ title: metadata.title,
705
+ version: metadata.version,
706
+ description: metadata.description
563
707
  },
564
708
  tags: tags.length > 0 ? tags : void 0,
565
709
  paths
566
710
  };
567
711
  }
568
- toJsonSchema(schema) {
712
+ toJsonSchema(schema, direction = "output") {
569
713
  try {
570
- return schema["~standard"].jsonSchema.output({ target: "openapi-3.0" });
714
+ const jsonSchema = schema["~standard"].jsonSchema;
715
+ return direction === "input" ? jsonSchema.input({ target: "openapi-3.0" }) : jsonSchema.output({ target: "openapi-3.0" });
571
716
  } catch (e) {
572
- if (e instanceof Error && e.message.includes("Transforms cannot be represented in JSON Schema")) throw new Error(`${e.message}.\nIt appears that you are using z.transform() - it is highly recommended that you use z.codec instead - https://zod.dev/codecs`);
717
+ if (e instanceof Error && e.message.includes("Transforms cannot be represented in JSON Schema")) throw new Error(`${e.message}.\nIt appears that you are using z.transform() - it is highly recommended that you use z.codec instead - https://zod.dev/codecs`, { cause: e });
573
718
  throw e;
574
719
  }
575
720
  }
576
721
  };
577
722
  const openApiGenerator = new OpenAPIGenerator();
578
- function _registerControllerClass(controllerClass, prefix) {
723
+ function _registerController(controllerClass, prefix) {
579
724
  openApiGenerator.registerController(controllerClass, prefix);
580
725
  }
581
- function setOpenApiConfig(config) {
582
- openApiGenerator.setConfig(config);
583
- }
584
726
  function generateOpenApiSpec() {
585
727
  return openApiGenerator.generateSpec();
586
728
  }
@@ -717,60 +859,6 @@ function _resolve(ctor) {
717
859
  return _InjectableRegistry.get(ctor);
718
860
  }
719
861
  //#endregion
720
- //#region src/annotation/validator.ts
721
- const _validatorSchemaStore = /* @__PURE__ */ new WeakMap();
722
- function ResponseBody(schema) {
723
- return (target, propertyKey) => {
724
- const ctor = target.constructor;
725
- const fnName = String(propertyKey);
726
- _setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "responseBody", schema, fnName);
727
- };
728
- }
729
- function RequestBody(schema) {
730
- return (target, propertyKey) => {
731
- const ctor = target.constructor;
732
- const fnName = String(propertyKey);
733
- _setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestBody", schema, fnName);
734
- };
735
- }
736
- function RequestParam(schema) {
737
- return (target, propertyKey) => {
738
- const ctor = target.constructor;
739
- const fnName = String(propertyKey);
740
- _setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestParam", schema, fnName);
741
- };
742
- }
743
- function RequestQuery(schema) {
744
- return (target, propertyKey) => {
745
- const ctor = target.constructor;
746
- const fnName = String(propertyKey);
747
- _setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestQuery", schema, fnName);
748
- };
749
- }
750
- function _getOrCreateSchemaDefinition(ctor, fnName) {
751
- const byFn = _getOrCreateMap(_validatorSchemaStore, ctor);
752
- const existing = byFn.get(fnName);
753
- if (existing) return existing;
754
- const created = {};
755
- byFn.set(fnName, created);
756
- return created;
757
- }
758
- async function _parseOrThrow(schema, input, kind) {
759
- const result = await schema["~standard"].validate(input);
760
- if (result.issues) {
761
- console.debug(`Failed to parse a schema`);
762
- throw new ParserError(kind, result.issues, schema["~standard"].vendor);
763
- }
764
- return result.value;
765
- }
766
- function _getValidatorSchema(ctor, fnName) {
767
- return _validatorSchemaStore.get(ctor)?.get(fnName);
768
- }
769
- function _setOnce(def, key, schema, fnName) {
770
- if (def[key]) throw new Error(`Duplicate schema for "${String(key)}" on method "${fnName}"`);
771
- def[key] = schema;
772
- }
773
- //#endregion
774
862
  //#region src/annotation/controller.ts
775
863
  const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
776
864
  /**
@@ -782,7 +870,7 @@ const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
782
870
  function Controller({ prefix = "", deps = [] } = {}) {
783
871
  return (target) => {
784
872
  const targetClass = target;
785
- _registerControllerClass(target, prefix);
873
+ _registerController(target, prefix);
786
874
  const router = Router();
787
875
  const routes = _getRoutes(target);
788
876
  const usedRoutes = /* @__PURE__ */ new Set();
@@ -807,15 +895,20 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
807
895
  if (method === "USE" && fn.length >= 4) {
808
896
  const middlewareFn = async (err, request, response, next) => {
809
897
  try {
810
- const result = fn.bind(controllerInstance)(err, request, response, next);
811
- if (result instanceof ResponseEntity) {
812
- response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(result.getBody()));
813
- return;
814
- }
815
- if (result instanceof RedirectView) {
816
- response.redirect(result.getUrl());
817
- return;
818
- }
898
+ await validate({
899
+ target,
900
+ fnName,
901
+ request
902
+ });
903
+ await handleResult({
904
+ result: fn.bind(controllerInstance)(err, request, response, next),
905
+ response,
906
+ target,
907
+ fnName,
908
+ method,
909
+ path: path instanceof RegExp ? path.source : fp,
910
+ isErrorMiddleware: true
911
+ });
819
912
  } catch (e) {
820
913
  console.error(e);
821
914
  next(e);
@@ -825,35 +918,52 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
825
918
  return;
826
919
  }
827
920
  router[methodName](fp, async (request, response, next) => {
828
- const schemas = _getValidatorSchema(target, fnName);
829
- if (schemas) {
830
- if (schemas.requestBody) request.body = await _parseOrThrow(schemas.requestBody, request.body, "reqbody");
831
- if (schemas.requestParam) request.params = await _parseOrThrow(schemas.requestParam, request.params, "reqparams");
832
- if (schemas.requestQuery) {
833
- const parsedQuery = await _parseOrThrow(schemas.requestQuery, request.query, "reqquery");
834
- Object.defineProperty(request, "query", {
835
- value: parsedQuery,
836
- writable: true,
837
- configurable: true
838
- });
839
- }
840
- }
841
- const result = await fn.bind(controllerInstance)(request, response, next);
842
- if (result instanceof ResponseEntity) {
843
- const body = schemas && schemas.responseBody ? await _parseOrThrow(schemas.responseBody, result.getBody(), "resbody") : result.getBody();
844
- response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(body));
845
- return;
846
- }
847
- if (result instanceof RedirectView) {
848
- response.redirect(result.getUrl());
849
- return;
850
- }
851
- if (method !== "USE" && !response.writableEnded) response.status(404).send(Html404ErrorPage(`Cannot ${methodName.toUpperCase()} ${path instanceof RegExp ? path.source : fp}`));
921
+ await validate({
922
+ target,
923
+ fnName,
924
+ request
925
+ });
926
+ await handleResult({
927
+ result: await fn.bind(controllerInstance)(request, response, next),
928
+ response,
929
+ target,
930
+ fnName,
931
+ method,
932
+ path: path instanceof RegExp ? path.source : fp
933
+ });
852
934
  });
853
935
  }
854
936
  _ControllerRegistry.set(targetClass, router);
855
937
  };
856
938
  }
939
+ async function handleResult({ result, target, fnName, response, method, path, isErrorMiddleware = false }) {
940
+ const schemas = _getValidatorSchema(target, fnName);
941
+ if (result instanceof ResponseEntity) {
942
+ const body = schemas && schemas.responseBody ? await _parseOrThrow(schemas.responseBody, result.getBody(), "resbody", fnName) : result.getBody();
943
+ response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(body));
944
+ return;
945
+ }
946
+ if (result instanceof RedirectView) {
947
+ response.redirect(result.getUrl());
948
+ return;
949
+ }
950
+ if (!isErrorMiddleware && method !== "USE" && !response.writableEnded) response.status(404).send(Html404ErrorPage(`Cannot ${method} ${path}`));
951
+ }
952
+ async function validate({ target, fnName, request }) {
953
+ const schemas = _getValidatorSchema(target, fnName);
954
+ if (schemas) {
955
+ if (schemas.requestBody) request.body = await _parseOrThrow(schemas.requestBody, request.body, "reqbody", fnName);
956
+ if (schemas.requestParam) request.params = await _parseOrThrow(schemas.requestParam, request.params, "reqparams", fnName);
957
+ if (schemas.requestQuery) {
958
+ const parsedQuery = await _parseOrThrow(schemas.requestQuery, request.query, "reqquery", fnName);
959
+ Object.defineProperty(request, "query", {
960
+ value: parsedQuery,
961
+ writable: true,
962
+ configurable: true
963
+ });
964
+ }
965
+ }
966
+ }
857
967
  //#endregion
858
968
  //#region src/annotation/middleware.ts
859
969
  /**
@@ -888,7 +998,10 @@ DefaultBaseErrorMiddleware = __decorate([MiddlewareClass()], DefaultBaseErrorMid
888
998
  //#region src/middleware/default/error/parse.ts
889
999
  let DefaultParserErrorMiddleware = class DefaultParserErrorMiddleware {
890
1000
  handle(err, _request, _response, next) {
891
- if (err instanceof ParserError) return ResponseEntity.status(err.status).body({ message: err.message });
1001
+ if (err instanceof ParserError) {
1002
+ console.warn(err);
1003
+ return ResponseEntity.status(err.status).body({ message: err.message });
1004
+ }
892
1005
  next(err);
893
1006
  }
894
1007
  };
@@ -915,17 +1028,48 @@ __decorate([GET(_settings.doc.openApiPath)], DefaultOpenApiMiddleware.prototype,
915
1028
  DefaultOpenApiMiddleware = __decorate([MiddlewareClass()], DefaultOpenApiMiddleware);
916
1029
  //#endregion
917
1030
  //#region src/middleware/default/swagger/index.ts
1031
+ /**
1032
+ * Enable the serving of the Swagger endpoint used to serve the OpenAPI spec generated by Sapling.
1033
+ *
1034
+ * Configure any middleware-specific settings with `Sapling.Extras.swaggerAndOpenApi`
1035
+ *
1036
+ * You must register `DefaultSwaggerMiddleware.Serve` & `DefaultSwaggerMiddleware.Setup` after `DefaultOpenApiMiddleware`
1037
+ *
1038
+ * ```ts
1039
+ * const middlewares = [
1040
+ * DefaultOpenApiMiddleware,
1041
+ * DefaultSwaggerMiddleware.Serve,
1042
+ * DefaultSwaggerMiddleware.Setup,
1043
+ * ];
1044
+ * middlewares.map(Sapling.resolve).forEach((r) => app.use(r));
1045
+ * ```
1046
+ */
918
1047
  let Serve = class Serve {
919
- constructor() {
920
- this.handlers = swagger.serve;
921
- }
1048
+ handlers = swagger.serve;
922
1049
  handle(request, response, next) {
923
1050
  return Sapling.chainHandlers(this.handlers, request, response, next);
924
1051
  }
925
1052
  };
926
1053
  __decorate([Middleware(_settings.doc.swaggerPath)], Serve.prototype, "handle", null);
927
1054
  Serve = __decorate([MiddlewareClass()], Serve);
1055
+ /**
1056
+ * Enable the serving of the Swagger endpoint used to serve the OpenAPI spec generated by Sapling.
1057
+ *
1058
+ * Configure any middleware-specific settings with `Sapling.Extras.swaggerAndOpenApi`
1059
+ *
1060
+ * You must register `DefaultSwaggerMiddleware.Serve` & `DefaultSwaggerMiddleware.Setup` after `DefaultOpenApiMiddleware`
1061
+ *
1062
+ * ```ts
1063
+ * const middlewares = [
1064
+ * DefaultOpenApiMiddleware,
1065
+ * DefaultSwaggerMiddleware.Serve,
1066
+ * DefaultSwaggerMiddleware.Setup,
1067
+ * ];
1068
+ * middlewares.map(Sapling.resolve).forEach((r) => app.use(r));
1069
+ * ```
1070
+ */
928
1071
  let Setup = class Setup {
1072
+ handler;
929
1073
  constructor() {
930
1074
  this.handler = swagger.setup(null, { swaggerOptions: { url: _settings.doc.openApiPath } });
931
1075
  }
@@ -935,9 +1079,25 @@ let Setup = class Setup {
935
1079
  };
936
1080
  __decorate([Middleware(_settings.doc.swaggerPath)], Setup.prototype, "handle", null);
937
1081
  Setup = __decorate([MiddlewareClass()], Setup);
1082
+ /**
1083
+ * Enable the serving of the Swagger endpoint used to serve the OpenAPI spec generated by Sapling.
1084
+ *
1085
+ * Configure any middleware-specific settings with `Sapling.Extras.swaggerAndOpenApi`
1086
+ *
1087
+ * You must register `DefaultSwaggerMiddleware.Serve` & `DefaultSwaggerMiddleware.Setup` after `DefaultOpenApiMiddleware`
1088
+ *
1089
+ * ```ts
1090
+ * const middlewares = [
1091
+ * DefaultOpenApiMiddleware,
1092
+ * DefaultSwaggerMiddleware.Serve,
1093
+ * DefaultSwaggerMiddleware.Setup,
1094
+ * ];
1095
+ * middlewares.map(Sapling.resolve).forEach((r) => app.use(r));
1096
+ * ```
1097
+ */
938
1098
  const DefaultSwaggerMiddleware = {
939
1099
  Serve,
940
1100
  Setup
941
1101
  };
942
1102
  //#endregion
943
- export { Controller, DELETE, DefaultBaseErrorMiddleware, DefaultOpenApiMiddleware, DefaultParserErrorMiddleware, DefaultResponseStatusErrorMiddleware, DefaultSwaggerMiddleware, GET, HEAD, Html404ErrorPage, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, PATCH, POST, PUT, ParserError, RedirectView, RequestBody, RequestParam, RequestQuery, ResponseBody, ResponseEntity, ResponseEntityBuilder, ResponseStatusError, Sapling, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _getOrCreateSchemaDefinition, _getRoutes, _getValidatorSchema, _parseOrThrow, _registerControllerClass, _resolve, _setOnce, _settings, generateOpenApiSpec, methodResolve, openApiGenerator, setOpenApiConfig };
1103
+ export { Controller, ControllerSchema, DELETE, DefaultBaseErrorMiddleware, DefaultOpenApiMiddleware, DefaultParserErrorMiddleware, DefaultResponseStatusErrorMiddleware, DefaultSwaggerMiddleware, GET, HEAD, Html404ErrorPage, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, PATCH, POST, PUT, ParserError, RedirectView, RequestBody, RequestParam, RequestQuery, ResponseBody, ResponseEntity, ResponseEntityBuilder, ResponseStatusError, RouteSchema, Sapling, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _getControllerSchema, _getOrCreateSchemaDefinition, _getRouteSchema, _getRoutes, _getValidatorSchema, _parseOrThrow, _registerController, _resolve, _saveValidatorSchema, _setControllerSchema, _setRouteSchema, _settings, generateOpenApiSpec, methodResolve, openApiGenerator };