@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.cjs CHANGED
@@ -48,6 +48,7 @@ const Html404ErrorPage = (error) => `<!DOCTYPE html>
48
48
  * You can either return `new RedirectView(url)` or `RedirectView.redirect(url)` inside of a controller method.
49
49
  */
50
50
  var RedirectView = class RedirectView {
51
+ _url;
51
52
  constructor(url) {
52
53
  this._url = url;
53
54
  }
@@ -143,8 +144,10 @@ let HttpStatus = /* @__PURE__ */ function(HttpStatus) {
143
144
  * @typeParam T - the type of the response body
144
145
  */
145
146
  var ResponseEntity = class {
147
+ _statusCode;
148
+ _headers = {};
149
+ _body;
146
150
  constructor(body, headers = {}, statusCode = 200) {
147
- this._headers = {};
148
151
  this._body = body;
149
152
  this._headers = headers;
150
153
  this._statusCode = statusCode;
@@ -197,8 +200,9 @@ var ResponseEntity = class {
197
200
  * ensuring type safety when constructing the response.
198
201
  */
199
202
  var ResponseEntityBuilder = class {
203
+ _statusCode;
204
+ _headers = {};
200
205
  constructor(statusCode) {
201
- this._headers = {};
202
206
  this._statusCode = statusCode;
203
207
  }
204
208
  /**
@@ -230,6 +234,7 @@ var ResponseEntityBuilder = class {
230
234
  * @see {@link Sapling.loadResponseStatusErrorMiddleware}
231
235
  */
232
236
  var ResponseStatusError = class ResponseStatusError extends Error {
237
+ status;
233
238
  constructor(status, message) {
234
239
  super(message ?? "Something went wrong.");
235
240
  this.status = status;
@@ -241,26 +246,27 @@ var ResponseStatusError = class ResponseStatusError extends Error {
241
246
  //#endregion
242
247
  //#region src/helper/error/parse.ts
243
248
  /**
244
- * This error should be thrown when some data cannot be parsed by a given schema.
249
+ * This error should be thrown when some data cannot be parsed by a given Standard Schema compatible schema.
245
250
  */
246
251
  var ParserError = class ParserError extends ResponseStatusError {
247
- constructor(location, issues, vendor) {
248
- super(400, ParserError.formatMessage(location, issues, vendor));
252
+ constructor(location, issues, vendor, functionName) {
253
+ super(400, ParserError.formatMessage(location, issues, vendor, functionName));
249
254
  Object.setPrototypeOf(this, new.target.prototype);
250
255
  }
251
- static formatMessage(location, issues, vendor) {
256
+ static formatMessage(location, issues, vendor, functionName) {
252
257
  const formatted = issues.map((i) => {
253
258
  const path = Array.isArray(i.path) ? i.path.map((seg) => typeof seg === "object" && seg ? String(seg.key) : String(seg)).join(".") : "";
254
259
  return path ? `${path}: ${i.message}` : i.message;
255
260
  }).join("; ");
256
- return `${vendor} failed to parse ${(() => {
257
- switch (location) {
258
- case "reqbody": return "request body";
259
- case "reqparams": return "request params";
260
- case "reqquery": return "request query";
261
- case "resbody": return "response body";
262
- }
263
- })()}: ${formatted}`;
261
+ return `Failed to parse ${this.getPrettyLocationString(location)} with ${vendor} on ${functionName}: ${formatted}`;
262
+ }
263
+ static getPrettyLocationString(location) {
264
+ switch (location) {
265
+ case "reqbody": return "request body";
266
+ case "reqparams": return "request params";
267
+ case "reqquery": return "request query";
268
+ case "resbody": return "response body";
269
+ }
264
270
  }
265
271
  };
266
272
  //#endregion
@@ -270,7 +276,11 @@ const _settings = {
270
276
  deserialize: JSON.parse,
271
277
  doc: {
272
278
  openApiPath: "/openapi.json",
273
- swaggerPath: "/swagger.html"
279
+ swaggerPath: "/swagger.html",
280
+ metadata: {
281
+ title: "API",
282
+ version: "1.0.0"
283
+ }
274
284
  }
275
285
  };
276
286
  /**
@@ -376,12 +386,59 @@ var Sapling = class Sapling {
376
386
  static setDeserializeFn(fn) {
377
387
  _settings.deserialize = fn;
378
388
  }
379
- static setOpenApiPath(path) {
380
- _settings.doc.openApiPath = path;
381
- }
382
- static setSwaggerPath(path) {
383
- _settings.doc.swaggerPath = path;
384
- }
389
+ /**
390
+ * Modify extra settings
391
+ */
392
+ static Extras = {
393
+ /**
394
+ * Modify default settings applied to OpenAPI & Swagger
395
+ */
396
+ swaggerAndOpenApi: {
397
+ /**
398
+ * Set base OpenAPI metadata values.
399
+ *
400
+ * @default { title: "API", version: "1.0.0" }
401
+ */
402
+ setMetadata(metadata) {
403
+ _settings.doc.metadata = metadata;
404
+ },
405
+ /**
406
+ * change default endpoint that will serve OpenAPI spec.
407
+ * Swagger will also load this endpoint on load.
408
+ *
409
+ * @default `/openapi.json`
410
+ */
411
+ setOpenApiPath(path) {
412
+ _settings.doc.openApiPath = path;
413
+ },
414
+ /**
415
+ * change Swagger endpoint.
416
+ *
417
+ * @default `/swagger.html`
418
+ */
419
+ setSwaggerPath(path) {
420
+ _settings.doc.swaggerPath = path;
421
+ }
422
+ } };
423
+ /**
424
+ * This method can be used in a `@MiddlewareClass` to register any libraries
425
+ * that expect you to register multiple registers at once. An example is `swagger-ui-express`
426
+ *
427
+ * @example
428
+ * ```ts
429
+ * ⠀@MiddlewareClass()
430
+ * class Serve {
431
+ * // `swagger.serve` returns multiple Express handlers for all the assets and routes
432
+ * // that will be served
433
+ * private readonly handlers: RequestHandler[] = swagger.serve;
434
+ *
435
+ * ⠀@Middleware(_settings.doc.swaggerPath)
436
+ * handle(request: Request, response: Response, next: NextFunction) {
437
+ * return Sapling.chainHandlers(this.handlers, request, response, next);
438
+ * }
439
+ * }
440
+ * ```
441
+ */
385
442
  static chainHandlers(handlers, request, response, next, index = 0) {
386
443
  if (index >= handlers.length) {
387
444
  next();
@@ -478,18 +535,33 @@ function _getRoutes(ctor) {
478
535
  return _routeStore.get(ctor) ?? [];
479
536
  }
480
537
  //#endregion
481
- //#region src/utils.ts
482
- function _getOrCreateMap(store, ctor) {
538
+ //#region src/annotation/schema.ts
539
+ function ControllerSchema(options) {
540
+ return (target) => {
541
+ _setControllerSchema(target, options);
542
+ };
543
+ }
544
+ function RouteSchema(options) {
545
+ return (target, propertyKey) => {
546
+ const ctor = target.constructor;
547
+ _setRouteSchema(ctor, String(propertyKey), options);
548
+ };
549
+ }
550
+ const _routeSchemaStore = /* @__PURE__ */ new WeakMap();
551
+ const _controllerSchemaStore = /* @__PURE__ */ new WeakMap();
552
+ function getOrCreateRouteSchemaStore(store, ctor) {
483
553
  const existing = store.get(ctor);
484
554
  if (existing) return existing;
485
555
  const created = /* @__PURE__ */ new Map();
486
556
  store.set(ctor, created);
487
557
  return created;
488
558
  }
489
- //#endregion
490
- //#region src/annotation/schema.ts
491
- const _routeSchemaStore = /* @__PURE__ */ new WeakMap();
492
- const _controllerSchemaStore = /* @__PURE__ */ new WeakMap();
559
+ function _setRouteSchema(ctor, fnName, options) {
560
+ getOrCreateRouteSchemaStore(_routeSchemaStore, ctor).set(fnName, options);
561
+ }
562
+ function _setControllerSchema(ctor, options) {
563
+ _controllerSchemaStore.set(ctor, options);
564
+ }
493
565
  function _getRouteSchema(ctor, fnName) {
494
566
  return _routeSchemaStore.get(ctor)?.get(fnName);
495
567
  }
@@ -497,26 +569,78 @@ function _getControllerSchema(ctor) {
497
569
  return _controllerSchemaStore.get(ctor);
498
570
  }
499
571
  //#endregion
572
+ //#region src/annotation/validator.ts
573
+ const _validatorSchemaStore = /* @__PURE__ */ new WeakMap();
574
+ function ResponseBody(schema) {
575
+ return (target, propertyKey) => {
576
+ const ctor = target.constructor;
577
+ const fnName = String(propertyKey);
578
+ _saveValidatorSchema(_getOrCreateSchemaDefinition(ctor, fnName), "responseBody", schema, fnName);
579
+ };
580
+ }
581
+ function RequestBody(schema) {
582
+ return (target, propertyKey) => {
583
+ const ctor = target.constructor;
584
+ const fnName = String(propertyKey);
585
+ _saveValidatorSchema(_getOrCreateSchemaDefinition(ctor, fnName), "requestBody", schema, fnName);
586
+ };
587
+ }
588
+ function RequestParam(schema) {
589
+ return (target, propertyKey) => {
590
+ const ctor = target.constructor;
591
+ const fnName = String(propertyKey);
592
+ _saveValidatorSchema(_getOrCreateSchemaDefinition(ctor, fnName), "requestParam", schema, fnName);
593
+ };
594
+ }
595
+ function RequestQuery(schema) {
596
+ return (target, propertyKey) => {
597
+ const ctor = target.constructor;
598
+ const fnName = String(propertyKey);
599
+ _saveValidatorSchema(_getOrCreateSchemaDefinition(ctor, fnName), "requestQuery", schema, fnName);
600
+ };
601
+ }
602
+ function getOrCreateValidatorSchemaStore(store, ctor) {
603
+ const existing = store.get(ctor);
604
+ if (existing) return existing;
605
+ const created = /* @__PURE__ */ new Map();
606
+ store.set(ctor, created);
607
+ return created;
608
+ }
609
+ function _getOrCreateSchemaDefinition(ctor, fnName) {
610
+ const byFn = getOrCreateValidatorSchemaStore(_validatorSchemaStore, ctor);
611
+ const existing = byFn.get(fnName);
612
+ if (existing) return existing;
613
+ const created = {};
614
+ byFn.set(fnName, created);
615
+ return created;
616
+ }
617
+ async function _parseOrThrow(schema, input, location, fnName) {
618
+ const result = await schema["~standard"].validate(input);
619
+ if (result.issues) throw new ParserError(location, result.issues, schema["~standard"].vendor, fnName);
620
+ return result.value;
621
+ }
622
+ function _saveValidatorSchema(def, key, schema, fnName) {
623
+ if (def[key]) throw new Error(`Duplicate schema for "${String(key)}" on method "${fnName}"`);
624
+ def[key] = schema;
625
+ }
626
+ function _getValidatorSchema(ctor, fnName) {
627
+ return _validatorSchemaStore.get(ctor)?.get(fnName);
628
+ }
629
+ //#endregion
500
630
  //#region src/helper/openapi.ts
501
631
  var OpenAPIGenerator = class {
502
- constructor() {
503
- this.controllers = /* @__PURE__ */ new Set();
504
- this.config = {
505
- title: "API",
506
- version: "1.0.0"
507
- };
508
- }
509
- setConfig(config) {
510
- this.config = config;
511
- }
632
+ controllers = /* @__PURE__ */ new Set();
512
633
  registerController(controllerClass, prefix) {
513
634
  this.controllers.add({
514
635
  class: controllerClass,
515
636
  prefix
516
637
  });
517
638
  }
639
+ get metadata() {
640
+ return _settings.doc.metadata;
641
+ }
518
642
  generateSpec() {
519
- const config = this.config;
643
+ const metadata = this.metadata;
520
644
  const paths = {};
521
645
  const tags = [];
522
646
  for (const { class: controllerClass, prefix } of this.controllers) {
@@ -531,50 +655,70 @@ var OpenAPIGenerator = class {
531
655
  const schemas = _getValidatorSchema(controllerClass, route.fnName);
532
656
  const routeSchema = _getRouteSchema(controllerClass, route.fnName);
533
657
  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.`);
