Skip to content

Commit

Permalink
Merge pull request #377 from contentful/fix/nested-link-resolution
Browse files Browse the repository at this point in the history
fix: don't resolve links inside JSON fields [ZEND-5720]
  • Loading branch information
t-col authored Dec 6, 2024
2 parents 6b5d776 + 7efafd2 commit bc6c0ce
Show file tree
Hide file tree
Showing 12 changed files with 4,575 additions and 9,520 deletions.
17 changes: 2 additions & 15 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ orbs:
executors:
node:
docker:
- image: cimg/node:16.13.2
- image: cimg/node:lts
jobs:
test:
executor: node
Expand All @@ -26,20 +26,9 @@ jobs:
- run:
name: Lint project
command: npm run lint
lint-commits:
docker:
- image: cimg/node:16.13.2
steps:
- checkout
- run:
name: npm ci
command: npm ci
- run:
name: Linting commits
command: npm run commitlint-circle
semantic-release:
docker:
- image: cimg/node:16.13.2
- image: cimg/node:lts
steps:
- checkout
- vault/get-secrets: # Loads vault secrets
Expand All @@ -55,10 +44,8 @@ workflows:
jobs:
- lint
- test
- lint-commits
- semantic-release:
context: vault
requires:
- lint
- test
- lint-commits
2 changes: 0 additions & 2 deletions .eslintignore

This file was deleted.

4 changes: 0 additions & 4 deletions .eslintrc.js

This file was deleted.

8 changes: 8 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import js from '@eslint/js'
import mochaPlugin from 'eslint-plugin-mocha'

export default [
{ ignores: ['node_modules', 'dist'], files: ['index.js', 'test/**/*.js'] },
js.configs.recommended,
mochaPlugin.configs.flat.recommended,
]
108 changes: 77 additions & 31 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import copy from 'fast-copy'

const UNRESOLVED_LINK = {} // unique object to avoid polyfill bloat using Symbol()
const UNRESOLVED_LINK = {} // Unique object to avoid polyfill bloat using Symbol()

/**
* isLink Function
* IsLink Function
* Checks if the object has sys.type "Link"
* @param object
*/
const isLink = (object) => object && object.sys && object.sys.type === 'Link'

/**
* isResourceLink Function
* IsResourceLink Function
* Checks if the object has sys.type "ResourceLink"
* @param object
*/
Expand Down Expand Up @@ -63,12 +63,13 @@ const getIdsFromUrn = (urn) => {
return undefined
}

// eslint-disable-next-line no-unused-vars
const [_, spaceId, environmentId = 'master', entryId] = urn.match(regExp)
return { spaceId, environmentId, entryId }
}

