1 | 'use strict';
|
2 |
|
3 | const isPlainObject = require('lodash.isplainobject');
|
4 | const isArray = require('lodash.isarray');
|
5 | const isString = require('lodash.isstring');
|
6 | const isNumber = require('lodash.isnumber');
|
7 | const has = require('lodash.has');
|
8 | const keys = require('lodash.keys');
|
9 | const without = require('lodash.without');
|
10 | const toNumber = require('lodash.tonumber');
|
11 | const toString = require('lodash.tostring');
|
12 | const includes = require('lodash.includes');
|
13 | const bson = require('bson');
|
14 | const MinKey = bson.MinKey;
|
15 | const MaxKey = bson.MaxKey;
|
16 | const Long = bson.Long;
|
17 | const Double = bson.Double;
|
18 | const Int32 = bson.Int32;
|
19 | const Decimal128 = bson.Decimal128;
|
20 | const Binary = bson.Binary;
|
21 | const BSONRegExp = bson.BSONRegExp;
|
22 | const Code = bson.Code;
|
23 | const Symbol = bson.Symbol;
|
24 | const Timestamp = bson.Timestamp;
|
25 |
|
26 |
|
27 |
|
28 |
|
29 | const OBJECT = 'Object';
|
30 |
|
31 |
|
32 |
|
33 |
|
34 | const ARRAY = 'Array';
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | const TRUE = 'true';
|
40 |
|
41 |
|
42 |
|
43 |
|
44 | const FALSE = 'false';
|
45 |
|
46 |
|
47 |
|
48 |
|
49 | const LONG = 'Long';
|
50 |
|
51 | const INT_32 = 'Int32';
|
52 | const INT_64 = 'Int64';
|
53 | const DOUBLE = 'Double';
|
54 | const DECIMAL_128 = 'Decimal128';
|
55 | const OBJECT_TYPE = '[object Object]';
|
56 | const EMPTY = '';
|
57 | const OBJECT_ID = 'ObjectID';
|
58 |
|
59 |
|
60 |
|
61 |
|
62 | const BSON_TYPE = '_bsontype';
|
63 |
|
64 |
|
65 |
|
66 |
|
67 | const MATCH = /\[object (\w+)\]/;
|
68 |
|
69 |
|
70 |
|
71 |
|
72 | const BSON_INT32_MAX = 0x7FFFFFFF;
|
73 |
|
74 |
|
75 |
|
76 |
|
77 | const BSON_INT32_MIN = -0x80000000;
|
78 |
|
79 | const BSON_INT64_MAX = Math.pow(2, 63) - 1;
|
80 | const BSON_INT64_MIN = -(BSON_INT64_MAX);
|
81 |
|
82 |
|
83 |
|
84 |
|
85 | const NUMBER_REGEX = /^-?\d+$/;
|
86 |
|
87 |
|
88 |
|
89 |
|
90 | const NUMBER_TYPES = [
|
91 | 'Long',
|
92 | 'Int32',
|
93 | 'Double',
|
94 | 'Decimal128'
|
95 | ];
|
96 |
|
97 | const toDate = (object) => {
|
98 | return new Date(object);
|
99 | };
|
100 |
|
101 | const toMinKey = () => {
|
102 | return new MinKey();
|
103 | };
|
104 |
|
105 | const toMaxKey = () => {
|
106 | return new MaxKey();
|
107 | };
|
108 |
|
109 | const toUndefined = () => {
|
110 | return undefined;
|
111 | };
|
112 |
|
113 | const toNull = () => {
|
114 | return null;
|
115 | };
|
116 |
|
117 | const toBoolean = (object) => {
|
118 | if (isString(object)) {
|
119 | if (object.toLowerCase() === TRUE) {
|
120 | return true;
|
121 | } else if (object.toLowerCase() === FALSE) {
|
122 | return false;
|
123 | }
|
124 | throw new Error(`'${object}' is not a valid boolean string`);
|
125 | }
|
126 | if (object) {
|
127 | return true;
|
128 | }
|
129 | return false;
|
130 | };
|
131 |
|
132 | const toObject = (object) => {
|
133 | if (isPlainObject(object)) {
|
134 | return object;
|
135 | }
|
136 | return {};
|
137 | };
|
138 |
|
139 | const toArray = (object) => {
|
140 | if (isArray(object)) {
|
141 | return object;
|
142 | }
|
143 | if (isPlainObject(object)) {
|
144 | return [];
|
145 | }
|
146 | return [ object ];
|
147 | };
|
148 |
|
149 | const toInt32 = (object) => {
|
150 | if (object === '-' || object === '') {
|
151 | throw new Error(`Value '${object}' is not a valid Int32 value`);
|
152 | }
|
153 | const number = toNumber(object);
|
154 | if (number >= BSON_INT32_MIN && number <= BSON_INT32_MAX) {
|
155 | return new Int32(number);
|
156 | }
|
157 | throw new Error(`Value ${number} is outside the valid Int32 range`);
|
158 | };
|
159 |
|
160 | const toInt64 = (object) => {
|
161 | if (object === '-' || object === '') {
|
162 | throw new Error(`Value '${object}' is not a valid Int64 value`);
|
163 | }
|
164 | const number = toNumber(object);
|
165 | if (number >= BSON_INT64_MIN && number <= BSON_INT64_MAX) {
|
166 | return Long.fromNumber(number);
|
167 | }
|
168 | throw new Error(`Value ${number} is outside the valid Int64 range`);
|
169 | };
|
170 |
|
171 | const toDouble = (object) => {
|
172 | if (object === '-' || object === '') {
|
173 | throw new Error(`Value '${object}' is not a valid Double value`);
|
174 | }
|
175 | if (isString(object) && object.endsWith('.')) {
|
176 | throw new Error('Please enter at least one digit after the decimal');
|
177 | }
|
178 | const number = toNumber(object);
|
179 | return new Double(number);
|
180 | };
|
181 |
|
182 | const toDecimal128 = (object) => {
|
183 | |
184 |
|
185 |
|
186 | if (has(object, BSON_TYPE) && includes(NUMBER_TYPES, object._bsontype)) {
|
187 | object = object._bsontype === LONG ? object.toNumber() : object.valueOf();
|
188 | }
|
189 | return Decimal128.fromString(String(object));
|
190 | };
|
191 |
|
192 | const toObjectID = (object) => {
|
193 | if (!isString(object) || object === '') {
|
194 | return new bson.ObjectID();
|
195 | }
|
196 | return bson.ObjectID.createFromHexString(object);
|
197 | };
|
198 |
|
199 | const toBinary = (object) => {
|
200 | return new Binary(String(object), Binary.SUBTYPE_DEFAULT);
|
201 | };
|
202 |
|
203 | const toRegex = (object) => {
|
204 | return new BSONRegExp(String(object));
|
205 | };
|
206 |
|
207 | const toCode = (object) => {
|
208 | return new Code(String(object), {});
|
209 | };
|
210 |
|
211 | const toSymbol = (object) => {
|
212 | return new Symbol(String(object));
|
213 | };
|
214 |
|
215 | const toTimestamp = (object) => {
|
216 | const number = toNumber(object);
|
217 | return Timestamp.fromNumber(number);
|
218 | };
|
219 |
|
220 |
|
221 |
|
222 |
|
223 | const CASTERS = {
|
224 | 'Array': toArray,
|
225 | 'Binary': toBinary,
|
226 | 'Boolean': toBoolean,
|
227 | 'Code': toCode,
|
228 | 'Date': toDate,
|
229 | 'Decimal128': toDecimal128,
|
230 | 'Double': toDouble,
|
231 | 'Int32': toInt32,
|
232 | 'Int64': toInt64,
|
233 | 'MaxKey': toMaxKey,
|
234 | 'MinKey': toMinKey,
|
235 | 'Null': toNull,
|
236 | 'Object': toObject,
|
237 | 'ObjectId': toObjectID,
|
238 | 'BSONRegexp': toRegex,
|
239 | 'String': toString,
|
240 | 'Symbol': toSymbol,
|
241 | 'Timestamp': toTimestamp,
|
242 | 'Undefined': toUndefined
|
243 | };
|
244 |
|
245 |
|
246 |
|
247 |
|
248 | const TYPES = keys(CASTERS);
|
249 |
|
250 |
|
251 |
|
252 |
|
253 | class Int32Check {
|
254 | test(string) {
|
255 | if (NUMBER_REGEX.test(string)) {
|
256 | var value = toNumber(string);
|
257 | return value >= BSON_INT32_MIN && value <= BSON_INT32_MAX;
|
258 | }
|
259 | return false;
|
260 | }
|
261 | }
|
262 |
|
263 |
|
264 |
|
265 |
|
266 | class Int64Check {
|
267 | test(string) {
|
268 | if (NUMBER_REGEX.test(string)) {
|
269 | return Number.isSafeInteger(toNumber(string));
|
270 | }
|
271 | return false;
|
272 | }
|
273 | }
|
274 |
|
275 | const INT32_CHECK = new Int32Check();
|
276 | const INT64_CHECK = new Int64Check();
|
277 |
|
278 |
|
279 |
|
280 |
|
281 |
|
282 |
|
283 |
|
284 |
|
285 | const numberToBsonType = (number) => {
|
286 | var string = toString(number);
|
287 | if (INT32_CHECK.test(string)) {
|
288 | return INT_32;
|
289 | } else if (INT64_CHECK.test(string)) {
|
290 | return INT_64;
|
291 | }
|
292 | return DOUBLE;
|
293 | };
|
294 |
|
295 |
|
296 |
|
297 |
|
298 | class TypeChecker {
|
299 |
|
300 | |
301 |
|
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 | cast(object, type) {
|
309 | var caster = CASTERS[type];
|
310 | var result = object;
|
311 | if (caster) {
|
312 | result = caster(object);
|
313 | }
|
314 | return result === OBJECT_TYPE ? EMPTY : result;
|
315 | }
|
316 |
|
317 | |
318 |
|
319 |
|
320 |
|
321 |
|
322 |
|
323 |
|
324 | type(object) {
|
325 | if (isNumber(object)) {
|
326 | return numberToBsonType(object);
|
327 | }
|
328 | if (isPlainObject(object)) {
|
329 | return OBJECT;
|
330 | }
|
331 | if (isArray(object)) {
|
332 | return ARRAY;
|
333 | }
|
334 | if (has(object, BSON_TYPE)) {
|
335 | if (object._bsontype === LONG) {
|
336 | return INT_64;
|
337 | }
|
338 | if (object._bsontype === OBJECT_ID) {
|
339 | return 'ObjectId';
|
340 | }
|
341 | return object._bsontype;
|
342 | }
|
343 | return Object.prototype.toString.call(object).replace(MATCH, '$1');
|
344 | }
|
345 |
|
346 | |
347 |
|
348 |
|
349 |
|
350 |
|
351 |
|
352 |
|
353 | castableTypes(highPrecisionSupport = false) {
|
354 | if (highPrecisionSupport === true) {
|
355 | return TYPES;
|
356 | }
|
357 | return without(TYPES, DECIMAL_128);
|
358 | }
|
359 | }
|
360 |
|
361 | module.exports = new TypeChecker();
|