Skip to content

Commit

Permalink
fix(gatsby): Sanitize length on objects (#34253)
Browse files Browse the repository at this point in the history
Co-authored-by: LekoArts <[email protected]>
Fix #26565
  • Loading branch information
iChenLei authored Nov 24, 2022
1 parent c935f05 commit 0889d31
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 26 deletions.
2 changes: 1 addition & 1 deletion packages/gatsby/src/redux/actions/public.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const {
const { splitComponentPath } = require(`gatsby-core-utils/parse-component-path`)
const { hasNodeChanged } = require(`../../utils/nodes`)
const { getNode, getDataStore } = require(`../../datastore`)
import sanitizeNode from "../../utils/sanitize-node"
import { sanitizeNode } from "../../utils/sanitize-node"
const { store } = require(`../index`)
const { validateComponent } = require(`../../utils/validate-component`)
import { nodeSchema } from "../../joi-schemas/joi"
Expand Down
22 changes: 21 additions & 1 deletion packages/gatsby/src/utils/__tests__/sanitize-node.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import sanitizeNode from "../sanitize-node"
import { sanitizeNode } from "../sanitize-node"

describe(`node sanitization`, () => {
let testNode
Expand Down Expand Up @@ -90,4 +90,24 @@ describe(`node sanitization`, () => {
// should be same instance
expect(result).toBe(testNodeWithoutUnserializableData)
})

it(`keeps length field but not OOM`, () => {
const testNodeWithLength = {
id: `id2`,
parent: ``,
children: [],
length: 81185414,
foo: `bar`,
internal: {
type: `Test`,
contentDigest: `digest1`,
owner: `test`,
counter: 0,
},
fields: [],
}
const result = sanitizeNode(testNodeWithLength)
// @ts-ignore - Just for tests
expect(result.length).toBeDefined()
})
})
71 changes: 47 additions & 24 deletions packages/gatsby/src/utils/sanitize-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import _ from "lodash"
import type { IGatsbyNode } from "../redux/types"
import type { GatsbyIterable } from "../datastore/common/iterable"

type data = IGatsbyNode | GatsbyIterable<IGatsbyNode>
type Data = IGatsbyNode | GatsbyIterable<IGatsbyNode>

type OmitUndefined = (data: Data) => Partial<Data>

/**
* @param {Object|Array} data
* @returns {Object|Array} data without undefined values
*/
type omitUndefined = (data: data) => Partial<data>

const omitUndefined: omitUndefined = data => {
const omitUndefined: OmitUndefined = data => {
const isPlainObject = _.isPlainObject(data)
if (isPlainObject) {
return _.pickBy(data, p => p !== undefined)
Expand All @@ -20,12 +20,12 @@ const omitUndefined: omitUndefined = data => {
return (data as GatsbyIterable<IGatsbyNode>).filter(p => p !== undefined)
}

type isTypeSupported = (data: Data) => boolean

/**
* @param {*} data
* @return {boolean}
* @return {boolean} Boolean if type is supported
*/
type isTypeSupported = (data: data) => boolean

const isTypeSupported: isTypeSupported = data => {
if (data === null) {
return true
Expand All @@ -41,42 +41,67 @@ const isTypeSupported: isTypeSupported = data => {
return isSupported
}

type sanitizeNode = (
data: Data,
isNode?: boolean,
path?: Set<unknown>
) => Data | undefined

/**
* Make data serializable
* @param {(Object|Array)} data to sanitize
* @param {boolean} isNode = true
* @param {Set<string>} path = new Set
*/

type sanitizeNode = (
data: data,
isNode?: boolean,
path?: Set<unknown>
) => data | undefined

const sanitizeNode: sanitizeNode = (data, isNode = true, path = new Set()) => {
export const sanitizeNode: sanitizeNode = (
data,
isNode = true,
path = new Set()
) => {
const isPlainObject = _.isPlainObject(data)
const isArray = _.isArray(data)

if (isPlainObject || _.isArray(data)) {
if (isPlainObject || isArray) {
if (path.has(data)) return data
path.add(data)

const returnData = isPlainObject ? {} : []
const returnData = isPlainObject
? ({} as IGatsbyNode)
: ([] as Array<IGatsbyNode>)
let anyFieldChanged = false
_.each(data, (o, key) => {

// _.each is a "Collection" method and thus objects with "length" property are iterated as arrays
const hasLengthProperty = isPlainObject
? Object.prototype.hasOwnProperty.call(data, `length`)
: false
let lengthProperty
if (hasLengthProperty) {
lengthProperty = (data as IGatsbyNode).length
delete (data as IGatsbyNode).length
}

_.each(data, (value, key) => {
if (isNode && key === `internal`) {
returnData[key] = o
returnData[key] = value
return
}
returnData[key] = sanitizeNode(o as data, false, path)
returnData[key] = sanitizeNode(value as Data, false, path)

if (returnData[key] !== o) {
if (returnData[key] !== value) {
anyFieldChanged = true
}
})

if (hasLengthProperty) {
;(data as IGatsbyNode).length = lengthProperty
returnData.length = sanitizeNode(lengthProperty as Data, false, path)
if (returnData.length !== lengthProperty) {
anyFieldChanged = true
}
}

if (anyFieldChanged) {
data = omitUndefined(returnData as data) as data
data = omitUndefined(returnData as Data) as Data
}

// arrays and plain objects are supported - no need to to sanitize
Expand All @@ -89,5 +114,3 @@ const sanitizeNode: sanitizeNode = (data, isNode = true, path = new Set()) => {
return data
}
}

export default sanitizeNode

0 comments on commit 0889d31

Please sign in to comment.