UNPKG

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