534
- const openApiPath = prefix + route.path;
658
+ const openApiPath = (prefix + route.path).replace(/:([A-Za-z0-9_]+)/g, "{$1}");
535
659
  if (!paths[openApiPath]) paths[openApiPath] = {};
536
660
  const responses = {};
537
- if (schemas?.responseBody) responses["200"] = {
538
- description: "Successful response",
539
- content: { "application/json": { schema: this.toJsonSchema(schemas.responseBody) } }
540
- };
541
- else responses["200"] = { description: "Successful response" };
542
- if (routeSchema?.responses) for (const resp of routeSchema.responses) responses[String(resp.statusCode)] = {
543
- description: `Response ${resp.statusCode}`,
544
- content: { "application/json": { schema: this.toJsonSchema(resp.schema) } }
545
- };
661
+ if (schemas?.responseBody) {
662
+ const responseSchema = this.toJsonSchema(schemas.responseBody, "output");
663
+ responses["200"] = {
664
+ description: responseSchema.description ?? "Successful response",
665
+ content: { "application/json": { schema: responseSchema } }
666
+ };
667
+ } else responses["200"] = { description: "Successful response" };
668
+ if (routeSchema?.responses) for (const resp of routeSchema.responses) {
669
+ const statusCode = String(resp.statusCode);
670
+ const existingResponse = responses[statusCode];
671
+ const existingSchema = existingResponse && "content" in existingResponse ? existingResponse.content?.["application/json"]?.schema : void 0;
672
+ const responseSchema = resp.schema ? this.toJsonSchema(resp.schema, "output") : statusCode === "200" ? existingSchema : void 0;
673
+ responses[statusCode] = {
674
+ ...existingResponse,
675
+ description: resp.description ?? responseSchema?.description ?? existingResponse?.description ?? `Response ${resp.statusCode}`,
676
+ ...responseSchema ? { content: { "application/json": { schema: responseSchema } } } : {}
677
+ };
678
+ }
546
679
  const operation = {
547
680
  responses,
681
+ summary: routeSchema?.summary,
548
682
  description: routeSchema?.description,
549
683
  tags: controllerSchema?.title ? [controllerSchema.title] : void 0
550
684
  };
