bootpress 6.0.1 → 7.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/README.md CHANGED
@@ -12,7 +12,7 @@ Recommended tool: [create-bootpress-app](https://www.npmjs.com/package/create-bo
12
12
  ```ts
13
13
  import express from "express";
14
14
  import { HttpError, PassParams, RestService } from "bootpress";
15
- import { asInteger, getOrThrow } from "bootpress/helpers";
15
+ import { as, getOrThrow } from "bootpress/helpers";
16
16
 
17
17
  const app = express();
18
18
  app.use(express.json());
@@ -23,8 +23,8 @@ const UserServiceImpl = {
23
23
  return this.users;
24
24
  },
25
25
  findUserById(idInParams: string) {
26
- const id = asInteger(idInParams);
27
- return getOrThrow(this.users.find(user => user == id), new HttpError(404, "Not Found"));
26
+ const id = as(idInParams, "integer");
27
+ return getOrThrow(this.users.find(user => user === id), new HttpError(404, "Not Found"));
28
28
  }
29
29
  };
30
30
 
@@ -37,33 +37,34 @@ app.get("/users/:id", PassParams("id")(UserService.findUserById));
37
37
  #### Advanced usage:
38
38
  ```ts
39
39
  import { HttpError, HttpResponse, PassBody, PassParams, PassQueries, RestService } from "bootpress";
40
- import { asInteger, asSchema, getOrThrow } from "bootpress/helpers";
40
+ import { as, asStrict, getOrThrow } from "bootpress/helpers";
41
41
 
42
42
  class PostServiceImpl {
43
43
  posts = [1, 2, 3, 4, 5];
44
- findById(id: number | string) {
45
- console.log("looking for " + id);
44
+ findById(id: string) {
46
45
  return getOrThrow(
47
- this.posts.find(p => p == id),
46
+ this.posts.find(p => p === as(id, "integer")),
48
47
  new HttpError(404, "Post is not found")
49
48
  );
50
49
  }
51
50
  add(body: any) {
52
- let casted = asSchema(body, {
51
+ let casted = asStrict(body, {
53
52
  "id": "number"
54
53
  });
55
54
  this.posts.push(casted.id);
56
55
  return new HttpResponse(201, casted.id);
57
56
  }
58
57
  delete(deleteInQuery: string, idInQuery: string) {
59
- const idx = deleteInQuery === "yes" ? this.posts.indexOf(asInteger(idInQuery)) : -1;
58
+ const idx = deleteInQuery === "yes" ? this.posts.indexOf(as(idInQuery, "integer")) : -1;
60
59
  if (idx > -1) {
61
- this.posts.splice(idx, 1);
62
- this.#printDeleted(idInQuery);
60
+ this.#logDeleted(idInQuery);
61
+ return this.posts.splice(idx, 1);
62
+ }else {
63
+ throw new HttpError(404, "Post is not found")
63
64
  }
64
65
  }
65
66
  // use private methods to
66
- #printDeleted(id: number | string) {
67
+ #logDeleted(id: number | string) {
67
68
  console.warn(`post ${id} is deleted`)
68
69
  }
69
70
  findAll() {
@@ -126,4 +127,25 @@ class LogServiceImpl {
126
127
  const LogService = new LogServiceImpl();
127
128
 
128
129
  app.get("/logs", LogService.findAll() as RequestHandler)
129
- ```
130
+ ```
131
+
132
+ ## v7.0.0 Release Notes:
133
+
134
+ ### Deprecated helper methods:
135
+ - asSchema
136
+ - asString
137
+ - asBoolean
138
+ - asInteger
139
+ - asNumber
140
+
141
+ Please use "as" or "asStrict" instead of these functions. For example:
142
+
143
+ ```ts
144
+ //const x: string = asString(o); // deprecated
145
+ const x: string = as(o, "string");
146
+ ```
147
+
148
+ ### Added / Changed helper methods:
149
+ - asStrict : Asserts types strictly
150
+ - PassBodyAs(schema): Body must be as same as schema
151
+ - ParseBodyAs(schema): Body have to be parsable to schema
@@ -7,6 +7,8 @@ type TypeMap = {
7
7
  "boolean[]": boolean[],
8
8
  "number": number,
9
9
  "number[]": number[],
10
+ "integer": number,
11
+ "integer[]": number[]
10
12
  }
11
13
 
12
14
  type ValidTypeKeys = keyof TypeMap;
@@ -29,12 +31,7 @@ type TypedSchema<T> = {
29
31
  }
30
32
 
31
33
  export function getOrThrow<T, E extends HttpError>(data: T, error: E): T;
32
- export function getOrElse<T, E>(data: T, defaultValue: E): T | E;
33
- export function asBoolean(o: any): boolean;
34
- export function asNumber(o: any): number;
35
- export function asInteger(o: any): number;
36
- export function asString(o: any): string;
37
- export function asSchema<T extends JsSchema>(o: any, jsSchema: T): TypedSchema<T>;
34
+ export function getOrElse<T, E>(data: T, defaultValue: E): E extends NonNullable<infer T> ? E : T | E;
38
35
  export function schema<T extends JsSchema>(schema: T): T;
39
36
 
40
37
  type ExtendedTypeMap = {
@@ -58,4 +55,5 @@ type ExtendedTypeMap = {
58
55
 
59
56
  type ExtendedTypeKeys = keyof ExtendedTypeMap;
60
57
  type ExtValOf<T extends ExtendedTypeKeys> = ExtendedTypeMap[T]
61
- export function as<T extends (ExtendedTypeKeys | JsSchema | ArraySchema)>(o:any, schema: T): T extends ExtendedTypeKeys ? ExtValOf<T> : TypedSchema<T>;
58
+ export function as<T extends (ExtendedTypeKeys | JsSchema | ArraySchema)>(o:any, type: T, errorVariableName?: string): T extends ExtendedTypeKeys ? ExtValOf<T> : TypedSchema<T>;
59
+ export function asStrict<T extends (ExtendedTypeKeys | JsSchema | ArraySchema)>(o:any, type: T, errorVariableName?: string): T extends ExtendedTypeKeys ? ExtValOf<T> : TypedSchema<T>;
package/helpers/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  const { HttpError } = require("../types");
2
2
 
3
+ const allowedPrimitives = ["string", "number", "boolean", "integer"];
4
+
3
5
  function getOrThrow(data, error) {
4
6
  if (data === null || data === undefined) {
5
7
  throw error;
@@ -34,9 +36,9 @@ function asBoolean(o, errorMessage = undefined, errorStatus = 400) {
34
36
  return validBooleanStrings.get(lowercased);
35
37
  } else if (typeof o === "boolean") {
36
38
  return o;
37
- } else if (typeof o === "number"){
38
- if(o === 1) return true;
39
- if(o === 0) return false;
39
+ } else if (typeof o === "number") {
40
+ if (o === 1) return true;
41
+ if (o === 0) return false;
40
42
  }
41
43
  throw new HttpError(errorStatus, errorMessage);
42
44
  }
@@ -68,7 +70,7 @@ function asInteger(o, errorMessage = undefined, errorStatus = 400) {
68
70
  }
69
71
 
70
72
  function asString(o, errorMessage = undefined, errorStatus = 400) {
71
- errorMessage = errorMessage ?? `Value ${o} should have been a string but it's not`;
73
+ errorMessage = errorMessage ?? `Value ${o} should have been a string but it's ${typeof o}`;
72
74
  const validTypes = ["string", "number"];
73
75
  if (validTypes.includes(typeof o)) {
74
76
  return '' + o;
@@ -77,7 +79,10 @@ function asString(o, errorMessage = undefined, errorStatus = 400) {
77
79
  }
78
80
  }
79
81
 
80
- function asSchema(o, schema) {
82
+ function asSchema(o, schema, strict = false) {
83
+ if(!(schema instanceof Object) || Array.isArray(schema)){
84
+ throw new HttpError(500, `Schema is not valid ${JSON.stringify(schema)}`);
85
+ }
81
86
  const schemaKeyValues = Object.entries(schema);
82
87
  let result = {};
83
88
  for (let i = 0; i < schemaKeyValues.length; i++) {
@@ -90,33 +95,19 @@ function asSchema(o, schema) {
90
95
  }
91
96
  }
92
97
  const expectedType = schemaKeyValues[i][1];
93
- const errorMessage = o[key] == null ? `Value of ${key} should have been a ${expectedType} but it's null` : `Value of ${key} should have been a ${expectedType} but it's a ${typeof o[key]}`;
94
98
 
95
99
  if (typeof expectedType === "object") {
96
100
  if (Array.isArray(expectedType)) {
97
101
  result[key] = [];
98
102
  for (let j = 0; j < o[key].length; j++) {
99
- result[key][j] = asSchema(o[key][j], expectedType[0])
103
+ result[key][j] = asSchema(o[key][j], expectedType[0], strict)
100
104
  }
101
105
  } else {
102
- result[key] = asSchema(o[key], expectedType);
106
+ result[key] = asSchema(o[key], expectedType, strict);
103
107
  }
104
108
  }
105
109
  else if (typeof expectedType === "string") {
106
- if (expectedType.endsWith("[]")) {
107
- const elementType = expectedType.replace("[]", "");
108
- for (let j = 0; j < o[key].length; j++) {
109
- if (typeof o[key][j] !== elementType) {
110
- throw new HttpError(400, `Each element of ${key} should have been a ${elementType} but a ${typeof o[key][j]} is present (${o[key][j]})`);
111
- }
112
- }
113
- result[key] = o[key];
114
- }
115
- else if (typeof o[key] === expectedType) {
116
- result[key] = o[key];
117
- } else {
118
- throw new HttpError(400, errorMessage);
119
- }
110
+ result[key] = strict ? asStrict(o[key], expectedType, key) : as(o[key], expectedType, key);
120
111
  }
121
112
  else {
122
113
  throw new HttpError(500, `Type of a schema key should be a primitive type or another schema`);
@@ -130,14 +121,10 @@ function schema(schema) {
130
121
  return schema;
131
122
  }
132
123
 
133
- function asArrayOf(o, elementType) {
124
+ function asPrimitiveArrayOf(o, elementType) {
134
125
  if (Array.isArray(o)) {
135
126
  for (let i = 0; i < o.length; i++) {
136
- if(elementType === "integer"){
137
- asInteger(o[i]);
138
- }else if(typeof o[i] != elementType){
139
- throw new HttpError(400, `Each element in array should have been a ${elementType} but ${o[i]} is present with type ${typeof o[i]}`);
140
- }
127
+ o[i] = checkPrimitive(o[i], elementType, "Array elemet");
141
128
  }
142
129
  return o;
143
130
  } else {
@@ -145,49 +132,50 @@ function asArrayOf(o, elementType) {
145
132
  }
146
133
  }
147
134
 
148
- function as(o, type) {
149
- if (typeof type == "string") {
135
+ function as(o, type, namedErrorVariable = o) {
136
+ if (typeof type === "string") {
150
137
  if (type.endsWith("[]")) {
151
138
  // array check
152
139
  const elementType = type.replace("[]", "");
153
- return asArrayOf(o, elementType);
154
- } else {
155
- if(type.endsWith("?") && o == null){
140
+ return asPrimitiveArrayOf(o, elementType);
141
+ } else { // non array types:
142
+ if (type.endsWith("?") && o == null) {
156
143
  return null;
157
- }else if(type.endsWith("?") && o != null ){
144
+ } else if (type.endsWith("?") && o != null) {
158
145
  const actualType = type.replace("?", "");
159
- return as(o, actualType);
146
+ return as(o, actualType, namedErrorVariable);
160
147
  }
161
148
  // primitive check
162
149
  switch (type) {
163
150
  case "string":
164
- return asString(o);
151
+ return asString(o, `Type of ${namedErrorVariable} should have been a string but it's ${o == null ? "null" : typeof o}`);
165
152
  case "number":
166
- return asNumber(o);
153
+ return asNumber(o, `Type of ${namedErrorVariable} should have been a number but it's ${o == null ? "null" : typeof o}`);
167
154
  case "boolean":
168
- return asBoolean(o);
155
+ return asBoolean(o, `Type of ${namedErrorVariable} should have been a boolean but it's ${o == null ? "null" : typeof o}`);
169
156
  case "integer":
170
- return asInteger(o);
157
+ return asInteger(o, `Type of ${namedErrorVariable} should have been an integer but it's ${o == null ? "null" : typeof o}`);
171
158
  default:
172
159
  throw new HttpError(500, `Unsupported type ${type}`);
173
160
  }
174
161
  }
175
- } else if (typeof type == "object" && type != null) {
162
+ } else if (typeof type === "object" && type != null) {
176
163
  if (Array.isArray(type)) {
177
- if(type.length > 1){
164
+ if (type.length > 1) {
178
165
  throw new HttpError(500, `You can define only one schema for types ArrayOf<Schema>`);
179
- }else if (type.length < 1){
166
+ } else if (type.length < 1) {
180
167
  throw new HttpError(500, `You must define a schema for types ArrayOf<Schema>`);
181
168
  }
182
169
  // array schema validation
183
- if(!Array.isArray(o)){
170
+ if (!Array.isArray(o)) {
184
171
  throw new HttpError(400, `Provided value should have been an array. (${JSON.stringify(o)})`)
185
172
  }
186
173
  const providedSchema = type[0];
187
- for(let i = 0; i < o.length; i++){
188
- asSchema(o[i], providedSchema);
174
+ let result = [];
175
+ for (let i = 0; i < o.length; i++) {
176
+ result.push(asSchema(o[i], providedSchema));
189
177
  }
190
- return o;
178
+ return result;
191
179
  } else {
192
180
  // schema validation
193
181
  return asSchema(o, type);
@@ -197,14 +185,69 @@ function as(o, type) {
197
185
  }
198
186
  }
199
187
 
188
+ function asStrict(o, type, namedErrorVariable = o) {
189
+ if (typeof type === "string") {
190
+ if (type.endsWith("[]")) {
191
+ // array check
192
+ const elementType = type.replace("[]", "");
193
+ return asPrimitiveArrayOf(o, elementType);
194
+ } else { // non array types:
195
+ if (type.endsWith("?") && o == null) {
196
+ return null;
197
+ } else if (type.endsWith("?") && o != null) {
198
+ const actualType = type.replace("?", "");
199
+ return asStrict(o, actualType, namedErrorVariable);
200
+ }
201
+ // primitive check
202
+ if (!allowedPrimitives.includes(type)) {
203
+ throw new HttpError(500, `Unsupported type ${type}`);
204
+ }
205
+ return checkPrimitive(o, type, namedErrorVariable);
206
+ }
207
+ } else if (typeof type === "object" && type != null) {
208
+ if (Array.isArray(type)) {
209
+ if (type.length > 1) {
210
+ throw new HttpError(500, `You can define only one schema for types ArrayOf<Schema>`);
211
+ } else if (type.length < 1) {
212
+ throw new HttpError(500, `You must define a schema for types ArrayOf<Schema>`);
213
+ }
214
+ // array schema validation
215
+ if (!Array.isArray(o)) {
216
+ throw new HttpError(400, `Provided value should have been an array but it's ${typeof o}`)
217
+ }
218
+ const providedSchema = type[0];
219
+ let result = [];
220
+ for (let i = 0; i < o.length; i++) {
221
+ result.push(asSchema(o[i], providedSchema, true));
222
+ }
223
+ return result;
224
+ } else {
225
+ // schema validation
226
+ return asSchema(o, type, true);
227
+ }
228
+ } else {
229
+ throw new HttpError(500, `Unsupported type check ${type}`)
230
+ }
231
+ }
232
+
233
+ function checkPrimitive(o, type, varName = o) {
234
+ const error = new HttpError(400, `${varName} should have been a ${type}. But it's ${typeof o}`);
235
+ if (type === "integer") {
236
+ if (!Number.isInteger(o)) {
237
+ throw error;
238
+ }
239
+ return o;
240
+ }
241
+ if (typeof o !== type) {
242
+ throw error;
243
+ }
244
+ return o;
245
+ }
246
+
200
247
  module.exports = {
201
248
  getOrThrow,
202
249
  getOrElse,
203
- asBoolean,
204
- asNumber,
205
- asInteger,
206
- asString,
207
- asSchema,
208
250
  schema,
209
- as
251
+ as,
252
+ asStrict
210
253
  }
package/index.d.ts CHANGED
@@ -16,6 +16,7 @@ declare function RestMethod<T>(callback: () => T): RequestHandler;
16
16
  declare function Restify(target: any, key: string, desc: PropertyDescriptor): PropertyDescriptor;
17
17
 
18
18
  declare function PassBody(serviceFunction: RequestHandler | RequsetHandlerWithArgs): RequestHandler
19
+ declare function ParseBodyAs(type: ValidTypeKeys | JsSchema | ArraySchema ): (serviceFunction: RequestHandler | RequsetHandlerWithArgs) => RequestHandler
19
20
  declare function PassBodyAs(type: ValidTypeKeys | JsSchema | ArraySchema ): (serviceFunction: RequestHandler | RequsetHandlerWithArgs) => RequestHandler
20
21
  declare function PassAllParams(serviceFunction: RequestHandler | RequsetHandlerWithArgs): RequestHandler
21
22
  declare function PassAllQueries(serviceFunction: RequestHandler | RequsetHandlerWithArgs): RequestHandler
@@ -38,6 +39,7 @@ export {
38
39
  PassAllCookies,
39
40
  PassBody,
40
41
  PassBodyAs,
42
+ ParseBodyAs,
41
43
  PassRequest,
42
44
  PassResponse
43
45
  }
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- const { as } = require("./helpers");
1
+ const { as, asStrict } = require("./helpers");
2
2
  const { HttpError } = require("./types");
3
3
 
4
4
  const protectedProperties = [
@@ -155,13 +155,26 @@ function PassAllQueries(actualHandler) {
155
155
  }
156
156
  }
157
157
 
158
- function PassBody(actualHandler) {
159
- return (...args) => {
160
- if (isRequstHandlerArgs(args)) {
161
- const req = args.at(-3); const res = args.at(-2);
162
- return actualHandler(req.body)(req, res);
163
- } else {
164
- return (req, res) => actualHandler(...args, req.body)(req, res)
158
+ function ParseBodyAs(type) {
159
+ return (actualHandler) => {
160
+ return (...args) => {
161
+ if (isRequstHandlerArgs(args)) {
162
+ const req = args.at(-3); const res = args.at(-2);
163
+ try {
164
+ return actualHandler(as(req.body, type))(req, res);
165
+ } catch (e) {
166
+ reply(res, e.status || 500, e.message || e);
167
+ }
168
+
169
+ } else {
170
+ return (req, res) => {
171
+ try {
172
+ return actualHandler(...args, as(req.body, type))(req, res);
173
+ } catch (e) {
174
+ reply(res, e.status || 500, e.message || e)
175
+ }
176
+ }
177
+ }
165
178
  }
166
179
  }
167
180
  }
@@ -172,16 +185,16 @@ function PassBodyAs(type) {
172
185
  if (isRequstHandlerArgs(args)) {
173
186
  const req = args.at(-3); const res = args.at(-2);
174
187
  try {
175
- return actualHandler(as(req.body, type))(req, res);
188
+ return actualHandler(asStrict(req.body, type))(req, res);
176
189
  } catch (e) {
177
190
  reply(res, e.status || 500, e.message || e);
178
191
  }
179
192
 
180
193
  } else {
181
194
  return (req, res) => {
182
- try{
183
- return actualHandler(...args, as(req.body, type))(req, res);
184
- }catch(e){
195
+ try {
196
+ return actualHandler(...args, asStrict(req.body, type))(req, res);
197
+ } catch (e) {
185
198
  reply(res, e.status || 500, e.message || e)
186
199
  }
187
200
  }
@@ -190,6 +203,28 @@ function PassBodyAs(type) {
190
203
  }
191
204
  }
192
205
 
206
+ function PassBody(actualHandler) {
207
+ return (...args) => {
208
+ if (isRequstHandlerArgs(args)) {
209
+ const req = args.at(-3); const res = args.at(-2);
210
+ try {
211
+ return actualHandler(req.body)(req, res);
212
+ } catch (e) {
213
+ reply(res, e.status || 500, e.message || e);
214
+ }
215
+
216
+ } else {
217
+ return (req, res) => {
218
+ try {
219
+ return actualHandler(...args, req.body)(req, res);
220
+ } catch (e) {
221
+ reply(res, e.status || 500, e.message || e)
222
+ }
223
+ }
224
+ }
225
+ }
226
+ }
227
+
193
228
  function PassRequest(actualHandler) {
194
229
  return (...args) => {
195
230
  if (isRequstHandlerArgs(args)) {
@@ -249,6 +284,7 @@ module.exports = {
249
284
  PassCookies,
250
285
  PassBody,
251
286
  PassBodyAs,
287
+ ParseBodyAs,
252
288
  PassRequest,
253
289
  PassResponse
254
290
  }
package/package.json CHANGED
@@ -1,33 +1,33 @@
1
- {
2
- "name": "bootpress",
3
- "version": "6.0.1",
4
- "description": "REST service methods for express",
5
- "main": "index.js",
6
- "repository": {
7
- "type": "git",
8
- "url": "git+https://github.com/ufukbakan/bootpress.git"
9
- },
10
- "keywords": [
11
- "bootpress",
12
- "express",
13
- "rest",
14
- "service",
15
- "methods",
16
- "spring",
17
- "boot",
18
- "like"
19
- ],
20
- "author": "Ufuk Bakan",
21
- "license": "MIT",
22
- "bugs": {
23
- "url": "https://github.com/ufukbakan/bootpress/issues"
24
- },
25
- "homepage": "https://github.com/ufukbakan/bootpress#readme",
26
- "dependencies": {
27
- "express": "^4.18.2"
28
- },
29
- "devDependencies": {
30
- "@types/express": "^4.17.17",
31
- "typescript": "^4.9.5"
32
- }
33
- }
1
+ {
2
+ "name": "bootpress",
3
+ "version": "7.0.0",
4
+ "description": "REST service methods for express",
5
+ "main": "index.js",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/ufukbakan/bootpress.git"
9
+ },
10
+ "keywords": [
11
+ "bootpress",
12
+ "express",
13
+ "rest",
14
+ "service",
15
+ "methods",
16
+ "spring",
17
+ "boot",
18
+ "like"
19
+ ],
20
+ "author": "Ufuk Bakan",
21
+ "license": "MIT",
22
+ "bugs": {
23
+ "url": "https://github.com/ufukbakan/bootpress/issues"
24
+ },
25
+ "homepage": "https://github.com/ufukbakan/bootpress#readme",
26
+ "dependencies": {
27
+ "express": "^4.18.2"
28
+ },
29
+ "devDependencies": {
30
+ "@types/express": "^4.17.17",
31
+ "typescript": "^4.9.5"
32
+ }
33
+ }