Skip to content

Commit

Permalink
fix(mutator): match deep set/setIfMissing behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
rexxars committed Oct 23, 2023
1 parent 25c7210 commit 2ed595c
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 5 deletions.
20 changes: 19 additions & 1 deletion packages/@sanity/mutator/src/patch/Patcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {Matcher} from '../jsonpath'
import {parsePatch} from './parse'
import {ImmutableAccessor} from './ImmutableAccessor'
import {PatchTypes, SingleDocumentPatch} from './types'
import {SetPatch} from './SetPatch'
import {SetIfMissingPatch} from './SetIfMissingPatch'

export interface Patch {
id: string
Expand Down Expand Up @@ -57,6 +59,9 @@ export class Patcher {
// a patch to be the payload. When matchers report a delivery, the
// apply(targets, accessor) is called on the patch
function process(matcher: Matcher, accessor: ImmutableAccessor) {
const isSetPatch =
matcher.payload instanceof SetPatch || matcher.payload instanceof SetIfMissingPatch

let result = accessor
// Every time we execute the matcher a new set of leads is generated. Each lead
// is a target (being an index, an attribute name or a range) in the form of an
Expand All @@ -75,7 +80,20 @@ function process(matcher: Matcher, accessor: ImmutableAccessor) {
result = result.setIndexAccessor(i, process(lead.matcher, item))
})
} else if (lead.target.isAttributeReference()) {
const oldValueAccessor = result.getAttribute(lead.target.name())
// `set`/`setIfMissing` on a primitive value overwrites it
if (isSetPatch && result.containerType() === 'primitive') {
result = result.set({})
}

let oldValueAccessor = result.getAttribute(lead.target.name())

// If the patch is a set/setIfMissing patch, we allow deeply setting properties,
// creating missing segments as we go.
if (!oldValueAccessor && isSetPatch) {
result = result.setAttribute(lead.target.name(), {})
oldValueAccessor = result.getAttribute(lead.target.name())
}

if (!oldValueAccessor) {
// Don't follow lead, no such attribute
return
Expand Down
9 changes: 7 additions & 2 deletions packages/@sanity/mutator/src/patch/SetIfMissingPatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ export class SetIfMissingPatch {
let result = accessor
targets.forEach((target) => {
if (target.isIndexReference()) {
// setIfMissing do not apply to arrays, since Gradient will reject nulls in arrays
// setIfMissing do not apply to arrays, since Content Lake will reject nulls in arrays
} else if (target.isAttributeReference()) {
if (!result.hasAttribute(target.name())) {
// setting a subproperty on a primitive value overwrites it, eg
// `{setIfMissing: {'address.street': 'California St'}}` on `{address: 'Fiction St'}` will
// result in `{address: {street: 'California St'}}`
if (result.containerType() === 'primitive') {
result = result.set({[target.name()]: this.value})
} else if (!result.hasAttribute(target.name())) {
result = accessor.setAttribute(target.name(), this.value)
}
} else {
Expand Down
9 changes: 8 additions & 1 deletion packages/@sanity/mutator/src/patch/SetPatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,14 @@ export class SetPatch {
result = result.setIndex(i, this.value)
})
} else if (target.isAttributeReference()) {
result = result.setAttribute(target.name(), this.value)
// setting a subproperty on a primitive value overwrites it, eg
// `{set: {'address.street': 'California St'}}` on `{address: 'Fiction St'}` will result in
// `{address: {street: 'California St'}}`
if (result.containerType() === 'primitive') {
result = result.set({[target.name()]: this.value})
} else {
result = result.setAttribute(target.name(), this.value)
}
} else {
throw new Error(`Unable to apply to target ${target.toString()}`)
}
Expand Down
72 changes: 72 additions & 0 deletions packages/@sanity/mutator/test/patchExamples/set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,78 @@ const examples: PatchExample[] = [
a: 'hello',
},
},
{
name: 'Set new deep key',
before: {},
patch: {
id: 'a',
set: {
'a.b.c': 'hello',
},
},
after: {
a: {
b: {
c: 'hello',
},
},
},
},
{
name: 'Set deep key on previous string value',
before: {
a: 'stringValue',
},
patch: {
id: 'a',
set: {
'a.b.c': 'hello',
},
},
after: {
a: {
b: {
c: 'hello',
},
},
},
},
{
name: 'Set deep key on previous number value',
before: {
a: 123,
},
patch: {
id: 'a',
set: {
'a.b.c': 'hello',
},
},
after: {
a: {
b: {
c: 'hello',
},
},
},
},
{
name: 'Set key on previous number value',
before: {
a: 123,
},
patch: {
id: 'a',
set: {
'a.b': 'hello',
},
},
after: {
a: {
b: 'hello',
},
},
},
{
name: 'Set range',
before: {
Expand Down
75 changes: 74 additions & 1 deletion packages/@sanity/mutator/test/patchExamples/setIfMissing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,80 @@ const examples: PatchExample[] = [
{b: 7, p: 'Thorvald Meyers gt.', zz: {yyy: 55, zzz: 10}},
],
},
}, // Potentially redundant, added to exactly match a test case from @sanity/form-builder that was failing.
},
{
name: 'Set new deep key',
before: {},
patch: {
id: 'a',
setIfMissing: {
'a.b.c': 'hello',
},
},
after: {
a: {
b: {
c: 'hello',
},
},
},
},
{
name: 'Set deep key on previous string value',
before: {
a: 'stringValue',
},
patch: {
id: 'a',
setIfMissing: {
'a.b.c': 'hello',
},
},
after: {
a: {
b: {
c: 'hello',
},
},
},
},
{
name: 'Set deep key on previous number value',
before: {
a: 123,
},
patch: {
id: 'a',
setIfMissing: {
'a.b.c': 'hello',
},
},
after: {
a: {
b: {
c: 'hello',
},
},
},
},
{
name: 'Set key on previous number value',
before: {
a: 123,
},
patch: {
id: 'a',
setIfMissing: {
'a.b': 'hello',
},
},
after: {
a: {
b: 'hello',
},
},
},
// Potentially redundant, added to exactly match a test case from @sanity/form-builder that was failing.
{
name: 'Set if missing by key',
before: {
Expand Down

0 comments on commit 2ed595c

Please sign in to comment.