551
685
  const parameters = [];
552
686
  if (schemas?.requestParam) {
553
- const paramSchema = this.toJsonSchema(schemas.requestParam);
554
- if (paramSchema.type === "object" && paramSchema.properties) for (const [name, schema] of Object.entries(paramSchema.properties)) parameters.push({
555
- name,
556
- in: "path",
557
- required: true,
558
- schema
559
- });
687
+ const paramSchema = this.toJsonSchema(schemas.requestParam, "input");
688
+ if (paramSchema.type === "object" && paramSchema.properties) for (const [name, schema] of Object.entries(paramSchema.properties)) {
689
+ const parameterSchema = schema;
690
+ parameters.push({
691
+ name,
692
+ in: "path",
693
+ required: true,
694
+ description: parameterSchema.description,
695
+ schema: parameterSchema
696
+ });
697
+ }
560
698
  }
561
699
  if (schemas?.requestQuery) {
562
- const querySchema = this.toJsonSchema(schemas.requestQuery);
700
+ const querySchema = this.toJsonSchema(schemas.requestQuery, "input");
563
701
  if (querySchema.type === "object" && querySchema.properties) for (const [name, schema] of Object.entries(querySchema.properties)) {
564
702
  const isRequired = Array.isArray(querySchema.required) && querySchema.required.includes(name);
703
+ const parameterSchema = schema;
565
704
  parameters.push({
566
705
  name,
567
706
  in: "query",
568
707
  required: isRequired,
569
- schema
708
+ description: parameterSchema.description,
709
+ schema: parameterSchema
570
710
  });
571
711
  }
572
712
  }
