Skip to content

Commit a153017

Browse files
authored
Migrate from jsonpath to jsonpath-plus (#4811)
1 parent 6fc2f78 commit a153017

6 files changed

Lines changed: 168 additions & 144 deletions

File tree

catalog/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ where verb is one of
1818

1919
## Changes
2020

21+
- [Changed] Replace `jsonpath` with `jsonpath-plus` for JSONPath evaluation ([#4811](https://github.com/quiltdata/quilt/pull/4811))
2122
- [Fixed] Fix "Error resolving revision" flash when navigating to a just-created package ([#4778](https://github.com/quiltdata/quilt/pull/4778))
2223
- [Changed] Qurator: Switch to Claude Sonnet 4.5, add `quratorDefaultModel` config field for per-stack model override ([#4764](https://github.com/quiltdata/quilt/pull/4764))
2324
- [Fixed] Fix crash when deleting a role on the admin page ([#4751](https://github.com/quiltdata/quilt/pull/4751))

catalog/app/components/FileEditor/QuiltConfigEditor/BucketPreferences/PackageDescription.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import cx from 'classnames'
2-
import jsonpath from 'jsonpath'
32
import React from 'react'
43
import * as M from '@material-ui/core'
54
import * as Lab from '@material-ui/lab'
65

76
import type { PackagePreferencesInput } from 'utils/BucketPreferences/BucketPreferences'
7+
import * as jsonpath from 'utils/jsonpath'
88

99
import type { KeyedValue } from './State'
1010

catalog/app/utils/jsonpath.spec.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { describe, expect, it } from 'vitest'
2+
3+
import { parse } from './jsonpath'
4+
5+
describe('utils/jsonpath', () => {
6+
describe('parse', () => {
7+
it('should accept dot-notation child access', () => {
8+
expect(() => parse('$.store.book')).not.toThrow()
9+
})
10+
11+
it('should accept wildcard subscript', () => {
12+
expect(() => parse('$.store.book[*].author')).not.toThrow()
13+
})
14+
15+
it('should accept recursive descent', () => {
16+
expect(() => parse('$..author')).not.toThrow()
17+
})
18+
19+
it('should accept wildcard member', () => {
20+
expect(() => parse('$.store.*')).not.toThrow()
21+
})
22+
23+
it('should accept recursive descent with member', () => {
24+
expect(() => parse('$.store..price')).not.toThrow()
25+
})
26+
27+
it('should accept numeric array index', () => {
28+
expect(() => parse('$..book[2]')).not.toThrow()
29+
})
30+
31+
it('should accept script subscript expression', () => {
32+
expect(() => parse('$..book[(@.length-1)]')).not.toThrow()
33+
})
34+
35+
it('should accept negative slice', () => {
36+
expect(() => parse('$..book[-1:]')).not.toThrow()
37+
})
38+
39+
it('should accept union of indices', () => {
40+
expect(() => parse('$..book[0,1]')).not.toThrow()
41+
})
42+
43+
it('should accept array slice', () => {
44+
expect(() => parse('$..book[:2]')).not.toThrow()
45+
})
46+
47+
it('should accept filter expression with existence check', () => {
48+
expect(() => parse('$..book[?(@.isbn)]')).not.toThrow()
49+
})
50+
51+
it('should accept filter expression with comparison', () => {
52+
expect(() => parse('$..book[?(@.price<10)]')).not.toThrow()
53+
})
54+
55+
it('should accept recursive wildcard', () => {
56+
expect(() => parse('$..*')).not.toThrow()
57+
})
58+
59+
it('should reject plain text', () => {
60+
expect(() => parse('not a path')).toThrow()
61+
})
62+
63+
it('should reject empty string', () => {
64+
expect(() => parse('')).toThrow()
65+
})
66+
67+
// Goessner spec does not allow `}` in expressions, but jsonpath-plus
68+
// silently accepts malformed bracket contents via toPathArray.
69+
it('should not reject malformed brackets (spec deviation)', () => {
70+
expect(() => parse('$[}}}')).not.toThrow()
71+
})
72+
73+
it('should reject expression missing root', () => {
74+
expect(() => parse('[invalid')).toThrow()
75+
})
76+
})
77+
})

catalog/app/utils/jsonpath.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { JSONPath } from 'jsonpath-plus'
2+
3+
// Thin wrapper around jsonpath-plus.
4+
//
5+
// jsonpath-plus is more permissive than the Goessner spec
6+
// (https://goessner.net/articles/JsonPath/), so we add guards
7+
// to reject expressions that the spec considers invalid.
8+
9+
export function parse(expr: string) {
10+
if (!expr) throw new SyntaxError(`Invalid JSONPath: ${expr}`)
11+
const parts = JSONPath.toPathArray(expr)
12+
if (parts[0] !== '$') throw new SyntaxError(`Invalid JSONPath: ${expr}`)
13+
return parts
14+
}

0 commit comments

Comments
 (0)