Skip to content

Commit

Permalink
Correctly Count Object References (#10903)
Browse files Browse the repository at this point in the history
* Correctly Count Object References

* Make error message better

* add more tests
  • Loading branch information
Timer committed Mar 9, 2020
1 parent befb500 commit 1e53a49
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 40 deletions.
29 changes: 18 additions & 11 deletions packages/next/lib/is-serializable-props.ts
Expand Up @@ -23,22 +23,26 @@ export function isSerializableProps(
)
}

const visited = new WeakSet()

function visit(value: any, path: string) {
function visit(visited: Map<any, string>, value: any, path: string) {
if (visited.has(value)) {
throw new SerializableError(
page,
method,
path,
'Circular references cannot be expressed in JSON.'
`Circular references cannot be expressed in JSON (references: \`${visited.get(
value
) || '(self)'}\`).`
)
}

visited.add(value)
visited.set(value, path)
}

function isSerializable(value: any, path: string): true {
function isSerializable(
refs: Map<any, string>,
value: any,
path: string
): true {
const type = typeof value
if (
// `null` can be serialized, but not `undefined`.
Expand All @@ -65,16 +69,18 @@ export function isSerializableProps(
}

if (isPlainObject(value)) {
visit(value, path)
visit(refs, value, path)

if (
Object.entries(value).every(([key, value]) => {
const nextPath = regexpPlainIdentifier.test(key)
? `${path}.${key}`
: `${path}[${JSON.stringify(key)}]`

const newRefs = new Map(refs)
return (
isSerializable(key, nextPath) && isSerializable(value, nextPath)
isSerializable(newRefs, key, nextPath) &&
isSerializable(newRefs, value, nextPath)
)
})
) {
Expand All @@ -90,11 +96,12 @@ export function isSerializableProps(
}

if (Array.isArray(value)) {
visit(value, path)
visit(refs, value, path)

const newRefs = new Map(refs)
if (
value.every((value, index) =>
isSerializable(value, `${path}[${index}]`)
isSerializable(newRefs, value, `${path}[${index}]`)
)
) {
return true
Expand Down Expand Up @@ -124,7 +131,7 @@ export function isSerializableProps(
)
}

return isSerializable(input, '')
return isSerializable(new Map(), input, '')
}

export class SerializableError extends Error {
Expand Down
96 changes: 92 additions & 4 deletions test/unit/is-serializable-props.test.js
Expand Up @@ -161,13 +161,13 @@ Reason: \`function\` cannot be serialized as JSON. Please only return JSON seria
expect(() => isSerializableProps('/', 'test', obj))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.child\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON."
Reason: Circular references cannot be expressed in JSON (references: \`(self)\`)."
`)

expect(() => isSerializableProps('/', 'test', { k: [obj] }))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.k[0].child\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON."
Reason: Circular references cannot be expressed in JSON (references: \`.k[0]\`)."
`)
})

Expand All @@ -178,13 +178,101 @@ Reason: Circular references cannot be expressed in JSON."
expect(() => isSerializableProps('/', 'test', { arr }))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.arr[2]\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON."
Reason: Circular references cannot be expressed in JSON (references: \`.arr\`)."
`)

expect(() => isSerializableProps('/', 'test', { k: [{ arr }] }))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.k[0].arr[2]\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON."
Reason: Circular references cannot be expressed in JSON (references: \`.k[0].arr\`)."
`)
})

it('can handle deep obj circular refs', () => {
const obj = { foo: 'bar', test: true, leve1: { level2: {} } }
obj.leve1.level2.child = obj

expect(() => isSerializableProps('/', 'test', obj))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.leve1.level2.child\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON (references: \`(self)\`)."
`)
})

it('can handle deep obj circular refs (with arrays)', () => {
const obj = { foo: 'bar', test: true, leve1: { level2: {} } }
obj.leve1.level2.child = [{ another: [obj] }]

expect(() => isSerializableProps('/', 'test', obj))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.leve1.level2.child[0].another[0]\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON (references: \`(self)\`)."
`)
})

it('can handle deep arr circular refs', () => {
const arr = [1, 2, []]
arr[3] = [false, [null, 0, arr]]

expect(() => isSerializableProps('/', 'test', { k: arr }))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.k[3][1][2]\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON (references: \`.k\`)."
`)
})

it('can handle deep arr circular refs (with objects)', () => {
const arr = [1, 2, []]
arr[3] = [false, { nested: [null, 0, arr] }]

expect(() => isSerializableProps('/', 'test', { k: arr }))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.k[3][1].nested[2]\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON (references: \`.k\`)."
`)
})

it('allows multi object refs', () => {
const obj = { foo: 'bar', test: true }
expect(
isSerializableProps('/', 'test', {
obj1: obj,
obj2: obj,
})
).toBe(true)
})

it('allows multi object refs nested', () => {
const obj = { foo: 'bar', test: true }
expect(
isSerializableProps('/', 'test', {
obj1: obj,
obj2: obj,
anArray: [obj],
aKey: { obj },
})
).toBe(true)
})

it('allows multi array refs', () => {
const arr = [{ foo: 'bar' }, true]
expect(
isSerializableProps('/', 'test', {
arr1: arr,
arr2: arr,
})
).toBe(true)
})

it('allows multi array refs nested', () => {
const arr = [{ foo: 'bar' }, true]
expect(
isSerializableProps('/', 'test', {
arr1: arr,
arr2: arr,
arr3: [arr],
arr4: [1, [2, 3, arr]],
})
).toBe(true)
})
})
27 changes: 2 additions & 25 deletions yarn.lock
Expand Up @@ -4145,7 +4145,7 @@ browserify-zlib@^0.2.0:
dependencies:
pako "~1.0.5"

