Skip to content

Commit 561ee14

Browse files
authoredMar 18, 2024
fix(util): safely stringify path segments named as GROQ data types (e.g. null) (#5986)
### Description Fields named as GROQ data types (`true`, `false`, and `null`) cannot be accessed using dot notation. These fields must instead be serialized using square bracket notation. Segments names `true`, `false`, or `null` will now be serialized using square bracket notation. All other segments will continue to use dot notation. #### Invalid ```groq parent.true ``` #### Valid ```groq parent["true"] ``` You can observe this by executing a GROQ query such as: ```groq [{ "true": "positive" }, { "false": "negative" }, { "null": "nothing" }].null // attribute expected ``` Versus: ```groq [{ "true": "positive" }, { "false": "negative" }, { "null": "nothing" }]["null"] // [ // null, // null, // "nothing" // ] ``` ### What to review The paths created by the `toString` function are correct. ### Testing This branch adds tests to the `packages/@sanity/util/test/PathUtils.test.ts` test suite.
1 parent 871b450 commit 561ee14

File tree

2 files changed

+26
-5
lines changed

2 files changed

+26
-5
lines changed
 

‎packages/@sanity/util/src/paths.ts

+17-5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ const EMPTY_PATH: Path = []
1515

1616
export const FOCUS_TERMINATOR = '$'
1717

18+
// Fields named as GROQ data types cannot be accessed using dot notation. These fields must instead
19+
// be serialized using square bracket notation.
20+
const GROQ_DATA_TYPE_VALUES = ['true', 'false', 'null']
21+
1822
export function get<R>(obj: unknown, path: Path | string): R | undefined
1923
export function get<R>(obj: unknown, path: Path | string, defaultValue: R): R
2024
export function get(obj: unknown, path: Path | string, defaultVal?: unknown): unknown {
@@ -164,14 +168,22 @@ export function toString(path: Path): string {
164168
}
165169

166170
return path.reduce<string>((target, segment, i) => {
167-
const segmentType = typeof segment
168-
if (segmentType === 'number') {
171+
const isHead = i === 0
172+
173+
if (typeof segment === 'number') {
169174
return `${target}[${segment}]`
170175
}
171176

172-
if (segmentType === 'string') {
173-
const separator = i === 0 ? '' : '.'
174-
return `${target}${separator}${segment}`
177+
if (typeof segment === 'string') {
178+
if (isHead) {
179+
return segment
180+
}
181+
182+
if (GROQ_DATA_TYPE_VALUES.includes(segment)) {
183+
return `${target}["${segment}"]`
184+
}
185+
186+
return `${target}.${segment}`
175187
}
176188

177189
if (isKeySegment(segment) && segment._key) {

‎packages/@sanity/util/test/PathUtils.test.ts

+9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {expect, test} from '@jest/globals'
2+
23
/* eslint-disable max-nested-callbacks, @typescript-eslint/ban-ts-comment */
34
import {fromString, get, resolveKeyedPath, toString} from '../src/paths'
45

@@ -104,6 +105,14 @@ test('toString: handles deep prop segments', () => {
104105
expect(toString(['bar', 'foo', 'baz'])).toEqual('bar.foo.baz')
105106
})
106107

108+
test('toString: handles deep prop segments named as GROQ data types', () => {
109+
expect(toString(['foo', 'true'])).toEqual('foo["true"]')
110+
expect(toString(['bar', 'false'])).toEqual('bar["false"]')
111+
expect(toString(['bat', 'null'])).toEqual('bat["null"]')
112+
expect(toString(['true', 'true'])).toEqual('true["true"]')
113+
expect(toString(['true', 'false', 'null'])).toEqual('true["false"]["null"]')
114+
})
115+
107116
test('toString: handles deep array index segments', () => {
108117
expect(toString(['foo', 13])).toEqual('foo[13]')
109118
expect(toString(['bar', 'foo', 3])).toEqual('bar.foo[3]')

0 commit comments

Comments
 (0)
Please sign in to comment.