UNPKG

7.56 kBJavaScriptView Raw
1'use strict';
2
3const isPlainObject = require('lodash.isplainobject');
4const isArray = require('lodash.isarray');
5const isString = require('lodash.isstring');
6const isNumber = require('lodash.isnumber');
7const has = require('lodash.has');
8const keys = require('lodash.keys');
9const without = require('lodash.without');
10const toNumber = require('lodash.tonumber');
11const toString = require('lodash.tostring');
12const includes = require('lodash.includes');
13const bson = require('bson');
14const MinKey = bson.MinKey;
15const MaxKey = bson.MaxKey;
16const Long = bson.Long;
17const Double = bson.Double;
18const Int32 = bson.Int32;
19const Decimal128 = bson.Decimal128;
20const Binary = bson.Binary;
21const BSONRegExp = bson.BSONRegExp;
22const Code = bson.Code;
23const Symbol = bson.Symbol;
24const Timestamp = bson.Timestamp;
25
26/**
27 * The object string.
28 */
29const OBJECT = 'Object';
30
31/**
32 * The array type string.
33 */
34const ARRAY = 'Array';
35
36/**
37 * True constant.
38 */
39const TRUE = 'true';
40
41/**
42 * False constant.
43 */
44const FALSE = 'false';
45
46/**
47 * Long constant.
48 */
49const LONG = 'Long';
50
51const INT_32 = 'Int32';
52const INT_64 = 'Int64';
53const DOUBLE = 'Double';
54const DECIMAL_128 = 'Decimal128';
55const OBJECT_TYPE = '[object Object]';
56const EMPTY = '';
57const OBJECT_ID = 'ObjectID';
58
59/**
60 * The bson type field.
61 */
62const BSON_TYPE = '_bsontype';
63
64/**
65 * The match regex.
66 */
67const MATCH = /\[object (\w+)\]/;
68
69/**
70 * The max int 32 value.
71 */
72const BSON_INT32_MAX = 0x7FFFFFFF;
73
74/**
75 * The min int 32 value.
76 */
77const BSON_INT32_MIN = -0x80000000;
78
79const BSON_INT64_MAX = Math.pow(2, 63) - 1;
80const BSON_INT64_MIN = -(BSON_INT64_MAX);
81
82/**
83 * The number regex.
84 */
85const NUMBER_REGEX = /^-?\d+$/;
86
87/**
88 * All bson types that are numbers.
89 */
90const NUMBER_TYPES = [
91 'Long',
92 'Int32',
93 'Double',
94 'Decimal128'
95];
96
97const toDate = (object) => {
98 return new Date(object);
99};
100
101const toMinKey = () => {
102 return new MinKey();
103};
104
105const toMaxKey = () => {
106 return new MaxKey();
107};
108
109const toUndefined = () => {
110 return undefined;
111};
112
113const toNull = () => {
114 return null;
115};
116
117const 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
132const toObject = (object) => {
133 if (isPlainObject(object)) {
134 return object;
135 }
136 return {};
137};
138
139const toArray = (object) => {
140 if (isArray(object)) {
141 return object;
142 }
143 if (isPlainObject(object)) {
144 return [];
145 }
146 return [ object ];
147};
148
149const 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
160const 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
171const 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
182const toDecimal128 = (object) => {
183 /*
184 If converting a BSON Object, extract the value before converting to a string.
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
192const toObjectID = (object) => {
193 if (!isString(object) || object === '') {
194 return new bson.ObjectID();
195 }
196 return bson.ObjectID.createFromHexString(object);
197};
198
199const toBinary = (object) => {
200 return new Binary(String(object), Binary.SUBTYPE_DEFAULT);
201};
202
203const toRegex = (object) => {
204 return new BSONRegExp(String(object));
205};
206
207const toCode = (object) => {
208 return new Code(String(object), {});
209};
210
211const toSymbol = (object) => {
212 return new Symbol(String(object));
213};
214
215const toTimestamp = (object) => {
216 const number = toNumber(object);
217 return Timestamp.fromNumber(number);
218};
219
220/**
221 * The functions to cast to a type.
222 */
223const 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 * An array of all bson types.
247 */
248const TYPES = keys(CASTERS);
249
250/**
251 * Checks if a string is an int32.
252 */
253class 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 * Checks if a string is an int64.
265 */
266class Int64Check {
267 test(string) {
268 if (NUMBER_REGEX.test(string)) {
269 return Number.isSafeInteger(toNumber(string));
270 }
271 return false;
272 }
273}
274
275const INT32_CHECK = new Int32Check();
276const INT64_CHECK = new Int64Check();
277
278/**
279 * Gets the BSON type for a JS number.
280 *
281 * @param {Number} number - The number.
282 *
283 * @returns {String} The BSON type.
284 */
285const 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 * Checks the types of objects and returns them as readable strings.
297 */
298class TypeChecker {
299
300 /**
301 * Cast the provided object to the desired type.
302 *
303 * @param {Object} object - The object to cast.
304 * @param {String} type - The type.
305 *
306 * @returns {Object} The cast object.
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 * Get the type for the object.
319 *
320 * @param {Object} object - The object.
321 *
322 * @returns {String} The object type.
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 * Get a list of types the object can be cast to.
348 *
349 * @param {Boolean} highPrecisionSupport - If Decimal128 is supported or not.
350 *
351 * @returns {Array} The available types.
352 */
353 castableTypes(highPrecisionSupport = false) {
354 if (highPrecisionSupport === true) {
355 return TYPES;
356 }
357 return without(TYPES, DECIMAL_128);
358 }
359}
360
361module.exports = new TypeChecker();