node-switchbot 1.0.7

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.
@@ -0,0 +1,471 @@
1
+ /* ------------------------------------------------------------------
2
+ * node-switchbot - parameter-checker.js
3
+ *
4
+ * Copyright (c) 2019, Futomi Hatano, All rights reserved.
5
+ * Released under the MIT license
6
+ * Date: 2019-11-16
7
+ * ---------------------------------------------------------------- */
8
+ 'use strict';
9
+
10
+ class ParameterChecker {
11
+ constructor() {
12
+ this._error = null;
13
+ }
14
+
15
+ get error() {
16
+ // ----------------------------------
17
+ // Error
18
+ // {
19
+ // code: 'TYPE_INVALID',
20
+ // message: 'The `age` must be an integer.'
21
+ // name: 'age',
22
+ // }
23
+ // ---------------------------------
24
+ return this._error;
25
+ }
26
+
27
+ isSpecified(value) {
28
+ return (value === void 0) ? false : true;
29
+ }
30
+
31
+ /* ------------------------------------------------------------------
32
+ * check(obj, rule, required)
33
+ * - Check if the specified object contains valid values
34
+ *
35
+ * [Arguments]
36
+ * - obj | Object | Required | Object including parameters you want to check
37
+ * - rules | Object | Required | Object including rules for the parameters
38
+ * - required | Boolean | Optional | Flag whther the `obj` is required or not.
39
+ * | | | The default is `false`
40
+ *
41
+ * [Return value]
42
+ * - If the value is valid, this method will return `true`.
43
+ * - If the value is invalid, this method will return `false` and
44
+ * an `Error` object will be set to `this._error`.
45
+ *
46
+ * [Usage]
47
+ * let valid = parameterChecker.check(params, {
48
+ * level: {
49
+ * required: false,
50
+ * type: 'integer',
51
+ * max: 100
52
+ * },
53
+ * greeting: {
54
+ * required: true, // But an empty string is allowed.
55
+ * type: 'string',
56
+ * max: 20 // the number of characters must be up to 20.
57
+ * }
58
+ * });
59
+ * if(!valid) {
60
+ * let e = parameterChecker.error.message;
61
+ * throw new Error(message);
62
+ * }
63
+ * ---------------------------------------------------------------- */
64
+ check(obj, rules, required = false) {
65
+ this._error = null;
66
+ if (required) {
67
+ if (!this.isSpecified(obj)) {
68
+ this._error = {
69
+ code: 'MISSING_REQUIRED',
70
+ message: 'The first argument is missing.'
71
+ };
72
+ return false;
73
+ }
74
+ } else {
75
+ if (!obj) {
76
+ return true;
77
+ }
78
+ }
79
+
80
+ if (!this.isObject(obj)) {
81
+ this._error = {
82
+ code: 'MISSING_REQUIRED',
83
+ message: 'The first argument is missing.'
84
+ };
85
+ return false;
86
+ }
87
+
88
+ let result = true;
89
+ let name_list = Object.keys(rules);
90
+
91
+ for (let i = 0; i < name_list.length; i++) {
92
+ let name = name_list[i];
93
+ let v = obj[name];
94
+ let rule = rules[name];
95
+
96
+ if (!rule) {
97
+ rule = {};
98
+ }
99
+ if (!this.isSpecified(v)) {
100
+ if (rule.required) {
101
+ result = false;
102
+ this._error = {
103
+ code: 'MISSING_REQUIRED',
104
+ message: 'The `' + name + '` is required.'
105
+ };
106
+ break;
107
+ } else {
108
+ continue;
109
+ }
110
+ }
111
+
112
+ if (rule.type === 'float') {
113
+ result = this.isFloat(v, rule, name);
114
+ } else if (rule.type === 'integer') {
115
+ result = this.isInteger(v, rule, name);
116
+ } else if (rule.type === 'boolean') {
117
+ result = this.isBoolean(v, rule, name);
118
+ } else if (rule.type === 'array') {
119
+ result = this.isArray(v, rule, name);
120
+ } else if (rule.type === 'object') {
121
+ result = this.isObject(v, rule, name);
122
+ } else if (rule.type === 'string') {
123
+ result = this.isString(v, rule, name);
124
+ } else {
125
+ result = false;
126
+ this._error = {
127
+ code: 'TYPE_UNKNOWN',
128
+ message: 'The rule specified for the `' + name + '` includes an unknown type: ' + rule.type,
129
+ };
130
+ }
131
+
132
+ if (result === false) {
133
+ this._error.name = name;
134
+ break;
135
+ }
136
+ }
137
+
138
+ return result;
139
+ }
140
+
141
+ /* ------------------------------------------------------------------
142
+ * isFloat(value, rule, name)
143
+ * - Check if the value is a float
144
+ *
145
+ * [Arguments]
146
+ * - value | Any | Required | The value you want to check
147
+ * - rule | Object | Optional |
148
+ * - required | Boolean | Optional | Required or not. Default is `false`.
149
+ * - min | Float | Optional | Minimum number
150
+ * - max | Float | Optional | Maximum number
151
+ * - enum | Array | Optional | list of possible values
152
+ * - name | String | Optional | Parameter name
153
+ *
154
+ * If non-number value is specified to the `min` or `max`,
155
+ * they will be ignored.
156
+ *
157
+ * [Return value]
158
+ * - If the value is valid, this method will return `true`.
159
+ * - If the value is invalid, this method will return `false` and
160
+ * an `Error` object will be set to `this._error`.
161
+ * ---------------------------------------------------------------- */
162
+ isFloat(value, rule = {}, name = 'value') {
163
+ this._error = null;
164
+
165
+ if (!rule.required && !this.isSpecified(value)) {
166
+ return true;
167
+ }
168
+
169
+ if (typeof (value) !== 'number') {
170
+ this._error = {
171
+ code: 'TYPE_INVALID',
172
+ message: 'The `' + name + '` must be a number (integer or float).'
173
+ };
174
+ return false;
175
+ }
176
+
177
+ if (typeof (rule.min) === 'number') {
178
+ if (value < rule.min) {
179
+ this._error = {
180
+ code: 'VALUE_UNDERFLOW',
181
+ message: 'The `' + name + '` must be grater than or equal to ' + rule.min + '.'
182
+ };
183
+ return false;
184
+ }
185
+ }
186
+ if (typeof (rule.max) === 'number') {
187
+ if (value > rule.max) {
188
+ this._error = {
189
+ code: 'VALUE_OVERFLOW',
190
+ message: 'The `' + name + '` must be less than or equal to ' + rule.max + '.'
191
+ };
192
+ return false;
193
+ }
194
+ }
195
+ if (Array.isArray(rule.enum) && rule.enum.length > 0) {
196
+ if (rule.enum.indexOf(value) === -1) {
197
+ this._error = {
198
+ code: 'ENUM_UNMATCH',
199
+ message: 'The `' + name + '` must be any one of ' + JSON.stringify(rule.enum) + '.'
200
+ };
201
+ return false;
202
+ }
203
+ }
204
+
205
+ return true;
206
+ }
207
+
208
+ /* ------------------------------------------------------------------
209
+ * isInteger(value, rule)
210
+ * - Check if the value is an integer
211
+ *
212
+ * [Arguments]
213
+ * - value | Any | Required | The value you want to check
214
+ * - rule | Object | Optional |
215
+ * - required | Boolean | Optional | Required or not. Default is `false`.|
216
+ * - min | Float | Optional | Minimum number
217
+ * - max | Float | Optional | Maximum number
218
+ * - enum | Array | Optional | list of possible values
219
+ * - name | String | Optional | Parameter name
220
+ *
221
+ * If non-number value is specified to the `min` or `max`,
222
+ * they will be ignored.
223
+ *
224
+ * [Return value]
225
+ * - If the value is valid, this method will return `true`.
226
+ * - If the value is invalid, this method will return `false` and
227
+ * an `Error` object will be set to `this._error`.
228
+ * ---------------------------------------------------------------- */
229
+ isInteger(value, rule = {}, name = 'value') {
230
+ this._error = null;
231
+
232
+ if (!rule.required && !this.isSpecified(value)) {
233
+ return true;
234
+ }
235
+
236
+ if (this.isFloat(value, rule)) {
237
+ if (value % 1 === 0) {
238
+ return true;
239
+ } else {
240
+ this._error = {
241
+ code: 'TYPE_INVALID',
242
+ message: 'The `' + name + '` must be an integer.'
243
+ };
244
+ return false;
245
+ }
246
+ } else {
247
+ return false;
248
+ }
249
+ }
250
+
251
+ /* ------------------------------------------------------------------
252
+ * isBoolean(value, rule, name)
253
+ * - Check if the value is a boolean.
254
+ *
255
+ * [Arguments]
256
+ * - value | Any | Required | The value you want to check
257
+ * - rule | Object | Optional |
258
+ * - required | Boolean | Optional | Required or not. Default is `false`.
259
+ * - name | String | Optional | Parameter name
260
+ *
261
+ * [Return value]
262
+ * - If the value is valid, this method will return `true`.
263
+ * - If the value is invalid, this method will return `false` and
264
+ * an `Error` object will be set to `this._error`.
265
+ * ---------------------------------------------------------------- */
266
+ isBoolean(value, rule = {}, name = 'value') {
267
+ this._error = null;
268
+
269
+ if (!rule.required && !this.isSpecified(value)) {
270
+ return true;
271
+ }
272
+
273
+ if (typeof (value) !== 'boolean') {
274
+ this._error = {
275
+ code: 'TYPE_INVALID',
276
+ message: 'The `' + name + '` must be boolean.'
277
+ };
278
+ return false;
279
+ }
280
+ return true;
281
+ }
282
+
283
+ /* ------------------------------------------------------------------
284
+ * isObject(value)
285
+ * - Check if the value is an object
286
+ *
287
+ * [Arguments]
288
+ * - value | Any | Required | The value you want to check
289
+ * - rule | Object | Optional |
290
+ * - required | Boolean | Optional | Required or not. Default is `false`.
291
+ * - name | String | Optional | Parameter name
292
+ *
293
+ * [Return value]
294
+ * - If the value is valid, this method will return `true`.
295
+ * - If the value is invalid, this method will return `false` and
296
+ * an `Error` object will be set to `this._error`.
297
+ * ---------------------------------------------------------------- */
298
+ isObject(value, rule = {}, name = 'value') {
299
+ this._error = null;
300
+ if (!rule.required && !this.isSpecified(value)) {
301
+ return true;
302
+ }
303
+
304
+ if (typeof (value) !== 'object' || value === null || Array.isArray(value)) {
305
+ this._error = {
306
+ code: 'TYPE_INVALID',
307
+ message: 'The `' + name + '` must be an object.'
308
+ };
309
+ return false;
310
+ }
311
+ return true;
312
+ }
313
+
314
+ /* ------------------------------------------------------------------
315
+ * isArray(value, rule, name)
316
+ * - Check if the value is an `Array` object
317
+ *
318
+ * [Arguments]
319
+ * - value | Any | Required | The value you want to check
320
+ * - rule | Object | Optional |
321
+ * - required | Boolean | Optional | Required or not. Default is `false`.
322
+ * - min | Integer | Optional | Minimum number of elements in the array
323
+ * - max | Integer | Optional | Maximum number of elements in the array
324
+ * - name | String | Optional | Parameter name
325
+ *
326
+ * If non-number value is specified to the `min` or `max`,
327
+ * they will be ignored.
328
+ *
329
+ * [Return value]
330
+ * - If the value is valid, this method will return `true`.
331
+ * - If the value is invalid, this method will return `false` and
332
+ * an `Error` object will be set to `this._error`.
333
+ * ---------------------------------------------------------------- */
334
+ isArray(value, rule = {}, name = 'value') {
335
+ this._error = null;
336
+
337
+ if (!rule.required && !this.isSpecified(value)) {
338
+ return true;
339
+ }
340
+
341
+ if (!Array.isArray(value)) {
342
+ this._error = {
343
+ code: 'TYPE_INVALID',
344
+ message: 'The value must be an array.'
345
+ };
346
+ return false;
347
+ }
348
+
349
+ if (typeof (rule.min) === 'number') {
350
+ if (value.length < rule.min) {
351
+ this._error = {
352
+ code: 'LENGTH_UNDERFLOW',
353
+ message: 'The number of characters in the `' + name + '` must be grater than or equal to ' + rule.min + '.'
354
+ };
355
+ return false;
356
+ }
357
+ }
358
+ if (typeof (rule.max) === 'number') {
359
+ if (value.length > rule.max) {
360
+ this._error = {
361
+ code: 'LENGTH_OVERFLOW',
362
+ message: 'The number of characters in the `' + name + '` must be less than or equal to ' + rule.max + '.'
363
+ };
364
+ return false;
365
+ }
366
+ }
367
+
368
+ return true;
369
+ }
370
+
371
+ /* ------------------------------------------------------------------
372
+ * isString(value, rule, name)
373
+ * - Check if the value is an `Array` object
374
+ *
375
+ * [Arguments]
376
+ * - value | Any | Required | The value you want to check
377
+ * - rule | Object | Optional |
378
+ * - required | Boolean | Optional | Required or not. Default is `false`.
379
+ * - min | Integer | Optional | Minimum number of characters in the string
380
+ * - max | Integer | Optional | Maximum number of characters in the string
381
+ * - minBytes | Integer | Optional | Minimum bytes of the string (UTF-8)
382
+ * - maxBytes | Integer | Optional | Maximum bytes of the string (UTF-8)
383
+ * - pattern | RegExp | Optional | Pattern of the string
384
+ * - enum | Array | Optional | list of possible values
385
+ * - name | String | Optional | Parameter name
386
+ *
387
+ * If non-number value is specified to the `min` or `max`,
388
+ * they will be ignored.
389
+ *
390
+ * [Return value]
391
+ * - If the value is valid, this method will return `true`.
392
+ * - If the value is invalid, this method will return `false` and
393
+ * an `Error` object will be set to `this._error`.
394
+ * ---------------------------------------------------------------- */
395
+ isString(value, rule = {}, name = 'value') {
396
+ this._error = null;
397
+
398
+ if (!rule.required && !this.isSpecified(value)) {
399
+ return true;
400
+ }
401
+
402
+ if (typeof (value) !== 'string') {
403
+ this._error = {
404
+ code: 'TYPE_INVALID',
405
+ message: 'The value must be a string.'
406
+ };
407
+ return false;
408
+ }
409
+
410
+ if (typeof (rule.min) === 'number') {
411
+ if (value.length < rule.min) {
412
+ this._error = {
413
+ code: 'LENGTH_UNDERFLOW',
414
+ message: 'The number of characters in the `' + name + '` must be grater than or equal to ' + rule.min + '.'
415
+ };
416
+ return false;
417
+ }
418
+ }
419
+ if (typeof (rule.max) === 'number') {
420
+ if (value.length > rule.max) {
421
+ this._error = {
422
+ code: 'LENGTH_OVERFLOW',
423
+ message: 'The number of characters in the `' + name + '` must be less than or equal to ' + rule.max + '.'
424
+ };
425
+ return false;
426
+ }
427
+ }
428
+ if (typeof (rule.minBytes) === 'number') {
429
+ let blen = Buffer.from(value, 'utf8').length;
430
+ if (blen < rule.minBytes) {
431
+ this._error = {
432
+ code: 'LENGTH_UNDERFLOW',
433
+ message: 'The byte length of the `' + name + '` (' + blen + ' bytes) must be grater than or equal to ' + rule.minBytes + ' bytes.'
434
+ };
435
+ return false;
436
+ }
437
+ }
438
+ if (typeof (rule.maxBytes) === 'number') {
439
+ let blen = Buffer.from(value, 'utf8').length;
440
+ if (blen > rule.maxBytes) {
441
+ this._error = {
442
+ code: 'LENGTH_OVERFLOW',
443
+ message: 'The byte length of the `' + name + '` (' + blen + ' bytes) must be less than or equal to ' + rule.maxBytes + ' bytes.'
444
+ };
445
+ return false;
446
+ }
447
+ }
448
+ if (rule.pattern instanceof RegExp) {
449
+ if (!rule.pattern.test(v)) {
450
+ this._error = {
451
+ code: 'PATTERN_UNMATCH',
452
+ message: 'The `' + name + '` does not conform with the pattern.'
453
+ };
454
+ return false;
455
+ }
456
+ }
457
+ if (Array.isArray(rule.enum) && rule.enum.length > 0) {
458
+ if (rule.enum.indexOf(value) === -1) {
459
+ this._error = {
460
+ code: 'ENUM_UNMATCH',
461
+ message: 'The `' + name + '` must be any one of ' + JSON.stringify(rule.enum) + '.'
462
+ };
463
+ return false;
464
+ }
465
+ }
466
+
467
+ return true;
468
+ }
469
+ }
470
+
471
+ module.exports = new ParameterChecker();