/**
* getResolvedLink Function
* GetResolvedLink Function
*
* @param entityMap
* @param link
Expand Down Expand Up @@ -100,12 +101,12 @@ const getResolvedLink = (entityMap, link) => {
}

/**
* cleanUpLinks Function
* CleanUpUnresolvedLinks Function
* - Removes unresolvable links from Arrays and Objects
*
* @param {Object[]|Object} input
*/
const cleanUpLinks = (input) => {
const cleanUpUnresolvedLinks = (input) => {
if (Array.isArray(input)) {
return input.filter((val) => val !== UNRESOLVED_LINK)
}
Expand All @@ -117,8 +118,34 @@ const cleanUpLinks = (input) => {
return input
}

const normalizeLink = (entityMap, link, removeUnresolved) => {
const resolvedLink = getResolvedLink(entityMap, link)
if (resolvedLink === UNRESOLVED_LINK) {
return removeUnresolved ? resolvedLink : link
}
return resolvedLink
}

const maybeNormalizeLink = (entityMap, maybeLink, removeUnresolved) => {
if (Array.isArray(maybeLink)) {
return maybeLink.reduce((acc, link) => {
const normalizedLink = maybeNormalizeLink(entityMap, link, removeUnresolved)
if (removeUnresolved && normalizedLink === UNRESOLVED_LINK) {
return acc
}
acc.push(normalizedLink)
return acc
}, [])
} else if (typeof maybeLink === 'object') {
if (isLink(maybeLink) || isResourceLink(maybeLink)) {
return normalizeLink(entityMap, maybeLink, removeUnresolved)
}
}
return maybeLink
}

/**
* walkMutate Function
* WalkMutate Function
* @param input
* @param predicate
* @param mutator
Expand All @@ -138,20 +165,12 @@ const walkMutate = (input, predicate, mutator, removeUnresolved) => {
}
}
if (removeUnresolved) {
input = cleanUpLinks(input)
input = cleanUpUnresolvedLinks(input)
}
}
return input
}

const normalizeLink = (entityMap, link, removeUnresolved) => {
const resolvedLink = getResolvedLink(entityMap, link)
if (resolvedLink === UNRESOLVED_LINK) {
return removeUnresolved ? resolvedLink : link
}
return resolvedLink
}

const makeEntryObject = (item, itemEntryPoints) => {
if (!Array.isArray(itemEntryPoints)) {
return item
Expand All @@ -166,7 +185,30 @@ const makeEntryObject = (item, itemEntryPoints) => {
}

/**
* resolveResponse Function
* Only normalize the top level properties of the entrypoint (e.g. item.fields),
* as JSON fields can contain values that are objects that look like links, but are not.
*/
const normalizeFromEntryPoint = (entityMap, entryPoint, removeUnresolved) => {
if (!entryPoint) {
return undefined
}

if (Array.isArray(entryPoint)) {
return maybeNormalizeLink(entityMap, entryPoint, removeUnresolved)
} else if (typeof entryPoint === 'object') {
return Object.entries(entryPoint).reduce((acc, [key, val]) => {
const normalizedLink = maybeNormalizeLink(entityMap, val, removeUnresolved)
if (removeUnresolved && normalizedLink === UNRESOLVED_LINK) {
return acc
}
acc[key] = normalizedLink
return acc
}, {})
}
}

/**
* ResolveResponse Function
* Resolves contentful response to normalized form.
* @param {Object} response Contentful response
* @param {{removeUnresolved: Boolean, itemEntryPoints: Array<String>}|{}} options
Expand All @@ -175,7 +217,7 @@ const makeEntryObject = (item, itemEntryPoints) => {
* @return {Object}
*/
const resolveResponse = (response, options) => {
options = options || {}
options ||= {}
if (!response.items) {
return []
}
Expand All @@ -184,9 +226,7 @@ const resolveResponse = (response, options) => {
(all, type) => [...all, ...response.includes[type]],
[],
)

const allEntries = [...responseClone.items, ...allIncludes].filter((entity) => Boolean(entity.sys))

const entityMap = new Map(
allEntries.reduce((acc, entity) => {
const entries = makeEntityMapKeys(entity.sys).map((key) => [key, entity])
Expand All @@ -196,17 +236,23 @@ const resolveResponse = (response, options) => {
)

allEntries.forEach((item) => {
const entryObject = makeEntryObject(item, options.itemEntryPoints)

Object.assign(
item,
walkMutate(
entryObject,
(x) => isLink(x) || isResourceLink(x),
(link) => normalizeLink(entityMap, link, options.removeUnresolved),
options.removeUnresolved,
),
)
if (options.itemEntryPoints && options.itemEntryPoints.length) {
for (const entryPoint of options.itemEntryPoints) {
item[entryPoint] = normalizeFromEntryPoint(entityMap, item[entryPoint], options.removeUnresolved)
}
} else {
const entryObject = makeEntryObject(item, options.itemEntryPoints)

Object.assign(
item,
walkMutate(
entryObject,
(x) => isLink(x) || isResourceLink(x),
(link) => normalizeLink(entityMap, link, options.removeUnresolved),
options.removeUnresolved,
),
)
}
})

return responseClone.items
Expand Down
Loading

0 comments on commit bc6c0ce

Please sign in to comment.