@63klabs/cache-data 1.3.9 → 1.3.10

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.
@@ -401,6 +401,7 @@ class ClientRequest extends RequestInfo {
401
401
 
402
402
  /* Validation system */
403
403
  #validationMatchers = {};
404
+ #validationReason = { isValid: true, statusCode: 200, messages: [] };
404
405
 
405
406
  /* The request data */
406
407
  #props = {};
@@ -470,6 +471,10 @@ class ClientRequest extends RequestInfo {
470
471
  bodyParameters: {},
471
472
  bodyPayload: this.#event?.body || null, // from body
472
473
  client: {
474
+ ip: this.getClientIp(),
475
+ userAgent: this.getClientUserAgent(),
476
+ origin: this.getClientOrigin(),
477
+ referrer: this.getClientReferrer(),
473
478
  isAuthenticated: this.isAuthenticated(),
474
479
  isGuest: this.isGuest(),
475
480
  authorizations: this.getAuthorizations(),
@@ -698,7 +703,7 @@ class ClientRequest extends RequestInfo {
698
703
  * Parameter validations
699
704
  * @returns {{
700
705
  * pathParameters?: object,
701
- * queryParameters?: object,
706
+ * queryStringParameters?: object,
702
707
  * headerParameters?: object,
703
708
  * cookieParameters?: object,
704
709
  * bodyParameters?: object
@@ -755,25 +760,129 @@ class ClientRequest extends RequestInfo {
755
760
  */
756
761
  #validate() {
757
762
 
758
- let valid = false;
763
+ const reasons = [];
764
+ let statusCode = 200;
765
+
766
+ // Referrer check
767
+ const referrerValid = this.isAuthorizedReferrer();
768
+ if (!referrerValid) {
769
+ reasons.push("Forbidden");
770
+ statusCode = this.#upgradeStatusCode(statusCode, 403);
771
+ }
772
+
773
+ // Authentication check
774
+ const authFailed = this.hasNoAuthorization();
775
+ if (authFailed) {
776
+ reasons.push("Unauthorized");
777
+ statusCode = this.#upgradeStatusCode(statusCode, 401);
778
+ }
779
+
780
+ // Parameter checks - collect invalid parameter names from each
781
+ const pathResult = this.#hasValidPathParameters();
782
+ const queryResult = this.#hasValidQueryStringParameters();
783
+ const headerResult = this.#hasValidHeaderParameters();
784
+ const cookieResult = this.#hasValidCookieParameters();
785
+ const bodyResult = this.#hasValidBodyParameters();
786
+
787
+ // Collect invalid parameter messages
788
+ const paramResults = [pathResult, queryResult, headerResult, cookieResult, bodyResult];
789
+ for (const result of paramResults) {
790
+ if (result.invalidParams) {
791
+ for (const paramName of result.invalidParams) {
792
+ reasons.push(`Invalid parameter: ${paramName}`);
793
+ statusCode = this.#upgradeStatusCode(statusCode, 400);
794
+ }
795
+ }
796
+ }
759
797
 
760
- // add your additional validations here
761
- valid = this.isAuthorizedReferrer() && this.#hasValidPathParameters() && this.#hasValidQueryStringParameters() && this.#hasValidHeaderParameters() && this.#hasValidCookieParameters() && this.#hasValidBodyParameters();
798
+ // Handle invalid JSON body
799
+ if (bodyResult.invalidBody) {
800
+ reasons.push("Invalid request body");
801
+ statusCode = this.#upgradeStatusCode(statusCode, 400);
802
+ }
762
803
 
763
- // set the variable
804
+ // Compute combined valid boolean from all check results
805
+ const valid = referrerValid && !authFailed
806
+ && pathResult.isValid && queryResult.isValid && headerResult.isValid
807
+ && cookieResult.isValid && bodyResult.isValid;
808
+
809
+ // Preserve backwards compatibility
764
810
  super._isValid = valid;
765
811
 
812
+ // Populate validation reason
813
+ this.#validationReason = {
814
+ isValid: valid,
815
+ statusCode: valid ? 200 : statusCode,
816
+ messages: valid ? [] : reasons
817
+ };
818
+
766
819
  };
767
820
 
821
+ /**
822
+ * Returns the higher-priority HTTP status code between two candidates.
823
+ * Priority order: 401 > 403 > 400 > 200.
824
+ *
825
+ * @private
826
+ * @param {number} current - The current status code
827
+ * @param {number} candidate - The candidate status code to compare
828
+ * @returns {number} The status code with higher priority
829
+ */
830
+ #upgradeStatusCode(current, candidate) {
831
+ const priority = { 401: 3, 403: 2, 400: 1, 200: 0 };
832
+ return (priority[candidate] || 0) > (priority[current] || 0) ? candidate : current;
833
+ };
834
+
835
+ /**
836
+ * Returns a structured validation result object describing why the request
837
+ * passed or failed validation. The object includes the validation status,
838
+ * an appropriate HTTP status code, and descriptive messages identifying
839
+ * each failure.
840
+ *
841
+ * A new object is returned on each call to prevent external mutation of
842
+ * internal state.
843
+ *
844
+ * @returns {{ isValid: boolean, statusCode: number, messages: Array<string> }}
845
+ * A new object on each call containing:
846
+ * - isValid: whether the request passed all validation checks
847
+ * - statusCode: the appropriate HTTP status code (200, 400, 401, or 403)
848
+ * - messages: array of descriptive failure messages (empty when valid)
849
+ * @example
850
+ * // Valid request
851
+ * const reason = clientRequest.getValidationReason();
852
+ * // { isValid: true, statusCode: 200, messages: [] }
853
+ *
854
+ * @example
855
+ * // Invalid request with bad parameters
856
+ * const reason = clientRequest.getValidationReason();
857
+ * // { isValid: false, statusCode: 400, messages: ["Invalid parameter: limit"] }
858
+ */
859
+ getValidationReason() {
860
+ return {
861
+ isValid: this.#validationReason.isValid,
862
+ statusCode: this.#validationReason.statusCode,
863
+ messages: [...this.#validationReason.messages]
864
+ };
865
+ }
866
+
768
867
  /**
769
868
  * Validate parameters using ValidationMatcher and ValidationExecutor.
770
869
  *
771
870
  * This method implements the core parameter validation logic:
772
871
  * 1. Uses ValidationMatcher to find the best matching validation rule (4-tier priority)
773
872
  * 2. Uses ValidationExecutor to execute validation with appropriate interface (single or multi-parameter)
774
- * 3. Extracts validated parameters and returns them
873
+ * 3. Extracts ALL validated parameters specified in the matching rule and returns them
775
874
  * 4. Respects excludeParamsWithNoValidationMatch flag (default: true)
776
875
  *
876
+ * When a validation rule matches and validation passes, ALL parameters specified in rule.params
877
+ * are extracted from clientParameters and included in the returned params object. This ensures
878
+ * that multi-parameter validation rules (e.g., validating query?param1,param2 together) correctly
879
+ * extract all validated parameters, not just the one that triggered the rule match.
880
+ *
881
+ * For single-parameter validation with multi-placeholder routes (e.g., users/{userId}/posts/{id}):
882
+ * - ValidationMatcher returns validateParam field indicating which parameter to validate
883
+ * - ValidationExecutor validates only that parameter with single-parameter interface
884
+ * - This method extracts ALL parameters from rule.params array (e.g., both userId and id)
885
+ *
777
886
  * @private
778
887
  * @param {Object} paramValidations - Parameter validation configuration (may include BY_ROUTE, BY_METHOD, and global validations)
779
888
  * @param {Object} clientParameters - Parameters from the request (path, query, header, or cookie parameters)
@@ -791,12 +900,25 @@ class ClientRequest extends RequestInfo {
791
900
 
792
901
  let rValue = {
793
902
  isValid: true,
794
- params: {}
903
+ params: {},
904
+ invalidParams: []
795
905
  }
796
906
 
797
- if (clientParameters && paramValidations) {
907
+ if (clientParameters) {
798
908
  // >! Check excludeParamsWithNoValidationMatch flag (default: true)
799
909
  const excludeUnmatched = ClientRequest.#validations.parameters?.excludeParamsWithNoValidationMatch !== false;
910
+
911
+ // >! When no validation rules exist for this parameter type,
912
+ // >! pass through all parameters if excludeUnmatched is false
913
+ if (!paramValidations) {
914
+ if (!excludeUnmatched) {
915
+ rValue.params = { ...clientParameters };
916
+ }
917
+ return rValue;
918
+ }
919
+
920
+ // >! Track which parameters have been validated to avoid duplicate validation
921
+ const validatedParams = new Set();
800
922
 
801
923
  // >! Create normalized parameter map for validation execution
802
924
  // >! Include ALL parameter types so multi-parameter validations can access them
@@ -841,12 +963,20 @@ class ClientRequest extends RequestInfo {
841
963
  normalizedParams[normalizedKey] = value;
842
964
  }
843
965
 
966
+ // >! Collect valid params separately so we can clear them if any fail
967
+ const collectedParams = {};
968
+
844
969
  // Use a for...of loop instead of forEach for better control flow
845
970
  for (const [key, value] of Object.entries(clientParameters)) {
846
971
  // >! Preserve existing parameter key normalization
847
972
  const paramKey = key.replace(/^\/|\/$/g, '');
848
973
  const paramValue = value;
849
974
 
975
+ // >! Skip parameters that have already been validated
976
+ if (validatedParams.has(paramKey)) {
977
+ continue;
978
+ }
979
+
850
980
  // >! Use ValidationMatcher to find the best matching validation rule
851
981
  const rule = validationMatcher.findValidationRule(paramKey);
852
982
 
@@ -856,22 +986,41 @@ class ClientRequest extends RequestInfo {
856
986
  const isValid = ValidationExecutor.execute(rule.validate, rule.params, normalizedParams);
857
987
 
858
988
  if (isValid) {
859
- rValue.params[paramKey] = paramValue;
989
+ // >! Extract ALL parameters specified in rule.params when validation passes
990
+ // >! This fixes the bug where only the current paramKey was added
991
+ for (const ruleParamName of rule.params) {
992
+ // >! Find the parameter value in clientParameters
993
+ // >! Use normalized key matching to handle case differences
994
+ const normalizedRuleParam = ruleParamName.replace(/^\/|\/$/g, '');
995
+
996
+ // >! Search for matching parameter in clientParameters
997
+ for (const [clientKey, clientValue] of Object.entries(clientParameters)) {
998
+ const normalizedClientKey = clientKey.replace(/^\/|\/$/g, '');
999
+
1000
+ if (normalizedClientKey === normalizedRuleParam) {
1001
+ collectedParams[clientKey] = clientValue;
1002
+ // >! Mark this parameter as validated to avoid duplicate validation
1003
+ validatedParams.add(normalizedClientKey);
1004
+ break;
1005
+ }
1006
+ }
1007
+ }
860
1008
  } else {
861
1009
  // >! Maintain existing logging for invalid parameters
862
1010
  DebugAndLog.warn(`Invalid parameter: ${paramKey} = ${paramValue}`);
863
1011
  rValue.isValid = false;
864
- rValue.params = {};
865
- // >! Ensure early exit on validation failure
866
- return rValue;
1012
+ rValue.invalidParams.push(paramKey);
867
1013
  }
868
1014
  } else if (!excludeUnmatched) {
869
1015
  // No validation rule found, but excludeUnmatched is false
870
1016
  // Include parameter without validation
871
- rValue.params[paramKey] = paramValue;
1017
+ collectedParams[paramKey] = paramValue;
872
1018
  }
873
1019
  // If excludeUnmatched is true and no rule found, skip parameter (existing behavior)
874
1020
  }
1021
+
1022
+ // >! If any parameter failed, clear params (preserves existing behavior)
1023
+ rValue.params = rValue.isValid ? collectedParams : {};
875
1024
  }
876
1025
  return rValue;
877
1026
  }
@@ -889,13 +1038,13 @@ class ClientRequest extends RequestInfo {
889
1038
  * const isValid = #hasValidPathParameters();
890
1039
  */
891
1040
  #hasValidPathParameters() {
892
- const { isValid, params } = this.#hasValidParameters(
1041
+ const { isValid, params, invalidParams } = this.#hasValidParameters(
893
1042
  ClientRequest.getParameterValidations()?.pathParameters,
894
1043
  this.#event?.pathParameters,
895
1044
  this.#validationMatchers.pathParameters
896
1045
  );
897
1046
  this.#props.pathParameters = params;
898
- return isValid;
1047
+ return { isValid, invalidParams };
899
1048
  }
900
1049
 
901
1050
  /**
@@ -923,13 +1072,13 @@ class ClientRequest extends RequestInfo {
923
1072
  const paramValidations = ClientRequest.getParameterValidations();
924
1073
  const queryValidations = paramValidations?.queryStringParameters || paramValidations?.queryParameters;
925
1074
 
926
- const { isValid, params } = this.#hasValidParameters(
1075
+ const { isValid, params, invalidParams } = this.#hasValidParameters(
927
1076
  queryValidations,
928
1077
  qs,
929
1078
  this.#validationMatchers.queryStringParameters
930
1079
  );
931
1080
  this.#props.queryStringParameters = params;
932
- return isValid;
1081
+ return { isValid, invalidParams };
933
1082
  }
934
1083
 
935
1084
  /**
@@ -977,23 +1126,23 @@ class ClientRequest extends RequestInfo {
977
1126
  const camelCaseKey = key.toLowerCase().replace(/-([a-z])/g, (g) => g[1].toUpperCase());
978
1127
  headers[camelCaseKey] = this.#event.headers[key];
979
1128
  }
980
- const { isValid, params } = this.#hasValidParameters(
1129
+ const { isValid, params, invalidParams } = this.#hasValidParameters(
981
1130
  ClientRequest.getParameterValidations()?.headerParameters,
982
1131
  headers,
983
1132
  this.#validationMatchers.headerParameters
984
1133
  );
985
1134
  this.#props.headerParameters = params;
986
- return isValid;
1135
+ return { isValid, invalidParams };
987
1136
  }
988
1137
 
989
1138
  #hasValidCookieParameters() {
990
- const { isValid, params } = this.#hasValidParameters(
1139
+ const { isValid, params, invalidParams } = this.#hasValidParameters(
991
1140
  ClientRequest.getParameterValidations()?.cookieParameters,
992
1141
  this.#event?.cookie,
993
1142
  this.#validationMatchers.cookieParameters
994
1143
  );
995
1144
  this.#props.cookieParameters = params;
996
- return isValid;
1145
+ return { isValid, invalidParams };
997
1146
  }
998
1147
 
999
1148
  /**
@@ -1028,12 +1177,12 @@ class ClientRequest extends RequestInfo {
1028
1177
  error?.stack
1029
1178
  );
1030
1179
  this.#props.bodyParameters = {};
1031
- return false;
1180
+ return { isValid: false, invalidParams: [], invalidBody: true };
1032
1181
  }
1033
1182
  }
1034
1183
 
1035
1184
  // >! Use existing validation framework with body validation matcher
1036
- const { isValid, params } = this.#hasValidParameters(
1185
+ const { isValid, params, invalidParams } = this.#hasValidParameters(
1037
1186
  ClientRequest.getParameterValidations()?.bodyParameters,
1038
1187
  bodyObject,
1039
1188
  this.#validationMatchers.bodyParameters
@@ -1041,7 +1190,7 @@ class ClientRequest extends RequestInfo {
1041
1190
 
1042
1191
  // >! Store validated parameters
1043
1192
  this.#props.bodyParameters = params;
1044
- return isValid;
1193
+ return { isValid, invalidParams };
1045
1194
  }
1046
1195
 
1047
1196
 
@@ -1252,7 +1401,7 @@ class ClientRequest extends RequestInfo {
1252
1401
  * Get the _processed_ request properties. These are the properties that
1253
1402
  * the ClientRequest object took from the event sent to Lambda, validated,
1254
1403
  * supplemented, and makes available to controllers.
1255
- * @returns {{ method: string, path: string, pathArray: string[], resource: string, resourceArray[], pathParameters: {}, queryStringParameters: {}, headerParameters: {}, cookieParameters: {}, bodyPayload: string, client: {isAuthenticated: boolean, isGuest: boolean, authorizations: string[], roles: string[]}, deadline: number, calcMsToDeadline: number}
1404
+ * @returns {{ method: string, path: string, pathArray: string[], resource: string, resourceArray[], pathParameters: {}, queryStringParameters: {}, headerParameters: {}, cookieParameters: {}, bodyParameters: {}, bodyPayload: string, client: {ip: string, userAgent: string, origin: string, referrer: string, isAuthenticated: boolean, isGuest: boolean, authorizations: string[], roles: string[]}, deadline: number, calcMsToDeadline: number}
1256
1405
  */
1257
1406
  getProps() {
1258
1407
  return this.#props;
@@ -32,7 +32,7 @@ const { safeClone } = require('./utils');
32
32
  *
33
33
  * // Use with endpoint requests
34
34
  * const dbConn = connections.get('database');
35
- * const result = await endpoint.get(dbConn);
35
+ * const result = await endpoint.send(dbConn);
36
36
  */
37
37
  class Connections {
38
38
 
@@ -132,7 +132,7 @@ class Connections {
132
132
  * The Connection object provides the base for requests but does not carry
133
133
  * the request. myConnection.get() will return an object (associative array)
134
134
  * that can then be used to generate and submit a request to a DAO class or
135
- * APIRequest object.
135
+ * ApiRequest object.
136
136
  * You can store and manage multiple connections using the Connections object.
137
137
  * @example
138
138
  * // Create a simple connection
@@ -145,7 +145,7 @@ class Connections {
145
145
  *
146
146
  * // Get connection object for use with endpoint
147
147
  * const connObj = apiConnection.get();
148
- * const users = await endpoint.get(connObj);
148
+ * const users = await endpoint.send(connObj);
149
149
  *
150
150
  * @example
151
151
  * // Create connection with authentication
@@ -581,7 +581,7 @@ class ConnectionAuthentication {
581
581
  * request.addParameter('page', 1);
582
582
  *
583
583
  * // Make the request
584
- * const result = await endpoint.get(request);
584
+ * const result = await endpoint.send(request);
585
585
  *
586
586
  * @example
587
587
  * // Build a request dynamically in a DAO
@@ -597,7 +597,7 @@ class ConnectionAuthentication {
597
597
  * 'Content-Type': 'application/json'
598
598
  * });
599
599
  *
600
- * return await endpoint.get(request);
600
+ * return await endpoint.send(request);
601
601
  * }
602
602
  * }
603
603
  */
@@ -525,6 +525,31 @@ class Response {
525
525
  }
526
526
  };
527
527
 
528
+ /**
529
+ * Sets a message or messages property on the JSON response body.
530
+ * If the body is not a JSON object, this method is a no-op.
531
+ * Does not alter the status code or headers.
532
+ *
533
+ * @param {string|Array<string>} message - A single message string or array of message strings
534
+ * @returns {void}
535
+ * @example
536
+ * // Single message
537
+ * response.setMessage("Invalid parameter: limit");
538
+ * // body becomes: { ...existingBody, message: "Invalid parameter: limit" }
539
+ *
540
+ * @example
541
+ * // Multiple messages
542
+ * response.setMessage(["Invalid parameter: limit", "Invalid parameter: offset"]);
543
+ * // body becomes: { ...existingBody, messages: ["Invalid parameter: limit", "Invalid parameter: offset"] }
544
+ */
545
+ setMessage = (message) => {
546
+ if (Array.isArray(message)) {
547
+ this.addToJsonBody({ messages: message });
548
+ } else {
549
+ this.addToJsonBody({ message: message });
550
+ }
551
+ };
552
+
528
553
  /**
529
554
  * Converts the response to a plain object.
530
555
  *
@@ -1,121 +1,16 @@
1
- contentType = "text/html; charset=utf-8";
1
+ const { createGenericResponseModule } = require("./generic.response");
2
2
 
3
- headers = {
4
- "Content-Type": contentType
5
- };
6
-
7
- html = (title, body) => {
3
+ const html = (title, body) => {
8
4
  return `<html><head><title>${title}</title></head><body>${body}</body></html>`;
9
- }
10
-
11
- response200 = {
12
- statusCode: 200,
13
- headers: headers,
14
- body: html("200 OK", "<p>Success</p>")
15
- };
16
-
17
- response400 = {
18
- statusCode: 400,
19
- headers: headers,
20
- body: html("400 Bad Request", "<p>Bad Request</p>")
21
- };
22
-
23
- response401 = {
24
- statusCode: 401,
25
- headers: headers,
26
- body: html("401 Unauthorized", "<p>Unauthorized</p>")
27
- };
28
-
29
- response403 = {
30
- statusCode: 403,
31
- headers: headers,
32
- body: html("403 Forbidden", "<p>Forbidden</p>")
33
- };
34
-
35
- response404 = {
36
- statusCode: 404,
37
- headers: headers,
38
- body: html("404 Not Found", "<p>Not Found</p>")
39
- };
40
-
41
- response405 = {
42
- statusCode: 405,
43
- headers: headers,
44
- body: html("405 Method Not Allowed", "<p>Method Not Allowed</p>")
45
- };
46
-
47
- response408 = {
48
- statusCode: 408,
49
- headers: headers,
50
- body: html("408 Request Timeout", "<p>Request Timeout</p>")
51
5
  };
52
6
 
53
- response418 = {
54
- statusCode: 418,
55
- headers: headers,
56
- body: html("418 I'm a teapot", "<p>I'm a teapot</p>")
7
+ const HTML_TITLE_MAP = {
8
+ 200: "OK",
9
+ 500: "Error"
57
10
  };
58
11
 
59
- response427 = {
60
- statusCode: 427,
61
- headers: headers,
62
- body: html("427 Too Many Requests", "<p>Too Many Requests</p>")
63
- };
64
-
65
- response500 = {
66
- statusCode: 500,
67
- headers: headers,
68
- body: html("500 Error", "<p>Internal Server Error</p>")
69
- };
12
+ const htmlBodyFormatter = (statusCode, message) => html(statusCode + " " + (HTML_TITLE_MAP[statusCode] || message), "<p>" + message + "</p>");
70
13
 
71
- /**
72
- *
73
- * @param {number|string} statusCode
74
- * @returns {{statusCode: number, headers: object, body: Array|Object|string}}
75
- */
76
- response = function (statusCode) {
77
- // convert to int
78
- statusCode = parseInt(statusCode, 10);
79
-
80
- switch (statusCode) {
81
- case 200:
82
- return this.response200;
83
- case 400:
84
- return this.response400;
85
- case 401:
86
- return this.response401;
87
- case 403:
88
- return this.response403;
89
- case 404:
90
- return this.response404;
91
- case 405:
92
- return this.response405;
93
- case 408:
94
- return this.response408;
95
- case 418:
96
- return this.response418;
97
- case 427:
98
- return this.response427;
99
- case 500:
100
- return this.response500;
101
- default:
102
- return this.response500;
103
- }
104
- };
14
+ const mod = createGenericResponseModule("text/html; charset=utf-8", htmlBodyFormatter);
105
15
 
106
- module.exports = {
107
- contentType,
108
- headers,
109
- html,
110
- response200,
111
- response400,
112
- response401,
113
- response403,
114
- response404,
115
- response405,
116
- response408,
117
- response418,
118
- response427,
119
- response500,
120
- response
121
- }
16
+ module.exports = { ...mod, html };
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Centralized generic response module factory.
3
+ *
4
+ * Encapsulates the shared STATUS_CODE_MAP, response object generation, and
5
+ * response() lookup function used by all five format-specific generic response
6
+ * files (HTML, JSON, RSS, Text, XML).
7
+ *
8
+ * @module generic.response
9
+ */
10
+
11
+ /**
12
+ * Map of HTTP status codes to their default message strings.
13
+ *
14
+ * @type {Object.<number, string>}
15
+ */
16
+ const STATUS_CODE_MAP = {
17
+ 200: "Success",
18
+ 400: "Bad Request",
19
+ 401: "Unauthorized",
20
+ 403: "Forbidden",
21
+ 404: "Not Found",
22
+ 405: "Method Not Allowed",
23
+ 408: "Request Timeout",
24
+ 418: "I'm a teapot",
25
+ 427: "Too Many Requests",
26
+ 500: "Internal Server Error"
27
+ };
28
+
29
+ /**
30
+ * Create a complete generic response module for a given content type and body formatter.
31
+ *
32
+ * Iterates over STATUS_CODE_MAP, calls bodyFormatter(statusCode, message) for each
33
+ * entry, and builds the response objects. Attaches a response() function that parses
34
+ * the status code to an integer and looks up the matching response object, falling
35
+ * back to response500 for unknown codes.
36
+ *
37
+ * @param {string} contentType - MIME content type string (e.g., "application/json")
38
+ * @param {function(number, string): *} bodyFormatter - Function that transforms (statusCode, message) into format-specific body
39
+ * @returns {{contentType: string, headers: Object, response200: Object, response400: Object, response401: Object, response403: Object, response404: Object, response405: Object, response408: Object, response418: Object, response427: Object, response500: Object, response: function(number|string): Object}}
40
+ * @example
41
+ * const { createGenericResponseModule } = require("./generic.response");
42
+ *
43
+ * const mod = createGenericResponseModule("application/json", (statusCode, message) => ({ message }));
44
+ * console.log(mod.response200.body); // { message: "Success" }
45
+ * console.log(mod.response(404).statusCode); // 404
46
+ */
47
+ function createGenericResponseModule(contentType, bodyFormatter) {
48
+ const headers = { "Content-Type": contentType };
49
+
50
+ const mod = {
51
+ contentType: contentType,
52
+ headers: headers
53
+ };
54
+
55
+ for (const code in STATUS_CODE_MAP) {
56
+ const statusCode = parseInt(code, 10);
57
+ const message = STATUS_CODE_MAP[code];
58
+ mod["response" + statusCode] = {
59
+ statusCode: statusCode,
60
+ headers: headers,
61
+ body: bodyFormatter(statusCode, message)
62
+ };
63
+ }
64
+
65
+ mod.response = function (statusCode) {
66
+ statusCode = parseInt(statusCode, 10);
67
+ return this["response" + statusCode] || this.response500;
68
+ };
69
+
70
+ return mod;
71
+ }
72
+
73
+ module.exports = { createGenericResponseModule };