Skip to content
This repository was archived by the owner on Sep 14, 2020. It is now read-only.

Commit 7357b01

Browse files
committed
refactor(typedMap): Extract and rename typedMap function into NativeSerializer module
Rather than exposing typedMap on the Bugsnag module in order to be able to test it, it made sense for it to be in its own file. This refactor pulls it out, gives it a sensible name and then tests it in isolation.
1 parent d4eccdd commit 7357b01

4 files changed

Lines changed: 177 additions & 175 deletions

File tree

src/Bugsnag.js

Lines changed: 3 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* global ErrorUtils, __DEV__ */
22

33
import { NativeModules } from 'react-native'
4+
import serializeForNativeLayer from './NativeSerializer'
45

56
const NativeClient = NativeModules.BugsnagReactNative
67

@@ -159,7 +160,7 @@ export class Client {
159160
NativeClient.leaveBreadcrumb({
160161
name,
161162
type,
162-
metadata: typedMap(breadcrumbMetaData)
163+
metadata: serializeForNativeLayer(breadcrumbMetaData)
163164
})
164165
}
165166

@@ -353,7 +354,7 @@ export class Report {
353354
errorClass: this.errorClass,
354355
errorMessage: this.errorMessage,
355356
groupingHash: this.groupingHash,
356-
metadata: typedMap(this.metadata),
357+
metadata: serializeForNativeLayer(this.metadata),
357358
severity: this.severity,
358359
stacktrace: this.stacktrace,
359360
user: this.user,
@@ -363,42 +364,3 @@ export class Report {
363364
}
364365
}
365366
}
366-
367-
const allowedMapObjectTypes = ['string', 'number', 'boolean']
368-
369-
/**
370-
* Convert an object into a structure with types suitable for serializing
371-
* across to native code.
372-
*/
373-
const typedMap = function (map, maxDepth = 10, depth = 0, seen = new Set()) {
374-
seen.add(map)
375-
const output = {}
376-
for (const key in map) {
377-
if (!{}.hasOwnProperty.call(map, key)) continue
378-
379-
const value = map[key]
380-
381-
// Checks for `null`, NaN, and `undefined`.
382-
if ([ undefined, null ].includes(value) || (typeof value === 'number' && isNaN(value))) {
383-
output[key] = {type: 'string', value: String(value)}
384-
} else if (typeof value === 'object') {
385-
if (seen.has(value)) {
386-
output[key] = {type: 'string', value: '[circular]'}
387-
} else if (depth === maxDepth) {
388-
output[key] = {type: 'string', value: '[max depth exceeded]'}
389-
} else {
390-
output[key] = {type: 'map', value: typedMap(value, maxDepth, depth + 1, seen)}
391-
}
392-
} else {
393-
const type = typeof value
394-
if (allowedMapObjectTypes.includes(type)) {
395-
output[key] = {type: type, value: value}
396-
} else {
397-
console.warn(`Could not serialize breadcrumb data for '${key}': Invalid type '${type}'`)
398-
}
399-
}
400-
}
401-
return output
402-
}
403-
404-
export { typedMap }

src/NativeSerializer.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const allowedMapObjectTypes = [ 'string', 'number', 'boolean' ]
2+
3+
/**
4+
* Convert an object into a structure with types suitable for serializing
5+
* across to native code.
6+
*/
7+
const serializeForNativeLayer = (map, maxDepth = 10, depth = 0, seen = new Set()) => {
8+
seen.add(map)
9+
const output = {}
10+
for (const key in map) {
11+
if (!{}.hasOwnProperty.call(map, key)) continue
12+
13+
const value = map[key]
14+
15+
// Checks for `null`, NaN, and `undefined`.
16+
if ([ undefined, null ].includes(value) || (typeof value === 'number' && isNaN(value))) {
17+
output[key] = { type: 'string', value: String(value) }
18+
} else if (typeof value === 'object') {
19+
if (seen.has(value)) {
20+
output[key] = { type: 'string', value: '[circular]' }
21+
} else if (depth === maxDepth) {
22+
output[key] = { type: 'string', value: '[max depth exceeded]' }
23+
} else {
24+
output[key] = { type: 'map', value: serializeForNativeLayer(value, maxDepth, depth + 1, seen) }
25+
}
26+
} else {
27+
const type = typeof value
28+
if (allowedMapObjectTypes.includes(type)) {
29+
output[key] = { type: type, value: value }
30+
} else {
31+
console.warn(`Could not serialize breadcrumb data for '${key}': Invalid type '${type}'`)
32+
}
33+
}
34+
}
35+
return output
36+
}
37+
38+
export default serializeForNativeLayer

