bootpress 9.0.2 → 10.0.0
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/.eslintrc +15 -0
- package/.gitattributes +2 -0
- package/README.md +40 -18
- package/helpers/index.d.ts +25 -12
- package/helpers/index.js +103 -39
- package/index.d.ts +32 -32
- package/index.js +44 -21
- package/package.json +2 -1
package/.eslintrc
ADDED
package/.gitattributes
ADDED
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<h1 align="center" style="margin-bottom: 0" >
|
|
2
|
-
<img src="https://raw.githubusercontent.com/ufukbakan/bootpress/main/bootpress.svg"
|
|
2
|
+
<img src="https://raw.githubusercontent.com/ufukbakan/bootpress/main/bootpress.svg" width=500 alt="bootpress">
|
|
3
3
|
</h1>
|
|
4
4
|
<p align=center>Express but Spring Boot like</p>
|
|
5
5
|
|
|
@@ -61,13 +61,17 @@ class PostServiceImpl {
|
|
|
61
61
|
return new HttpResponse(201, casted.id);
|
|
62
62
|
}
|
|
63
63
|
delete(deleteInQuery: string | undefined, idInQuery: string | undefined) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
this
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
if (deleteInQuery === "yes") {
|
|
65
|
+
const id = as(idInQuery, "integer");
|
|
66
|
+
const index = this.posts.indexOf(id);
|
|
67
|
+
if (index > -1) {
|
|
68
|
+
this.#logDeleted(idInQuery!);
|
|
69
|
+
return this.posts.splice(index, 1);
|
|
70
|
+
} else {
|
|
71
|
+
throw new HttpError(404, "Post is not found")
|
|
72
|
+
}
|
|
70
73
|
}
|
|
74
|
+
throw new HttpError(400, "Bad Request");
|
|
71
75
|
}
|
|
72
76
|
// use private methods to
|
|
73
77
|
#logDeleted(id: number | string) {
|
|
@@ -139,9 +143,9 @@ app.get("/logs", LogService.findAll() as RequestHandler)
|
|
|
139
143
|
|
|
140
144
|
```PassBody(serviceFunction)``` -> Passes body to service function without any validation
|
|
141
145
|
|
|
142
|
-
```ParseBodyAs(type)(serviceFunction)``` -> Parses body to specified type then passes it to service function
|
|
146
|
+
```ParseBodyAs(type, config?)(serviceFunction)``` -> Parses body to specified type then passes it to service function. Config object is optional and has messageTemplate field which represents a string with details placeholder: ```{0}```
|
|
143
147
|
|
|
144
|
-
```PassBodyAs(type)(serviceFunction)``` -> Validates body with provided type and passes it to service function
|
|
148
|
+
```PassBodyAs(type, config?)(serviceFunction)``` -> Validates body with provided type and passes it to service function. Config object is optional and has messageTemplate field which represents a string with details placeholder: ```{0}```
|
|
145
149
|
|
|
146
150
|
```PassAllParams(serviceFunction)``` -> Passes all path parameters to service function as a Record<string, string> (pure js object that contains key-value pairs)
|
|
147
151
|
|
|
@@ -179,11 +183,11 @@ Returns the value back if it's not null, undefined or empty array.
|
|
|
179
183
|
Returns the value if it's not null or undefined otherwise returns the default value.
|
|
180
184
|
### **schema(object)**
|
|
181
185
|
Helps you to define a JS Schema.
|
|
182
|
-
### **as(any, string | object | array)**
|
|
183
|
-
Tries to parse
|
|
184
|
-
|
|
186
|
+
### **as(target: any, [type: string | object | array](#type-paramter-for-as--asstrict-methods), [config? object](#config-object-optional-for-as--asstrict-methods))**
|
|
187
|
+
Tries to parse target value to provided type.
|
|
188
|
+
#### Type paramter (for as & asStrict methods)
|
|
185
189
|
If type of provided type is string then it's a primitive key and valid values are:
|
|
186
|
-
```
|
|
190
|
+
```ts
|
|
187
191
|
"string"
|
|
188
192
|
"string[]"
|
|
189
193
|
"boolean"
|
|
@@ -202,25 +206,43 @@ If type of provided type is string then it's a primitive key and valid values ar
|
|
|
202
206
|
"integer[]?"
|
|
203
207
|
```
|
|
204
208
|
If typeof provided type is object then it's a JS Schema and structure must follow:
|
|
205
|
-
```
|
|
209
|
+
```ts
|
|
206
210
|
{
|
|
207
|
-
"property": string | object |
|
|
208
|
-
"nullableProperty?": string | object |
|
|
211
|
+
"property": string | object | Array // Nullable primitives not allowed here instead use question mark end of the property key
|
|
212
|
+
"nullableProperty?": string | object | Array
|
|
209
213
|
}
|
|
210
214
|
```
|
|
211
215
|
|
|
212
216
|
If typeof provided type is an array the structure must follow:
|
|
213
|
-
```
|
|
217
|
+
```ts
|
|
214
218
|
[
|
|
215
219
|
yourJsSchemaObject
|
|
216
220
|
]
|
|
217
221
|
```
|
|
218
222
|
There must be only one element in an array schema which defines ````ArrayOf<Schema>````
|
|
219
|
-
|
|
223
|
+
#### Config object (optional for as & asStrict methods)
|
|
224
|
+
Config object is optional and structure follows:
|
|
225
|
+
```ts
|
|
226
|
+
{
|
|
227
|
+
errorVariableName: string | undefined, // variable name in the error message
|
|
228
|
+
messageTemplate: string | undefined // default values is "{0}" where it directly writes error details.
|
|
229
|
+
// an messageTemplate example is: "Parse error:\n{0}"
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
### **asStrict(target: any, [type: string | object | array](#type-parameter), [config? object](#config-object-optional-for-as--asstrict-methods))**
|
|
220
233
|
Same as 'as' method but doesn't try to parse different types instead throws error.
|
|
221
234
|
|
|
222
235
|
# Release Notes
|
|
223
236
|
|
|
237
|
+
## v10.0.0:
|
|
238
|
+
- Configuration support for as, asStrict, passBodyAs and parseBodyAs methods.
|
|
239
|
+
- Integrated logger. (Changeable via setLogger method)
|
|
240
|
+
- 500 server errors are logged with error level.
|
|
241
|
+
|
|
242
|
+
## v9.1.0:
|
|
243
|
+
- Fixed chained argument type error bugs
|
|
244
|
+
- Improvements in argument passer type declarations
|
|
245
|
+
|
|
224
246
|
## v9.0.2:
|
|
225
247
|
- Added support for null/undefined returning async functions
|
|
226
248
|
|
package/helpers/index.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ type JsSchema = {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
type ArraySchema = [JsSchema]
|
|
24
|
+
type TypedArraySchema<A extends ArraySchema> = TypedSchema<A[0]>
|
|
24
25
|
|
|
25
26
|
type RemoveQuestionMark<T extends string> = T extends `${infer Prefix}?` ? Prefix : T;
|
|
26
27
|
|
|
@@ -34,15 +35,7 @@ export function getOrThrow<T, K extends NonNullable<T>, E extends HttpError>(dat
|
|
|
34
35
|
export function getOrElse<T, E>(data: T, defaultValue: E): E extends NonNullable<infer T> ? E : T | E;
|
|
35
36
|
export function schema<T extends JsSchema>(schema: T): TypedSchema<T>;
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
"string": string,
|
|
39
|
-
"string[]": string[],
|
|
40
|
-
"boolean": boolean,
|
|
41
|
-
"boolean[]": boolean[],
|
|
42
|
-
"number": number,
|
|
43
|
-
"number[]": number[],
|
|
44
|
-
"integer": number,
|
|
45
|
-
"integer[]": number[],
|
|
38
|
+
interface ExtendedTypeMap extends TypeMap {
|
|
46
39
|
"string?": string | null,
|
|
47
40
|
"string[]?": string[] | null,
|
|
48
41
|
"boolean?": boolean | null,
|
|
@@ -54,6 +47,26 @@ type ExtendedTypeMap = {
|
|
|
54
47
|
}
|
|
55
48
|
|
|
56
49
|
type ExtendedTypeKeys = keyof ExtendedTypeMap;
|
|
57
|
-
type ExtValOf<T extends ExtendedTypeKeys> = ExtendedTypeMap[T]
|
|
58
|
-
|
|
59
|
-
|
|
50
|
+
type ExtValOf<T extends ExtendedTypeKeys> = ExtendedTypeMap[T];
|
|
51
|
+
type ErrorVariableConfiguration = {
|
|
52
|
+
errorVariableName?: string
|
|
53
|
+
};
|
|
54
|
+
type ErrorTemplateConfiguration = {
|
|
55
|
+
messageTemplate?: string
|
|
56
|
+
};
|
|
57
|
+
type ErrorConfiguration = ErrorVariableConfiguration & ErrorTemplateConfiguration;
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
export function as<T extends (ExtendedTypeKeys | JsSchema | ArraySchema)>(o: any, type: T, config?: ErrorConfiguration): T extends ExtendedTypeKeys ? ExtValOf<T> : TypedSchema<T>;
|
|
61
|
+
export function asStrict<T extends (ExtendedTypeKeys | JsSchema | ArraySchema)>(o: any, type: T, config?: ErrorConfiguration): T extends ExtendedTypeKeys ? ExtValOf<T> : TypedSchema<T>;
|
|
62
|
+
|
|
63
|
+
type Log = {
|
|
64
|
+
fatal: (message: any) => void,
|
|
65
|
+
error: (message: any) => void,
|
|
66
|
+
warn: (message: any) => void,
|
|
67
|
+
info: (message: any) => void,
|
|
68
|
+
debug: (message: any) => void,
|
|
69
|
+
trace: (message: any) => void,
|
|
70
|
+
}
|
|
71
|
+
export const log: Log;
|
|
72
|
+
export function setLogger(logger: Log): void;
|
package/helpers/index.js
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
const { HttpError } = require("../types");
|
|
2
2
|
|
|
3
3
|
const allowedPrimitives = ["string", "number", "boolean", "integer"];
|
|
4
|
+
/**
|
|
5
|
+
* For both getter & setter
|
|
6
|
+
*
|
|
7
|
+
* Any logger with Log4J interface can be usable. (e.g. Pino)
|
|
8
|
+
*
|
|
9
|
+
*/
|
|
10
|
+
let _logger = {
|
|
11
|
+
fatal: (message) => console.error(message),
|
|
12
|
+
error: (message) => console.error(message),
|
|
13
|
+
warn: (message) => console.warn(message),
|
|
14
|
+
info: (message) => console.log(message),
|
|
15
|
+
debug: (message) => console.log(message),
|
|
16
|
+
trace: (message) => console.log(message),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const log = {
|
|
20
|
+
fatal: (message) => _logger.fatal(message),
|
|
21
|
+
error: (message) => _logger.error(message),
|
|
22
|
+
warn: (message) => _logger.warn(message),
|
|
23
|
+
info: (message) => _logger.info(message),
|
|
24
|
+
debug: (message) => _logger.debug(message),
|
|
25
|
+
trace: (message) => _logger.trace(message),
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function setLogger(logger) {
|
|
29
|
+
_logger = logger;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function passStringArgs(str, ...args) {
|
|
33
|
+
let result = str;
|
|
34
|
+
for (let i = 0; i < args.length; i++) {
|
|
35
|
+
result = str.replace(`{${i}}`, args[i]);
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
4
39
|
|
|
5
40
|
function getOrThrow(data, error) {
|
|
6
41
|
if (data === null || data === undefined || isEmptyArray(data)) {
|
|
@@ -10,7 +45,7 @@ function getOrThrow(data, error) {
|
|
|
10
45
|
}
|
|
11
46
|
}
|
|
12
47
|
|
|
13
|
-
function isEmptyArray(x){
|
|
48
|
+
function isEmptyArray(x) {
|
|
14
49
|
return Array.isArray(x) && x.length < 1;
|
|
15
50
|
}
|
|
16
51
|
|
|
@@ -83,9 +118,12 @@ function asString(o, errorMessage = undefined, errorStatus = 400) {
|
|
|
83
118
|
}
|
|
84
119
|
}
|
|
85
120
|
|
|
86
|
-
function asSchema(o, schema,
|
|
87
|
-
|
|
88
|
-
|
|
121
|
+
function asSchema(o, schema, config = { strict: false, messageTemplate: "{0}" }) {
|
|
122
|
+
const strict = config.strict ?? false;
|
|
123
|
+
if (!(schema instanceof Object) || Array.isArray(schema)) {
|
|
124
|
+
const error = new HttpError(500, `Schema is not valid ${JSON.stringify(schema)}`);
|
|
125
|
+
_logger.error(error.stack);
|
|
126
|
+
throw error;
|
|
89
127
|
}
|
|
90
128
|
const schemaKeyValues = Object.entries(schema);
|
|
91
129
|
let result = {};
|
|
@@ -104,17 +142,19 @@ function asSchema(o, schema, strict = false) {
|
|
|
104
142
|
if (Array.isArray(expectedType)) {
|
|
105
143
|
result[key] = [];
|
|
106
144
|
for (let j = 0; j < o[key].length; j++) {
|
|
107
|
-
result[key][j] = asSchema(o[key][j], expectedType[0],
|
|
145
|
+
result[key][j] = asSchema(o[key][j], expectedType[0], config)
|
|
108
146
|
}
|
|
109
147
|
} else {
|
|
110
|
-
result[key] = asSchema(o[key], expectedType,
|
|
148
|
+
result[key] = asSchema(o[key], expectedType, config);
|
|
111
149
|
}
|
|
112
150
|
}
|
|
113
151
|
else if (typeof expectedType === "string") {
|
|
114
|
-
result[key] = strict ? asStrict(o[key], expectedType, key) : as(o[key], expectedType, key);
|
|
152
|
+
result[key] = strict ? asStrict(o[key], expectedType, { ...config, errorVariableName: key }) : as(o[key], expectedType, { ...config, errorVariableName: key });
|
|
115
153
|
}
|
|
116
154
|
else {
|
|
117
|
-
|
|
155
|
+
const error = new HttpError(500, `Type of a schema key should be a primitive type or another schema`);
|
|
156
|
+
_logger.error(error.stack);
|
|
157
|
+
throw error;
|
|
118
158
|
}
|
|
119
159
|
}
|
|
120
160
|
return result;
|
|
@@ -125,117 +165,139 @@ function schema(schema) {
|
|
|
125
165
|
return schema;
|
|
126
166
|
}
|
|
127
167
|
|
|
128
|
-
function asPrimitiveArrayOf(o, elementType) {
|
|
168
|
+
function asPrimitiveArrayOf(o, elementType, config = { errorVariableName: o, messageTemplate: "{0}" }) {
|
|
169
|
+
const messageTemplate = config.messageTemplate ?? "{0}";
|
|
129
170
|
if (Array.isArray(o)) {
|
|
130
171
|
for (let i = 0; i < o.length; i++) {
|
|
131
|
-
o[i] = checkPrimitive(o[i], elementType,
|
|
172
|
+
o[i] = checkPrimitive(o[i], elementType, config);
|
|
132
173
|
}
|
|
133
174
|
return o;
|
|
134
175
|
} else {
|
|
135
|
-
throw new HttpError(400, `Provided object is not an array: ${JSON.stringify(o)}`)
|
|
176
|
+
throw new HttpError(400, passStringArgs(messageTemplate, `Provided object is not an array: ${JSON.stringify(o)}`))
|
|
136
177
|
}
|
|
137
178
|
}
|
|
138
179
|
|
|
139
|
-
function as(o, type,
|
|
180
|
+
function as(o, type, config = { errorVariableName: o, messageTemplate: "{0}" }) {
|
|
181
|
+
const errorVariableName = config.errorVariableName ?? o;
|
|
182
|
+
const messageTemplate = config.messageTemplate ?? "{0}";
|
|
140
183
|
if (typeof type === "string") {
|
|
141
184
|
if (type.endsWith("[]")) {
|
|
142
185
|
// array check
|
|
143
186
|
const elementType = type.replace("[]", "");
|
|
144
|
-
return asPrimitiveArrayOf(o, elementType);
|
|
187
|
+
return asPrimitiveArrayOf(o, elementType, config);
|
|
145
188
|
} else { // non array types:
|
|
146
189
|
if (type.endsWith("?") && o == null) {
|
|
147
190
|
return null;
|
|
148
191
|
} else if (type.endsWith("?") && o != null) {
|
|
149
192
|
const actualType = type.replace("?", "");
|
|
150
|
-
return as(o, actualType,
|
|
193
|
+
return as(o, actualType, config);
|
|
151
194
|
}
|
|
152
195
|
// primitive check
|
|
153
196
|
switch (type) {
|
|
154
197
|
case "string":
|
|
155
|
-
return asString(o, `Type of ${
|
|
198
|
+
return asString(o, passStringArgs(messageTemplate, `Type of ${errorVariableName} should have been a string but it's ${o == null ? "null" : typeof o}`));
|
|
156
199
|
case "number":
|
|
157
|
-
return asNumber(o, `Type of ${
|
|
200
|
+
return asNumber(o, passStringArgs(messageTemplate, `Type of ${errorVariableName} should have been a number but it's ${o == null ? "null" : typeof o}`));
|
|
158
201
|
case "boolean":
|
|
159
|
-
return asBoolean(o, `Type of ${
|
|
202
|
+
return asBoolean(o, passStringArgs(messageTemplate, `Type of ${errorVariableName} should have been a boolean but it's ${o == null ? "null" : typeof o}`));
|
|
160
203
|
case "integer":
|
|
161
|
-
return asInteger(o, `Type of ${
|
|
204
|
+
return asInteger(o, passStringArgs(messageTemplate, `Type of ${errorVariableName} should have been an integer but it's ${o == null ? "null" : typeof o}`));
|
|
162
205
|
default:
|
|
163
|
-
|
|
206
|
+
const error = new HttpError(500, `Unsupported type ${type}`);
|
|
207
|
+
_logger.error(error.stack);
|
|
208
|
+
throw error;
|
|
164
209
|
}
|
|
165
210
|
}
|
|
166
211
|
} else if (typeof type === "object" && type != null) {
|
|
167
212
|
if (Array.isArray(type)) {
|
|
168
213
|
if (type.length > 1) {
|
|
169
|
-
|
|
214
|
+
const error = new HttpError(500, `You can define only one schema for types ArrayOf<Schema>`);
|
|
215
|
+
_logger.error(error.stack);
|
|
216
|
+
throw error;
|
|
170
217
|
} else if (type.length < 1) {
|
|
171
|
-
|
|
218
|
+
const error = new HttpError(500, `You must define a schema for types ArrayOf<Schema>`);
|
|
219
|
+
_logger.error(error.stack)
|
|
220
|
+
throw error;
|
|
172
221
|
}
|
|
173
222
|
// array schema validation
|
|
174
223
|
if (!Array.isArray(o)) {
|
|
175
|
-
throw new HttpError(400, `Provided value should have been an array. (${JSON.stringify(o)})`)
|
|
224
|
+
throw new HttpError(400, passStringArgs(messageTemplate, `Provided value should have been an array. (${JSON.stringify(o)})`))
|
|
176
225
|
}
|
|
177
226
|
const providedSchema = type[0];
|
|
178
227
|
let result = [];
|
|
179
228
|
for (let i = 0; i < o.length; i++) {
|
|
180
|
-
result.push(asSchema(o[i], providedSchema));
|
|
229
|
+
result.push(asSchema(o[i], providedSchema, config));
|
|
181
230
|
}
|
|
182
231
|
return result;
|
|
183
232
|
} else {
|
|
184
233
|
// schema validation
|
|
185
|
-
return asSchema(o, type);
|
|
234
|
+
return asSchema(o, type, config);
|
|
186
235
|
}
|
|
187
236
|
} else {
|
|
188
|
-
|
|
237
|
+
const error = new HttpError(500, `Unsupported type check ${type}`);
|
|
238
|
+
_logger.error(error);
|
|
239
|
+
throw error;
|
|
189
240
|
}
|
|
190
241
|
}
|
|
191
242
|
|
|
192
|
-
function asStrict(o, type,
|
|
243
|
+
function asStrict(o, type, config = { errorVariableName: o, messageTemplate: "{0}" }) {
|
|
244
|
+
const messageTemplate = config.messageTemplate ?? "{0}";
|
|
193
245
|
if (typeof type === "string") {
|
|
194
246
|
if (type.endsWith("[]")) {
|
|
195
247
|
// array check
|
|
196
248
|
const elementType = type.replace("[]", "");
|
|
197
|
-
return asPrimitiveArrayOf(o, elementType);
|
|
249
|
+
return asPrimitiveArrayOf(o, elementType, config);
|
|
198
250
|
} else { // non array types:
|
|
199
251
|
if (type.endsWith("?") && o == null) {
|
|
200
252
|
return null;
|
|
201
253
|
} else if (type.endsWith("?") && o != null) {
|
|
202
254
|
const actualType = type.replace("?", "");
|
|
203
|
-
return asStrict(o, actualType,
|
|
255
|
+
return asStrict(o, actualType, config);
|
|
204
256
|
}
|
|
205
257
|
// primitive check
|
|
206
258
|
if (!allowedPrimitives.includes(type)) {
|
|
207
|
-
|
|
259
|
+
const error = new HttpError(500, `Unsupported type ${type}`);
|
|
260
|
+
_logger.error(error);
|
|
261
|
+
throw error;
|
|
208
262
|
}
|
|
209
|
-
return checkPrimitive(o, type,
|
|
263
|
+
return checkPrimitive(o, type, config);
|
|
210
264
|
}
|
|
211
265
|
} else if (typeof type === "object" && type != null) {
|
|
212
266
|
if (Array.isArray(type)) {
|
|
213
267
|
if (type.length > 1) {
|
|
214
|
-
|
|
268
|
+
const error = new HttpError(500, `You can define only one schema for types ArrayOf<Schema>`);
|
|
269
|
+
_logger.error(error);
|
|
270
|
+
throw error;
|
|
215
271
|
} else if (type.length < 1) {
|
|
216
|
-
|
|
272
|
+
const error = new HttpError(500, `You must define a schema for types ArrayOf<Schema>`);
|
|
273
|
+
_logger.error(error);
|
|
274
|
+
throw error;
|
|
217
275
|
}
|
|
218
276
|
// array schema validation
|
|
219
277
|
if (!Array.isArray(o)) {
|
|
220
|
-
throw new HttpError(400, `Provided value should have been an array but it's ${typeof o}`)
|
|
278
|
+
throw new HttpError(400, passStringArgs(messageTemplate, `Provided value should have been an array but it's ${typeof o}`))
|
|
221
279
|
}
|
|
222
280
|
const providedSchema = type[0];
|
|
223
281
|
let result = [];
|
|
224
282
|
for (let i = 0; i < o.length; i++) {
|
|
225
|
-
result.push(asSchema(o[i], providedSchema, true));
|
|
283
|
+
result.push(asSchema(o[i], providedSchema, { ...config, strict: true }));
|
|
226
284
|
}
|
|
227
285
|
return result;
|
|
228
286
|
} else {
|
|
229
287
|
// schema validation
|
|
230
|
-
return asSchema(o, type, true);
|
|
288
|
+
return asSchema(o, type, { ...config, strict: true });
|
|
231
289
|
}
|
|
232
290
|
} else {
|
|
233
|
-
|
|
291
|
+
const error = new HttpError(500, `Unsupported type check ${type}`);
|
|
292
|
+
_logger.error(error);
|
|
293
|
+
throw error;
|
|
234
294
|
}
|
|
235
295
|
}
|
|
236
296
|
|
|
237
|
-
function checkPrimitive(o, type,
|
|
238
|
-
const
|
|
297
|
+
function checkPrimitive(o, type, config = { errorVariableName: o, messageTemplate: "{0}" }) {
|
|
298
|
+
const errorVariableName = config.errorVariableName ?? o;
|
|
299
|
+
const messageTemplate = config.messageTemplate ?? "{0}";
|
|
300
|
+
const error = new HttpError(400, passStringArgs(messageTemplate, `${errorVariableName} should have been a ${type}. But it's ${o == null ? "null" : typeof o}`));
|
|
239
301
|
if (type === "integer") {
|
|
240
302
|
if (!Number.isInteger(o)) {
|
|
241
303
|
throw error;
|
|
@@ -253,5 +315,7 @@ module.exports = {
|
|
|
253
315
|
getOrElse,
|
|
254
316
|
schema,
|
|
255
317
|
as,
|
|
256
|
-
asStrict
|
|
318
|
+
asStrict,
|
|
319
|
+
log,
|
|
320
|
+
setLogger
|
|
257
321
|
}
|
package/index.d.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ArraySchema, JsSchema,
|
|
1
|
+
import { Request, Response } from "express";
|
|
2
|
+
import { ArraySchema, ErrorTemplateConfiguration, ExtValOf, ExtendedTypeKeys, JsSchema, TypedArraySchema, TypedSchema } from "./helpers";
|
|
3
3
|
|
|
4
4
|
type RequestHandler = (req: Request, res: Response) => void
|
|
5
|
-
type
|
|
6
|
-
type
|
|
5
|
+
type ArrayWithLastElement<L> = [...rest: any[], lastArg: L];
|
|
6
|
+
type RequsetHandlerWithLastArg<T, F extends (...args: any) => RequestHandler = (...args: any[]) => RequestHandler, P extends ArrayWithLastElement<T> = Parameters<F>> = (...args: P) => RequestHandler;
|
|
7
7
|
type NonEmptyArray = readonly [any, ...any[]];
|
|
8
|
-
type ShiftRequestHandler<F extends
|
|
8
|
+
type ShiftRequestHandler<F extends RequsetHandlerWithLastArg<any> | RequestHandler> =
|
|
9
|
+
F extends (arg1: infer T, ...rest: infer U) => RequestHandler ?
|
|
10
|
+
U extends NonEmptyArray ? (...rest: U) => RequestHandler : RequestHandler
|
|
11
|
+
: RequestHandler
|
|
9
12
|
|
|
10
|
-
type RestedService<T extends Record<PropertyKey, any>> = {
|
|
13
|
+
type RestedService<T extends Record<PropertyKey, any>> = {
|
|
14
|
+
[K in keyof T]:
|
|
11
15
|
T[K] extends Function
|
|
12
16
|
? (...args: Parameters<T[K]>) => RequestHandler
|
|
13
17
|
: T[K]
|
|
@@ -15,35 +19,31 @@ type RestedService<T extends Record<PropertyKey, any>> = { [K in keyof T]:
|
|
|
15
19
|
|
|
16
20
|
type InstanceOrClass<T extends Record<PropertyKey, any>> = T | (new () => T);
|
|
17
21
|
|
|
22
|
+
type TypeValueOf<S extends ExtendedTypeKeys | JsSchema | TypedSchema<JsSchema> | ArraySchema> =
|
|
23
|
+
S extends TypedSchema<JsSchema> ? S
|
|
24
|
+
: S extends ExtendedTypeKeys ? ExtValOf<S>
|
|
25
|
+
: S extends JsSchema ? TypedSchema<S>
|
|
26
|
+
: S extends ArraySchema ? TypedArraySchema<S>
|
|
27
|
+
: never;
|
|
28
|
+
|
|
18
29
|
declare function RestService<T extends Record<PropertyKey, any>>(service: InstanceOrClass<T>): RestedService<T>;
|
|
19
30
|
declare function RestMethod<T>(callback: () => T): RequestHandler;
|
|
20
31
|
declare function Restify(target: any, key: PropertyKey, desc: PropertyDescriptor): PropertyDescriptor;
|
|
21
32
|
|
|
22
|
-
declare function PassBody<F extends
|
|
23
|
-
declare function ParseBodyAs<
|
|
24
|
-
declare function PassBodyAs<
|
|
25
|
-
declare function PassAllParams<F extends
|
|
26
|
-
declare function PassAllQueries<F extends
|
|
27
|
-
declare function PassAllCookies<F extends
|
|
28
|
-
declare function PassParam<F extends
|
|
29
|
-
declare function PassQuery<F extends
|
|
30
|
-
declare function PassCookie<F extends
|
|
31
|
-
declare function PassRequest<F extends
|
|
32
|
-
declare function PassResponse<F extends
|
|
33
|
+
declare function PassBody<F extends RequsetHandlerWithLastArg<any>>(serviceFunction: F): ShiftRequestHandler<F>
|
|
34
|
+
declare function ParseBodyAs<S extends ExtendedTypeKeys | JsSchema | TypedSchema<JsSchema> | ArraySchema, T = TypeValueOf<S>>(type: S, config?: ErrorTemplateConfiguration): <F extends RequsetHandlerWithLastArg<T>>(serviceFunction: F) => ShiftRequestHandler<F>
|
|
35
|
+
declare function PassBodyAs<S extends ExtendedTypeKeys | JsSchema | TypedSchema<JsSchema> | ArraySchema, T = TypeValueOf<S>>(type: S, config?: ErrorTemplateConfiguration): <F extends RequsetHandlerWithLastArg<T>>(serviceFunction: F) => ShiftRequestHandler<F>
|
|
36
|
+
declare function PassAllParams<F extends RequsetHandlerWithLastArg<string[]>>(serviceFunction: F): ShiftRequestHandler<F>
|
|
37
|
+
declare function PassAllQueries<F extends RequsetHandlerWithLastArg<string[]>>(serviceFunction: F): ShiftRequestHandler<F>
|
|
38
|
+
declare function PassAllCookies<F extends RequsetHandlerWithLastArg<string[]>>(serviceFunction: F): ShiftRequestHandler<F>
|
|
39
|
+
declare function PassParam(paramName: string): <F extends RequsetHandlerWithLastArg<string | undefined>>(serviceFunction: F) => ShiftRequestHandler<F>
|
|
40
|
+
declare function PassQuery(queryName: string): <F extends RequsetHandlerWithLastArg<string | undefined>>(serviceFunction: F) => ShiftRequestHandler<F>
|
|
41
|
+
declare function PassCookie(cookieName: string): <F extends RequsetHandlerWithLastArg<string | undefined>>(serviceFunction: F) => ShiftRequestHandler<F>
|
|
42
|
+
declare function PassRequest<F extends RequsetHandlerWithLastArg<Request>>(serviceFunction: F): ShiftRequestHandler<F>
|
|
43
|
+
declare function PassResponse<F extends RequsetHandlerWithLastArg<Response>>(serviceFunction: F): ShiftRequestHandler<F>
|
|
33
44
|
|
|
34
45
|
export {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Restify
|
|
38
|
-
|
|
39
|
-
PassAllParams,
|
|
40
|
-
PassQuery,
|
|
41
|
-
PassAllQueries,
|
|
42
|
-
PassCookie,
|
|
43
|
-
PassAllCookies,
|
|
44
|
-
PassBody,
|
|
45
|
-
PassBodyAs,
|
|
46
|
-
ParseBodyAs,
|
|
47
|
-
PassRequest,
|
|
48
|
-
PassResponse
|
|
49
|
-
}
|
|
46
|
+
ParseBodyAs, PassAllCookies, PassAllParams, PassAllQueries, PassBody,
|
|
47
|
+
PassBodyAs, PassCookie, PassParam, PassQuery, PassRequest,
|
|
48
|
+
PassResponse, RestMethod, RestService, Restify
|
|
49
|
+
};
|
package/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
const { as, asStrict } = require("./helpers");
|
|
2
|
-
const { HttpError } = require("./types");
|
|
1
|
+
const { as, asStrict, log } = require("./helpers");
|
|
3
2
|
|
|
4
3
|
const protectedProperties = [
|
|
5
4
|
"toString",
|
|
@@ -48,14 +47,18 @@ function RestService(service) {
|
|
|
48
47
|
reply(res, r.status ?? 200, r.data ?? r);
|
|
49
48
|
}
|
|
50
49
|
}).catch(e => {
|
|
51
|
-
|
|
50
|
+
const status = e.status ?? 500;
|
|
51
|
+
status === 500 && log.error(e.stack);
|
|
52
|
+
reply(res, status, e.message ?? e);
|
|
52
53
|
})
|
|
53
54
|
}
|
|
54
55
|
else {
|
|
55
56
|
reply(res, result.status ?? 200, result.data ?? result)
|
|
56
57
|
}
|
|
57
58
|
} catch (e) {
|
|
58
|
-
|
|
59
|
+
const status = e.status ?? 500;
|
|
60
|
+
status === 500 && log.error(e.stack);
|
|
61
|
+
reply(res, status, e.message ?? e);
|
|
59
62
|
}
|
|
60
63
|
});
|
|
61
64
|
} else {
|
|
@@ -80,13 +83,17 @@ function RestMethod(callback) {
|
|
|
80
83
|
reply(res, r.status ?? 200, r.data ?? r);
|
|
81
84
|
}
|
|
82
85
|
}).catch(e => {
|
|
83
|
-
|
|
86
|
+
const status = e.status ?? 500;
|
|
87
|
+
status === 500 && log.error(e.stack);
|
|
88
|
+
reply(res, status, e.message ?? e);
|
|
84
89
|
})
|
|
85
90
|
} else {
|
|
86
91
|
reply(res, result.status ?? 200, result.data ?? result);
|
|
87
92
|
}
|
|
88
93
|
} catch (e) {
|
|
89
|
-
|
|
94
|
+
const status = e.status ?? 500;
|
|
95
|
+
status === 500 && log.error(e.stack);
|
|
96
|
+
reply(res, status, e.message ?? e)
|
|
90
97
|
}
|
|
91
98
|
}
|
|
92
99
|
}
|
|
@@ -110,13 +117,17 @@ function Restify(target, key, desc) {
|
|
|
110
117
|
reply(res, r.status ?? 200, r.data ?? r);
|
|
111
118
|
}
|
|
112
119
|
}).catch(e => {
|
|
113
|
-
|
|
120
|
+
const status = e.status ?? 500;
|
|
121
|
+
status === 500 && log.error(e.stack);
|
|
122
|
+
reply(res, status, e.message ?? e);
|
|
114
123
|
})
|
|
115
124
|
} else {
|
|
116
125
|
reply(res, result.status ?? 200, result.data ?? result);
|
|
117
126
|
}
|
|
118
127
|
} catch (e) {
|
|
119
|
-
|
|
128
|
+
const status = e.status ?? 500;
|
|
129
|
+
status === 500 && log.error(e.stack);
|
|
130
|
+
reply(res, status, e.message ?? e);
|
|
120
131
|
}
|
|
121
132
|
}
|
|
122
133
|
}).bind(target)
|
|
@@ -132,7 +143,7 @@ function isRequest(o) {
|
|
|
132
143
|
}
|
|
133
144
|
|
|
134
145
|
function isRequstHandlerArgs(args) {
|
|
135
|
-
const [
|
|
146
|
+
const [_last1, last2, last3, ..._others] = [...args].reverse();
|
|
136
147
|
return isResponse(last2) && isRequest(last3);
|
|
137
148
|
}
|
|
138
149
|
|
|
@@ -186,23 +197,27 @@ function PassAllQueries(actualHandler) {
|
|
|
186
197
|
}
|
|
187
198
|
}
|
|
188
199
|
|
|
189
|
-
function ParseBodyAs(type) {
|
|
200
|
+
function ParseBodyAs(type, config = { messageTemplate: "Malformed Request Body\n{0}" }) {
|
|
190
201
|
return (actualHandler) => {
|
|
191
202
|
return (...args) => {
|
|
192
203
|
if (isRequstHandlerArgs(args)) {
|
|
193
204
|
const req = args.at(-3); const res = args.at(-2);
|
|
194
205
|
try {
|
|
195
|
-
return actualHandler(as(req.body, type))(req, res);
|
|
206
|
+
return actualHandler(as(req.body, type, config))(req, res);
|
|
196
207
|
} catch (e) {
|
|
197
|
-
|
|
208
|
+
const status = e.status ?? 500;
|
|
209
|
+
status === 500 && log.error(e.stack);
|
|
210
|
+
reply(res, status, e.message || e);
|
|
198
211
|
}
|
|
199
212
|
|
|
200
213
|
} else {
|
|
201
214
|
return (req, res) => {
|
|
202
215
|
try {
|
|
203
|
-
return actualHandler(...args, as(req.body, type))(req, res);
|
|
216
|
+
return actualHandler(...args, as(req.body, type, config))(req, res);
|
|
204
217
|
} catch (e) {
|
|
205
|
-
|
|
218
|
+
const status = e.status ?? 500;
|
|
219
|
+
status === 500 && log.error(e.stack);
|
|
220
|
+
reply(res, status, e.message || e)
|
|
206
221
|
}
|
|
207
222
|
}
|
|
208
223
|
}
|
|
@@ -210,23 +225,27 @@ function ParseBodyAs(type) {
|
|
|
210
225
|
}
|
|
211
226
|
}
|
|
212
227
|
|
|
213
|
-
function PassBodyAs(type) {
|
|
228
|
+
function PassBodyAs(type, config = { messageTemplate: "Malformed Request Body\n{0}" }) {
|
|
214
229
|
return (actualHandler) => {
|
|
215
230
|
return (...args) => {
|
|
216
231
|
if (isRequstHandlerArgs(args)) {
|
|
217
232
|
const req = args.at(-3); const res = args.at(-2);
|
|
218
233
|
try {
|
|
219
|
-
return actualHandler(asStrict(req.body, type))(req, res);
|
|
234
|
+
return actualHandler(asStrict(req.body, type, config))(req, res);
|
|
220
235
|
} catch (e) {
|
|
221
|
-
|
|
236
|
+
const status = e.status ?? 500;
|
|
237
|
+
status === 500 && log.error(e.stack);
|
|
238
|
+
reply(res, status, e.message || e);
|
|
222
239
|
}
|
|
223
240
|
|
|
224
241
|
} else {
|
|
225
242
|
return (req, res) => {
|
|
226
243
|
try {
|
|
227
|
-
return actualHandler(...args, asStrict(req.body, type))(req, res);
|
|
244
|
+
return actualHandler(...args, asStrict(req.body, type, config))(req, res);
|
|
228
245
|
} catch (e) {
|
|
229
|
-
|
|
246
|
+
const status = e.status ?? 500;
|
|
247
|
+
status === 500 && log.error(e.stack);
|
|
248
|
+
reply(res, status, e.message || e)
|
|
230
249
|
}
|
|
231
250
|
}
|
|
232
251
|
}
|
|
@@ -241,7 +260,9 @@ function PassBody(actualHandler) {
|
|
|
241
260
|
try {
|
|
242
261
|
return actualHandler(req.body)(req, res);
|
|
243
262
|
} catch (e) {
|
|
244
|
-
|
|
263
|
+
const status = e.status ?? 500;
|
|
264
|
+
status === 500 && log.error(e.stack);
|
|
265
|
+
reply(res, status, e.message || e);
|
|
245
266
|
}
|
|
246
267
|
|
|
247
268
|
} else {
|
|
@@ -249,7 +270,9 @@ function PassBody(actualHandler) {
|
|
|
249
270
|
try {
|
|
250
271
|
return actualHandler(...args, req.body)(req, res);
|
|
251
272
|
} catch (e) {
|
|
252
|
-
|
|
273
|
+
const status = e.status ?? 500;
|
|
274
|
+
status === 500 && log.error(e.stack);
|
|
275
|
+
reply(res, status, e.message || e)
|
|
253
276
|
}
|
|
254
277
|
}
|
|
255
278
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bootpress",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.0",
|
|
4
4
|
"description": "REST service methods for express",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/express": "^4.17.17",
|
|
31
|
+
"eslint": "^8.55.0",
|
|
31
32
|
"typescript": "^4.9.5"
|
|
32
33
|
}
|
|
33
34
|
}
|