1/** 2 * Copyright (c) 650 Industries. 3 * Copyright (c) Meta Platforms, Inc. and affiliates. 4 * 5 * This source code is licensed under the MIT license found in the 6 * LICENSE file in the root directory of this source tree. 7 */ 8/** 9 * Tries to stringify with JSON.stringify and toString, but catches exceptions 10 * (e.g. from circular objects) and always returns a string and never throws. 11 */ 12export function createStringifySafeWithLimits(limits) { 13 const { maxDepth = Number.POSITIVE_INFINITY, maxStringLimit = Number.POSITIVE_INFINITY, maxArrayLimit = Number.POSITIVE_INFINITY, maxObjectKeysLimit = Number.POSITIVE_INFINITY, } = limits; 14 const stack = []; 15 function replacer(_key, value) { 16 while (stack.length && this !== stack[0]) { 17 stack.shift(); 18 } 19 if (typeof value === 'string') { 20 const truncatedString = '...(truncated)...'; 21 if (value.length > maxStringLimit + truncatedString.length) { 22 return value.substring(0, maxStringLimit) + truncatedString; 23 } 24 return value; 25 } 26 if (typeof value !== 'object' || value === null) { 27 return value; 28 } 29 let retval = value; 30 if (Array.isArray(value)) { 31 if (stack.length >= maxDepth) { 32 retval = `[ ... array with ${value.length} values ... ]`; 33 } 34 else if (value.length > maxArrayLimit) { 35 retval = value 36 .slice(0, maxArrayLimit) 37 .concat([`... extra ${value.length - maxArrayLimit} values truncated ...`]); 38 } 39 } 40 else { 41 // Add refinement after Array.isArray call. 42 if (typeof value !== 'object') { 43 throw new Error('This was already found earlier'); 44 } 45 const keys = Object.keys(value); 46 if (stack.length >= maxDepth) { 47 retval = `{ ... object with ${keys.length} keys ... }`; 48 } 49 else if (keys.length > maxObjectKeysLimit) { 50 // Return a sample of the keys. 51 retval = {}; 52 for (const k of keys.slice(0, maxObjectKeysLimit)) { 53 retval[k] = value[k]; 54 } 55 const truncatedKey = '...(truncated keys)...'; 56 retval[truncatedKey] = keys.length - maxObjectKeysLimit; 57 } 58 } 59 stack.unshift(retval); 60 return retval; 61 } 62 return function stringifySafe(arg) { 63 if (arg === undefined) { 64 return 'undefined'; 65 } 66 else if (arg === null) { 67 return 'null'; 68 } 69 else if (typeof arg === 'function') { 70 try { 71 return arg.toString(); 72 } 73 catch { 74 return '[function unknown]'; 75 } 76 } 77 else if (arg instanceof Error) { 78 return arg.name + ': ' + arg.message; 79 } 80 else { 81 // Perform a try catch, just in case the object has a circular 82 // reference or stringify throws for some other reason. 83 try { 84 const ret = JSON.stringify(arg, replacer); 85 if (ret === undefined) { 86 return '["' + typeof arg + '" failed to stringify]'; 87 } 88 return ret; 89 } 90 catch { 91 if (typeof arg.toString === 'function') { 92 try { 93 // $FlowFixMe[incompatible-use] : toString shouldn't take any arguments in general. 94 return arg.toString(); 95 } 96 catch { } 97 } 98 } 99 } 100 return '["' + typeof arg + '" failed to stringify]'; 101 }; 102} 103const stringifySafe = createStringifySafeWithLimits({ 104 maxDepth: 10, 105 maxStringLimit: 100, 106 maxArrayLimit: 50, 107 maxObjectKeysLimit: 50, 108}); 109export default stringifySafe; 110//# sourceMappingURL=index.js.map