src/__tests__/Bugsnag.test.js

Lines changed: 0 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -358,137 +358,3 @@ test('{enable|disable}ConsoleBreadCrumbs(): gracefully handles serialization edg
358358
// switched off for the next test ¯\_(ツ)_/¯
359359
c.disableConsoleBreadCrumbs()
360360
})
361-
362-
test('typedMap converts arrays, objects, strings and numbers', () => {
363-
const { typedMap } = require('../Bugsnag')
364-
expect(typedMap({
365-
arr: [ 0, 1, 2 ],
366-
obj: {
367-
a: 'a',
368-
b: 'b',
369-
c: 'c'
370-
},
371-
x: 10,
372-
y: 'str'
373-
})).toEqual({
374-
arr: {
375-
type: 'map',
376-
value: {
377-
'0': { type: 'number', value: 0 },
378-
'1': { type: 'number', value: 1 },
379-
'2': { type: 'number', value: 2 }
380-
}
381-
},
382-
obj: {
383-
type: 'map',
384-
value: {
385-
a: { type: 'string', value: 'a' },
386-
b: { type: 'string', value: 'b' },
387-
c: { type: 'string', value: 'c' }
388-
}
389-
},
390-
x: { type: 'number', value: 10 },
391-
y: { type: 'string', value: 'str' }
392-
})
393-
})
394-
395-
test('typedMap handles circular structures', () => {
396-
const { typedMap } = require('../Bugsnag')
397-
const objA = {}
398-
objA.ref = objA
399-
400-
// this tests the top level object being referred to as a node
401-
expect(typedMap(objA)).toEqual({ ref: { type: 'string', value: '[circular]' } })
402-
403-
const objB = {}
404-
objB.leafA = {}
405-
objB.leafB = {}
406-
objB.leafA.ref = objB.leafB
407-
objB.leafB.ref = objB.leafA
408-
409-
// this tests subtrees referring to eachother
410-
expect(typedMap(objB)).toEqual({
411-
leafA: {
412-
type: 'map',
413-
value: {
414-
ref: {
415-
type: 'map',
416-
value: {
417-
ref: { type: 'string', value: '[circular]' }
418-
}
419-
}
420-
}
421-
},
422-
leafB: { type: 'string', value: '[circular]' }
423-
})
424-
})
425-
426-
test('typedMap handles deep nesting', () => {
427-
const { typedMap } = require('../Bugsnag')
428-
const nesty = {
429-
zero: {
430-
one: {
431-
two: {
432-
three: {
433-
four: {
434-
five: { six: { seven: { eight: { nine: { ten: { eleven: 'TOO_DEEP!' } } } } } }
435-
}
436-
}
437-
}
438-
}
439-
}
440-
}
441-
expect(typedMap(nesty)).toEqual({
442-
zero: {
443-
type: 'map',
444-
value: {
445-
one: {
446-
type: 'map',
447-
value: {
448-
two: {
449-
type: 'map',
450-
value: {
451-
three: {
452-
type: 'map',
453-
value: {
454-
four: {
455-
type: 'map',
456-
value: {
457-
five: {
458-
type: 'map',
459-
value: {
460-
six: {
461-
type: 'map',
462-
value: {
463-
seven: {
464-
type: 'map',
465-
value: {
466-
eight: {
467-
type: 'map',
468-
value: {
469-
nine: {
470-
type: 'map',
471-
value: {
472-
ten: { type: 'string', value: '[max depth exceeded]' }
473-
}
474-
}
475-
}
476-
}
477-
}
478-
}
479-
}
480-
}
481-
}
482-
}
483-
}
484-
}
485-
}
486-
}
487-
}
488-
}
489-
}
490-
}
491-
}
492-
}
493-
})
494-
})
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/* global test, expect */
2+
3+
import serializeForNativeLayer from '../NativeSerializer'
4+
5+
test('serializeForNativeLayer converts arrays, objects, strings, numbers and booleans', () => {
6+
expect(serializeForNativeLayer({
7+
arr: [ 0, 1, 2 ],
8+
obj: {
9+
a: 'a',
10+
b: 'b',
11+
c: 'c'
12+
},
13+
x: 10,
14+
y: 'str',
15+
yes: false
16+
})).toEqual({
17+
arr: {
18+
type: 'map',
19+
value: {
20+
'0': { type: 'number', value: 0 },
21+
'1': { type: 'number', value: 1 },
22+
'2': { type: 'number', value: 2 }
23+
}
24+
},
25+
obj: {
26+
type: 'map',
27+
value: {
28+
a: { type: 'string', value: 'a' },
29+
b: { type: 'string', value: 'b' },
30+
c: { type: 'string', value: 'c' }
31+
}
32+
},
33+
x: { type: 'number', value: 10 },
34+
y: { type: 'string', value: 'str' },
35+
yes: { type: 'boolean', value: false }
36+
})
37+
})
38+
39+
test('serializeForNativeLayer handles circular structures', () => {
40+
const objA = {}
41+
objA.ref = objA
42+
43+
// this tests the top level object being referred to as a node
44+
expect(serializeForNativeLayer(objA)).toEqual({ ref: { type: 'string', value: '[circular]' } })
45+
46+
const objB = {}
47+
objB.leafA = {}
48+
objB.leafB = {}
49+
objB.leafA.ref = objB.leafB
50+
objB.leafB.ref = objB.leafA
51+
52+
// this tests subtrees referring to eachother
53+
expect(serializeForNativeLayer(objB)).toEqual({
54+
leafA: {
55+
type: 'map',
56+
value: {
57+
ref: {
58+
type: 'map',
59+
value: {
60+
ref: { type: 'string', value: '[circular]' }
61+
}
62+
}
63+
}
64+
},
65+
leafB: { type: 'string', value: '[circular]' }
66+
})
67+
})
68+
69+
test('serializeForNativeLayer handles deep nesting', () => {
70+
const nesty = {
71+
zero: {
72+
one: {
73+
two: {
74+
three: {
75+
four: {
76+
five: { six: { seven: { eight: { nine: { ten: { eleven: 'TOO_DEEP!' } } } } } }
77+
}
78+
}
79+
}
80+
}
81+
}
82+
}
83+
expect(serializeForNativeLayer(nesty)).toEqual({
84+
zero: {
85+
type: 'map',
86+
value: {
87+
one: {
88+
type: 'map',
89+
value: {
90+
two: {
91+
type: 'map',
92+
value: {
93+
three: {
94+
type: 'map',
95+
value: {
96+
four: {
97+
type: 'map',
98+
value: {
99+
five: {
100+
type: 'map',
101+
value: {
102+
six: {
103+
type: 'map',
104+
value: {
105+
seven: {
106+
type: 'map',
107+
value: {
108+
eight: {
109+
type: 'map',
110+
value: {
111+
nine: {
112+
type: 'map',
113+
value: {
114+
ten: { type: 'string', value: '[max depth exceeded]' }
115+
}
116+
}
117+
}
118+
}
119+
}
120+
}
121+
}
122+
}
123+
}
124+
}
125+
}
126+
}
127+
}
128+
}
129+
}
130+
}
131+
}
132+
}
133+
}
134+
}
135+
})
136+
})

0 commit comments

Comments
 (0)