573
713
  if (parameters.length > 0) operation.parameters = parameters;
574
- if (schemas?.requestBody) operation.requestBody = {
575
- required: true,
576
- content: { "application/json": { schema: this.toJsonSchema(schemas.requestBody) } }
577
- };
714
+ if (schemas?.requestBody) {
715
+ const requestSchema = this.toJsonSchema(schemas.requestBody, "input");
716
+ operation.requestBody = {
717
+ required: true,
718
+ description: requestSchema.description,
719
+ content: { "application/json": { schema: requestSchema } }
720
+ };
721
+ }
578
722
  const method = route.method.toLowerCase();
579
723
  paths[openApiPath][method] = operation;
580
724
  }
@@ -582,30 +726,28 @@ var OpenAPIGenerator = class {
582
726
  return {
583
727
  openapi: "3.0.0",
584
728
  info: {
585
- title: config.title,
586
- version: config.version,
587
- description: config.description
729
+ title: metadata.title,
730
+ version: metadata.version,
731
+ description: metadata.description
588
732
  },
589
733
  tags: tags.length > 0 ? tags : void 0,
590
734
  paths
591
735
  };
592
736
  }
593
- toJsonSchema(schema) {
737
+ toJsonSchema(schema, direction = "output") {
594
738
  try {
595
- return schema["~standard"].jsonSchema.output({ target: "openapi-3.0" });
739
+ const jsonSchema = schema["~standard"].jsonSchema;
740
+ return direction === "input" ? jsonSchema.input({ target: "openapi-3.0" }) : jsonSchema.output({ target: "openapi-3.0" });
596
741
  } catch (e) {
597
- 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`);
742
+ 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 });
598
743
  throw e;
599
744
  }
600
745
  }
601
746
  };
602
747
  const openApiGenerator = new OpenAPIGenerator();
603
- function _registerControllerClass(controllerClass, prefix) {
748
+ function _registerController(controllerClass, prefix) {
604
749
  openApiGenerator.registerController(controllerClass, prefix);
605
750
  }
606
- function setOpenApiConfig(config) {
607
- openApiGenerator.setConfig(config);
608
- }
609
751
  function generateOpenApiSpec() {
610
752
  return openApiGenerator.generateSpec();
611
753
  }
@@ -742,60 +884,6 @@ function _resolve(ctor) {
742
884
  return _InjectableRegistry.get(ctor);
743
885
  }
744
886
  //#endregion
745
- //#region src/annotation/validator.ts
746
- const _validatorSchemaStore = /* @__PURE__ */ new WeakMap();
747
- function ResponseBody(schema) {
748
- return (target, propertyKey) => {
749
- const ctor = target.constructor;
750
- const fnName = String(propertyKey);
751
- _setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "responseBody", schema, fnName);
752
- };
753
- }
754
- function RequestBody(schema) {
755
- return (target, propertyKey) => {
756
- const ctor = target.constructor;
757
- const fnName = String(propertyKey);
758
- _setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestBody", schema, fnName);
759
- };
760
- }
761
- function RequestParam(schema) {
762
- return (target, propertyKey) => {
763
- const ctor = target.constructor;
764
- const fnName = String(propertyKey);
765
- _setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestParam", schema, fnName);
766
- };
767
- }
768
- function RequestQuery(schema) {
769
- return (target, propertyKey) => {
770
- const ctor = target.constructor;
771
- const fnName = String(propertyKey);
772
- _setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestQuery", schema, fnName);
773
- };
774
- }
775
- function _getOrCreateSchemaDefinition(ctor, fnName) {
776
- const byFn = _getOrCreateMap(_validatorSchemaStore, ctor);
777
- const existing = byFn.get(fnName);
778
- if (existing) return existing;
779
- const created = {};
780
- byFn.set(fnName, created);
781
- return created;
782
- }
783
- async function _parseOrThrow(schema, input, kind) {
784
- const result = await schema["~standard"].validate(input);
785
- if (result.issues) {
786
- console.debug(`Failed to parse a schema`);
787
- throw new ParserError(kind, result.issues, schema["~standard"].vendor);
788
- }
789
- return result.value;
790
- }
791
- function _getValidatorSchema(ctor, fnName) {
792
- return _validatorSchemaStore.get(ctor)?.get(fnName);
793
- }
794
- function _setOnce(def, key, schema, fnName) {
795
- if (def[key]) throw new Error(`Duplicate schema for "${String(key)}" on method "${fnName}"`);
796
- def[key] = schema;
797
- }
798
- //#endregion
799
887
  //#region src/annotation/controller.ts
800
888
  const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
801
889
  /**
@@ -807,7 +895,7 @@ const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
807
895
  function Controller({ prefix = "", deps = [] } = {}) {
808
896
  return (target) => {
809
897
  const targetClass = target;
810
- _registerControllerClass(target, prefix);
898
+ _registerController(target, prefix);
811
899
  const router = (0, express.Router)();
812
900
  const routes = _getRoutes(target);
813
901
  const usedRoutes = /* @__PURE__ */ new Set();
@@ -832,15 +920,20 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
832
920
  if (method === "USE" && fn.length >= 4) {
833
921
  const middlewareFn = async (err, request, response, next) => {
834
922
  try {
835
- const result = fn.bind(controllerInstance)(err, request, response, next);
836
- if (result instanceof ResponseEntity) {
837
- response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(result.getBody()));
838
- return;
839
- }
840
- if (result instanceof RedirectView) {
841
- response.redirect(result.getUrl());
842
- return;
843
- }
923
+ await validate({
924
+ target,
925
+ fnName,
926
+ request
927
+ });
928
+ await handleResult({
929
+ result: fn.bind(controllerInstance)(err, request, response, next),
930
+ response,
931
+ target,
932
+ fnName,
933
+ method,
934
+ path: path instanceof RegExp ? path.source : fp,
935
+ isErrorMiddleware: true
936
+ });
844
937
  } catch (e) {
845
938
  console.error(e);
846
939
  next(e);
@@ -850,35 +943,52 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
850
943
  return;
851
944
  }
852
945
  router[methodName](fp, async (request, response, next) => {
853
- const schemas = _getValidatorSchema(target, fnName);
854
- if (schemas) {
855
- if (schemas.requestBody) request.body = await _parseOrThrow(schemas.requestBody, request.body, "reqbody");
856
- if (schemas.requestParam) request.params = await _parseOrThrow(schemas.requestParam, request.params, "reqparams");
857
- if (schemas.requestQuery) {
858
- const parsedQuery = await _parseOrThrow(schemas.requestQuery, request.query, "reqquery");
859
- Object.defineProperty(request, "query", {
860
- value: parsedQuery,
861
- writable: true,
862
- configurable: true
863
- });
864
- }
865
- }
866
- const result = await fn.bind(controllerInstance)(request, response, next);
867
- if (result instanceof ResponseEntity) {
868
- const body = schemas && schemas.responseBody ? await _parseOrThrow(schemas.responseBody, result.getBody(), "resbody") : result.getBody();
869
- response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(body));
870
- return;
871
- }
872
- if (result instanceof RedirectView) {
873
- response.redirect(result.getUrl());
874
- return;
875
- }
876
- if (method !== "USE" && !response.writableEnded) response.status(404).send(Html404ErrorPage(`Cannot ${methodName.toUpperCase()} ${path instanceof RegExp ? path.source : fp}`));
946
+ await validate({
947
+ target,
948
+ fnName,
949
+ request
950
+ });
951
+ await handleResult({
952
+ result: await fn.bind(controllerInstance)(request, response, next),
953
+ response,
954
+ target,
955
+ fnName,
956
+ method,
957
+ path: path instanceof RegExp ? path.source : fp
958
+ });
877
959
  });
878
960
  }
879
961
  _ControllerRegistry.set(targetClass, router);
880
962
  };
881
963
  }
964
+ async function handleResult({ result, target, fnName, response, method, path, isErrorMiddleware = false }) {
965
+ const schemas = _getValidatorSchema(target, fnName);
966
+ if (result instanceof ResponseEntity) {
967
+ const body = schemas && schemas.responseBody ? await _parseOrThrow(schemas.responseBody, result.getBody(), "resbody", fnName) : result.getBody();
968
+ response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(body));
969
+ return;
970
+ }
971
+ if (result instanceof RedirectView) {
972
+ response.redirect(result.getUrl());
973
+ return;
974
+ }
975
+ if (!isErrorMiddleware && method !== "USE" && !response.writableEnded) response.status(404).send(Html404ErrorPage(`Cannot ${method} ${path}`));
976
+ }
977
+ async function validate({ target, fnName, request }) {
978
+ const schemas = _getValidatorSchema(target, fnName);
979
+ if (schemas) {
980
+ if (schemas.requestBody) request.body = await _parseOrThrow(schemas.requestBody, request.body, "reqbody", fnName);
981
+ if (schemas.requestParam) request.params = await _parseOrThrow(schemas.requestParam, request.params, "reqparams", fnName);
982
+ if (schemas.requestQuery) {
983
+ const parsedQuery = await _parseOrThrow(schemas.requestQuery, request.query, "reqquery", fnName);
984
+ Object.defineProperty(request, "query", {
985
+ value: parsedQuery,
986
+ writable: true,
987
+ configurable: true
988
+ });
989
+ }
990
+ }
991
+ }
882
992
  //#endregion
883
993
  //#region src/annotation/middleware.ts
884
994
  /**
@@ -913,7 +1023,10 @@ DefaultBaseErrorMiddleware = __decorate([MiddlewareClass()], DefaultBaseErrorMid
913
1023
  //#region src/middleware/default/error/parse.ts
914
1024
  let DefaultParserErrorMiddleware = class DefaultParserErrorMiddleware {
915
1025
  handle(err, _request, _response, next) {
916
- if (err instanceof ParserError) return ResponseEntity.status(err.status).body({ message: err.message });
1026
+ if (err instanceof ParserError) {
1027
+ console.warn(err);
1028
+ return ResponseEntity.status(err.status).body({ message: err.message });
1029
+ }
917
1030
  next(err);
918
1031
  }
919
1032
  };
@@ -940,17 +1053,48 @@ __decorate([GET(_settings.doc.openApiPath)], DefaultOpenApiMiddleware.prototype,
940
1053
  DefaultOpenApiMiddleware = __decorate([MiddlewareClass()], DefaultOpenApiMiddleware);
941
1054
  //#endregion
942
1055
  //#region src/middleware/default/swagger/index.ts
1056
+ /**
1057
+ * Enable the serving of the Swagger endpoint used to serve the OpenAPI spec generated by Sapling.
1058
+ *
1059
+ * Configure any middleware-specific settings with `Sapling.Extras.swaggerAndOpenApi`
1060
+ *
1061
+ * You must register `DefaultSwaggerMiddleware.Serve` & `DefaultSwaggerMiddleware.Setup` after `DefaultOpenApiMiddleware`
1062
+ *
1063
+ * ```ts
1064
+ * const middlewares = [
1065
+ * DefaultOpenApiMiddleware,
1066
+ * DefaultSwaggerMiddleware.Serve,
1067
+ * DefaultSwaggerMiddleware.Setup,
1068
+ * ];
1069
+ * middlewares.map(Sapling.resolve).forEach((r) => app.use(r));
1070
+ * ```
1071
+ */
943
1072
  let Serve = class Serve {
944
- constructor() {
945
- this.handlers = swagger_ui_express.default.serve;
946
- }
1073
+ handlers = swagger_ui_express.default.serve;
947
1074
  handle(request, response, next) {
948
1075
  return Sapling.chainHandlers(this.handlers, request, response, next);
949
1076
  }
950
1077
  };
951
1078
  __decorate([Middleware(_settings.doc.swaggerPath)], Serve.prototype, "handle", null);
952
1079
  Serve = __decorate([MiddlewareClass()], Serve);
1080
+ /**
1081
+ * Enable the serving of the Swagger endpoint used to serve the OpenAPI spec generated by Sapling.
1082
+ *
1083
+ * Configure any middleware-specific settings with `Sapling.Extras.swaggerAndOpenApi`
1084
+ *
1085
+ * You must register `DefaultSwaggerMiddleware.Serve` & `DefaultSwaggerMiddleware.Setup` after `DefaultOpenApiMiddleware`
1086
+ *
1087
+ * ```ts
1088
+ * const middlewares = [
1089
+ * DefaultOpenApiMiddleware,
1090
+ * DefaultSwaggerMiddleware.Serve,
1091
+ * DefaultSwaggerMiddleware.Setup,
1092
+ * ];
1093
+ * middlewares.map(Sapling.resolve).forEach((r) => app.use(r));
1094
+ * ```
1095
+ */
953
1096
  let Setup = class Setup {
1097
+ handler;
954
1098
  constructor() {
955
1099
  this.handler = swagger_ui_express.default.setup(null, { swaggerOptions: { url: _settings.doc.openApiPath } });
956
1100
  }
@@ -960,12 +1104,29 @@ let Setup = class Setup {
960
1104
  };
961
1105
  __decorate([Middleware(_settings.doc.swaggerPath)], Setup.prototype, "handle", null);
962
1106
  Setup = __decorate([MiddlewareClass()], Setup);
1107
+ /**
1108
+ * Enable the serving of the Swagger endpoint used to serve the OpenAPI spec generated by Sapling.
1109
+ *
1110
+ * Configure any middleware-specific settings with `Sapling.Extras.swaggerAndOpenApi`
1111
+ *
1112
+ * You must register `DefaultSwaggerMiddleware.Serve` & `DefaultSwaggerMiddleware.Setup` after `DefaultOpenApiMiddleware`
1113
+ *
1114
+ * ```ts
1115
+ * const middlewares = [
1116
+ * DefaultOpenApiMiddleware,
1117
+ * DefaultSwaggerMiddleware.Serve,
1118
+ * DefaultSwaggerMiddleware.Setup,
1119
+ * ];
1120
+ * middlewares.map(Sapling.resolve).forEach((r) => app.use(r));
1121
+ * ```
1122
+ */
963
1123
  const DefaultSwaggerMiddleware = {
964
1124
  Serve,
965
1125
  Setup
966
1126
  };
967
1127
  //#endregion
968
1128
  exports.Controller = Controller;
1129
+ exports.ControllerSchema = ControllerSchema;
969
1130
  exports.DELETE = DELETE;
970
1131
  Object.defineProperty(exports, "DefaultBaseErrorMiddleware", {
971
1132
  enumerable: true,
@@ -1012,20 +1173,24 @@ exports.ResponseBody = ResponseBody;
1012
1173
  exports.ResponseEntity = ResponseEntity;
1013
1174
  exports.ResponseEntityBuilder = ResponseEntityBuilder;
1014
1175
  exports.ResponseStatusError = ResponseStatusError;
1176
+ exports.RouteSchema = RouteSchema;
1015
1177
  exports.Sapling = Sapling;
1016
1178
  exports._ControllerRegistry = _ControllerRegistry;
1017
1179
  exports._InjectableDeps = _InjectableDeps;
1018
1180
  exports._InjectableRegistry = _InjectableRegistry;
1019
1181
  exports._Route = _Route;
1182
+ exports._getControllerSchema = _getControllerSchema;
1020
1183
  exports._getOrCreateSchemaDefinition = _getOrCreateSchemaDefinition;
1184
+ exports._getRouteSchema = _getRouteSchema;
1021
1185
  exports._getRoutes = _getRoutes;
1022
1186
  exports._getValidatorSchema = _getValidatorSchema;
1023
1187
  exports._parseOrThrow = _parseOrThrow;
1024
- exports._registerControllerClass = _registerControllerClass;
1188
+ exports._registerController = _registerController;
1025
1189
  exports._resolve = _resolve;
1026
- exports._setOnce = _setOnce;
1190
+ exports._saveValidatorSchema = _saveValidatorSchema;
1191
+ exports._setControllerSchema = _setControllerSchema;
1192
+ exports._setRouteSchema = _setRouteSchema;
1027
1193
  exports._settings = _settings;
1028
1194
  exports.generateOpenApiSpec = generateOpenApiSpec;
1029
1195
  exports.methodResolve = methodResolve;
1030
1196
  exports.openApiGenerator = openApiGenerator;
1031
- exports.setOpenApiConfig = setOpenApiConfig;