browserslist@4.8.3, browserslist@^4.0.0, browserslist@^4.3.6, browserslist@^4.6.0, browserslist@^4.6.4, browserslist@^4.8.0, browserslist@^4.8.2, browserslist@^4.8.3:
browserslist@4.8.3, browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6, browserslist@^4.0.0, browserslist@^4.3.6, browserslist@^4.6.0, browserslist@^4.6.4, browserslist@^4.8.0, browserslist@^4.8.2, browserslist@^4.8.3:
version "4.8.3"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.8.3.tgz#65802fcd77177c878e015f0e3189f2c4f627ba44"
integrity sha512-iU43cMMknxG1ClEZ2MDKeonKE1CCrFVkQK2AqO2YWFmvIrx4JWrvQ4w4hQez6EpVI8rHTtqh/ruHHDHSOKxvUg==
Expand All @@ -4154,14 +4154,6 @@ browserslist@4.8.3, browserslist@^4.0.0, browserslist@^4.3.6, browserslist@^4.6.
electron-to-chromium "^1.3.322"
node-releases "^1.1.44"

browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6:
version "1.7.7"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9"
integrity sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=
dependencies:
caniuse-db "^1.0.30000639"
electron-to-chromium "^1.2.7"

browserstack-local@1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/browserstack-local/-/browserstack-local-1.4.0.tgz#d979cac056f57b9af159b3bcd7fdc09b4354537c"
Expand Down Expand Up @@ -4465,21 +4457,11 @@ caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634:
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30001023.tgz#f856f71af16a5a44e81f1fcefc1673912a43da72"
integrity sha512-EnlshvE6oAum+wWwKmJNVaoqJMjIc0bLUy4Dj77VVnz1o6bzSPr1Ze9iPy6g5ycg1xD6jGU6vBmo7pLEz2MbCQ==

caniuse-db@^1.0.30000639:
version "1.0.30001033"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30001033.tgz#383288df833c85d83c2bfc3469245ec1fa1f881e"
integrity sha512-2ZReq+OHqHhsIQSiv8OVNhQ6Ht9eYJpwblZydHV8nI44Od6J5YUl3J9Wxvjry/v969jCHH5fR9+C6FwJ41XbOQ==

caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001012, caniuse-lite@^1.0.30001017, caniuse-lite@^1.0.30001019:
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001012, caniuse-lite@^1.0.30001017, caniuse-lite@^1.0.30001019, caniuse-lite@^1.0.30001020:
version "1.0.30001019"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001019.tgz#857e3fccaad2b2feb3f1f6d8a8f62d747ea648e1"
integrity sha512-6ljkLtF1KM5fQ+5ZN0wuyVvvebJxgJPTmScOMaFuQN2QuOzvRJnWSKfzQskQU5IOU4Gap3zasYPIinzwUjoj/g==

caniuse-lite@^1.0.30001020:
version "1.0.30001033"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001033.tgz#60c328fb56860de60f9a2cb419c31fb80587cba0"
integrity sha512-8Ibzxee6ibc5q88cM1usPsMpJOG5CTq0s/dKOmlekPbDGKt+UrnOOTPSjQz3kVo6yL7N4SB5xd+FGLHQmbzh6A==

capitalize@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/capitalize/-/capitalize-1.0.0.tgz#dc802c580aee101929020d2ca14b4ca8a0ae44be"
Expand Down Expand Up @@ -6271,11 +6253,6 @@ ejs@^2.6.1:
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba"
integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==

electron-to-chromium@^1.2.7:
version "1.3.372"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.372.tgz#fb61b6dfe06f3278a384d084ebef75d463ec7580"
integrity sha512-77a4jYC52OdisHM+Tne7dgWEvQT1FoNu/jYl279pP88ZtG4ZRIPyhQwAKxj6C2rzsyC1OwsOds9JlZtNncSz6g==

electron-to-chromium@^1.3.322:
version "1.3.327"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.327.tgz#516f28b4271727004362b4ac814494ae64d9dde7"
Expand Down

0 comments on commit 1e53a49

Please sign in to comment.