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

Commit 3133844

Browse files
Merge pull request #225 from bugsnag/typed-map-refactor
refactor(typedMap): Extract and rename typedMap function into NativeSerializer module
2 parents d4eccdd + 7357b01 commit 3133844

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)