From cd53b0ae061730fc5b77de5bc7816f6a237c8ff0 Mon Sep 17 00:00:00 2001 From: andresceballosm Date: Tue, 30 Jul 2024 15:34:23 -0500 Subject: [PATCH 1/6] Added JSONPath syntactic validation --- packages/taco/package.json | 4 +- packages/taco/src/conditions/base/json-api.ts | 29 +++++++--- .../test/conditions/base/json-api.test.ts | 56 +++++++++++++++++++ 3 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 packages/taco/test/conditions/base/json-api.test.ts diff --git a/packages/taco/package.json b/packages/taco/package.json index 88bf9143e..2048bf56a 100644 --- a/packages/taco/package.json +++ b/packages/taco/package.json @@ -44,11 +44,13 @@ "@nucypher/taco-auth": "workspace:*", "ethers": "*", "semver": "^7.6.3", + "jsonpath": "^1.1.1", "zod": "*" }, "devDependencies": { "@nucypher/test-utils": "workspace:*", - "@types/semver": "^7.5.8" + "@types/semver": "^7.5.8", + "@types/jsonpath": "^0.2.4" }, "engines": { "node": ">=18", diff --git a/packages/taco/src/conditions/base/json-api.ts b/packages/taco/src/conditions/base/json-api.ts index 0a55b20b8..f8835913e 100644 --- a/packages/taco/src/conditions/base/json-api.ts +++ b/packages/taco/src/conditions/base/json-api.ts @@ -1,18 +1,31 @@ -import { z } from 'zod'; +import { parse } from "jsonpath"; +import { z } from "zod"; -import { Condition } from '../condition'; -import { - OmitConditionType, - returnValueTestSchema, -} from '../shared'; +import { Condition } from "../condition"; +import { OmitConditionType, returnValueTestSchema } from "../shared"; -export const JsonApiConditionType = 'json-api'; +export const JsonApiConditionType = "json-api"; + +const validateJSONPath = (jsonPath: string): boolean => { + try { + parse(jsonPath); + return true; + } catch (error) { + return false; + } +}; + +export const jsonPathSchema = z + .string() + .refine((val) => validateJSONPath(val), { + message: "Invalid JSONPath expression", + }); export const JsonApiConditionSchema = z.object({ conditionType: z.literal(JsonApiConditionType).default(JsonApiConditionType), endpoint: z.string().url(), parameters: z.record(z.string(), z.unknown()).optional(), - query: z.string().optional(), + query: jsonPathSchema.optional(), returnValueTest: returnValueTestSchema, // Update to allow multiple return values after expanding supported methods }); diff --git a/packages/taco/test/conditions/base/json-api.test.ts b/packages/taco/test/conditions/base/json-api.test.ts new file mode 100644 index 000000000..94b07af81 --- /dev/null +++ b/packages/taco/test/conditions/base/json-api.test.ts @@ -0,0 +1,56 @@ +import { describe, expect, it } from 'vitest'; + +import { jsonPathSchema } from '../../../src/conditions/base/json-api'; + +describe('JSONPath Validation', () => { + it('Invalid JSONPath: Incomplete filter expression', () => { + const invalidPath = '$.store.book[?(@.price < ]'; + const result = jsonPathSchema.safeParse(invalidPath); + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.errors[0].message).toBe( + 'Invalid JSONPath expression', + ); + } + }); + + it('Invalid JSONPath: Incorrect use of brackets', () => { + const invalidPath = '$[store][book]'; + const result = jsonPathSchema.safeParse(invalidPath); + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.errors[0].message).toBe( + 'Invalid JSONPath expression', + ); + } + }); + + it('Invalid JSONPath: Unclosed wildcard asterisk', () => { + const invalidPath = '$.store.book[*'; + const result = jsonPathSchema.safeParse(invalidPath); + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.errors[0].message).toBe( + 'Invalid JSONPath expression', + ); + } + }); + + it('Valid JSONPath expression', () => { + const validPath = '$.store.book[?(@.price < 10)]'; + const result = jsonPathSchema.safeParse(validPath); + expect(result.success).toBe(true); + }); + + it('Valid JSONPath with correct quotes', () => { + const validPath = "$.store['book[?(@.price < ]']"; + const result = jsonPathSchema.safeParse(validPath); + expect(result.success).toBe(true); + }); + + it('Valid JSONPath with correct wildcard', () => { + const validPath = '$.store.book[*]'; + const result = jsonPathSchema.safeParse(validPath); + expect(result.success).toBe(true); + }); +}); From eacd6f8fcaf8081242f7b7faa91f776ecbd71d8f Mon Sep 17 00:00:00 2001 From: andresceballosm Date: Wed, 31 Jul 2024 10:07:44 -0500 Subject: [PATCH 2/6] Updated pnpm-lock.yamls and fix linter errors --- packages/taco/src/conditions/base/json-api.ts | 12 ++++++------ .../taco/test/conditions/base/json.test.ts | 19 +++++++++++-------- .../test/conditions/condition-expr.test.ts | 11 +++++++---- packages/taco/test/test-utils.ts | 9 +++------ pnpm-lock.yaml | 11 +++++++++++ 5 files changed, 38 insertions(+), 24 deletions(-) diff --git a/packages/taco/src/conditions/base/json-api.ts b/packages/taco/src/conditions/base/json-api.ts index f8835913e..7e96cd648 100644 --- a/packages/taco/src/conditions/base/json-api.ts +++ b/packages/taco/src/conditions/base/json-api.ts @@ -1,10 +1,10 @@ -import { parse } from "jsonpath"; -import { z } from "zod"; +import { parse } from 'jsonpath'; +import { z } from 'zod'; -import { Condition } from "../condition"; -import { OmitConditionType, returnValueTestSchema } from "../shared"; +import { Condition } from '../condition'; +import { OmitConditionType, returnValueTestSchema } from '../shared'; -export const JsonApiConditionType = "json-api"; +export const JsonApiConditionType = 'json-api'; const validateJSONPath = (jsonPath: string): boolean => { try { @@ -18,7 +18,7 @@ const validateJSONPath = (jsonPath: string): boolean => { export const jsonPathSchema = z .string() .refine((val) => validateJSONPath(val), { - message: "Invalid JSONPath expression", + message: 'Invalid JSONPath expression', }); export const JsonApiConditionSchema = z.object({ diff --git a/packages/taco/test/conditions/base/json.test.ts b/packages/taco/test/conditions/base/json.test.ts index 9c26a7f7a..0166485a2 100644 --- a/packages/taco/test/conditions/base/json.test.ts +++ b/packages/taco/test/conditions/base/json.test.ts @@ -25,7 +25,10 @@ describe('JsonApiCondition', () => { endpoint: 'not-a-url', }; - const result = JsonApiCondition.validate(JsonApiConditionSchema, badJsonApiObj); + const result = JsonApiCondition.validate( + JsonApiConditionSchema, + badJsonApiObj, + ); expect(result.error).toBeDefined(); expect(result.data).toBeUndefined(); @@ -35,26 +38,26 @@ describe('JsonApiCondition', () => { }, }); }); - + describe('parameters', () => { it('accepts conditions without query path', () => { - const { query, ...noQueryObj} = testJsonApiConditionObj; + const { query, ...noQueryObj } = testJsonApiConditionObj; const result = JsonApiCondition.validate( JsonApiConditionSchema, - noQueryObj + noQueryObj, ); - + expect(result.error).toBeUndefined(); expect(result.data).toEqual(noQueryObj); }); it('accepts conditions without parameters', () => { - const { query, ...noParamsObj} = testJsonApiConditionObj; + const { query, ...noParamsObj } = testJsonApiConditionObj; const result = JsonApiCondition.validate( JsonApiConditionSchema, - noParamsObj + noParamsObj, ); - + expect(result.error).toBeUndefined(); expect(result.data).toEqual(noParamsObj); }); diff --git a/packages/taco/test/conditions/condition-expr.test.ts b/packages/taco/test/conditions/condition-expr.test.ts index 4defa46cf..66bf48299 100644 --- a/packages/taco/test/conditions/condition-expr.test.ts +++ b/packages/taco/test/conditions/condition-expr.test.ts @@ -406,17 +406,20 @@ describe('condition set', () => { it('json api condition serialization', () => { const conditionExpr = new ConditionExpression(jsonApiCondition); - + const conditionExprJson = conditionExpr.toJson(); expect(conditionExprJson).toBeDefined(); expect(conditionExprJson).toContain('endpoint'); - expect(conditionExprJson).toContain('https://_this_would_totally_fail.com'); + expect(conditionExprJson).toContain( + 'https://_this_would_totally_fail.com', + ); expect(conditionExprJson).toContain('parameters'); expect(conditionExprJson).toContain('query'); expect(conditionExprJson).toContain('$.ethereum.usd'); expect(conditionExprJson).toContain('returnValueTest'); - - const conditionExprFromJson = ConditionExpression.fromJSON(conditionExprJson); + + const conditionExprFromJson = + ConditionExpression.fromJSON(conditionExprJson); expect(conditionExprFromJson).toBeDefined(); expect(conditionExprFromJson.condition).toBeInstanceOf(JsonApiCondition); }); diff --git a/packages/taco/test/test-utils.ts b/packages/taco/test/test-utils.ts index 60ebab597..546b7c697 100644 --- a/packages/taco/test/test-utils.ts +++ b/packages/taco/test/test-utils.ts @@ -39,10 +39,7 @@ import { ContractConditionType, FunctionAbiProps, } from '../src/conditions/base/contract'; -import { - JsonApiConditionProps, - JsonApiConditionType -} from '../src/conditions/base/json-api'; +import { JsonApiConditionType } from '../src/conditions/base/json-api'; import { RpcConditionProps, RpcConditionType, @@ -231,8 +228,8 @@ export const testJsonApiConditionObj = { conditionType: JsonApiConditionType, endpoint: 'https://_this_would_totally_fail.com', parameters: { - 'ids': 'ethereum', - 'vs_currencies': 'usd', + ids: 'ethereum', + vs_currencies: 'usd', }, query: '$.ethereum.usd', returnValueTest: testReturnValueTest, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 307ac8f3c..b9c5373c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -573,6 +573,9 @@ importers: ethers: specifier: '*' version: 5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) + jsonpath: + specifier: ^1.1.1 + version: 1.1.1 semver: specifier: ^7.6.3 version: 7.6.3 @@ -583,6 +586,9 @@ importers: '@nucypher/test-utils': specifier: workspace:* version: link:../test-utils + '@types/jsonpath': + specifier: ^0.2.4 + version: 0.2.4 '@types/semver': specifier: ^7.5.8 version: 7.5.8 @@ -2604,6 +2610,9 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/jsonpath@0.2.4': + resolution: {integrity: sha512-K3hxB8Blw0qgW6ExKgMbXQv2UPZBoE2GqLpVY+yr7nMD2Pq86lsuIzyAaiQ7eMqFL5B6di6pxSkogLJEyEHoGA==} + '@types/keyv@3.1.4': resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} @@ -11558,6 +11567,8 @@ snapshots: '@types/json5@0.0.29': {} + '@types/jsonpath@0.2.4': {} + '@types/keyv@3.1.4': dependencies: '@types/node': 20.16.3 From 7490f385bcb67e3f4079f5dbaf15ad895d306032 Mon Sep 17 00:00:00 2001 From: andresceballosm Date: Wed, 31 Jul 2024 14:33:57 -0500 Subject: [PATCH 3/6] Removed jsonpath library --- packages/taco/package.json | 2 - packages/taco/src/conditions/base/json-api.ts | 69 ++++++++++++++++--- pnpm-lock.yaml | 11 --- 3 files changed, 61 insertions(+), 21 deletions(-) diff --git a/packages/taco/package.json b/packages/taco/package.json index 2048bf56a..f41506c8a 100644 --- a/packages/taco/package.json +++ b/packages/taco/package.json @@ -44,13 +44,11 @@ "@nucypher/taco-auth": "workspace:*", "ethers": "*", "semver": "^7.6.3", - "jsonpath": "^1.1.1", "zod": "*" }, "devDependencies": { "@nucypher/test-utils": "workspace:*", "@types/semver": "^7.5.8", - "@types/jsonpath": "^0.2.4" }, "engines": { "node": ">=18", diff --git a/packages/taco/src/conditions/base/json-api.ts b/packages/taco/src/conditions/base/json-api.ts index 7e96cd648..01ac73e77 100644 --- a/packages/taco/src/conditions/base/json-api.ts +++ b/packages/taco/src/conditions/base/json-api.ts @@ -1,4 +1,3 @@ -import { parse } from 'jsonpath'; import { z } from 'zod'; import { Condition } from '../condition'; @@ -6,14 +5,68 @@ import { OmitConditionType, returnValueTestSchema } from '../shared'; export const JsonApiConditionType = 'json-api'; -const validateJSONPath = (jsonPath: string): boolean => { - try { - parse(jsonPath); - return true; - } catch (error) { - return false; +function tokenize(expression: string): string[] { + const regex = + /(\$|@|\.\.|\.|[[\]]|\?|\(|\)|==|!=|<=|>=|<|>|&&|\|\||[a-zA-Z_][\w]*|\d+|'[^']*')/g; + return expression.match(regex) || []; +} + +function validateJSONPath(expression: string): boolean { + const tokens = tokenize(expression); + + let depth = 0; + let inBracket = false; + let inFilter = false; + let lastTokenWasCloseBracket = false; + + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + + if (token === '$' && i !== 0) return false; // $ only at the beginning + if (token === '@' && !inFilter) return false; // @ only in filters + + if (token === '[') { + if (lastTokenWasCloseBracket) return false; // Don't allow [...][] + depth++; + inBracket = true; + lastTokenWasCloseBracket = false; + } else if (token === ']') { + if (depth === 0) return false; + depth--; + inBracket = false; + lastTokenWasCloseBracket = true; + } else { + lastTokenWasCloseBracket = false; + } + + if (token === '?') { + if (!inBracket) return false; + inFilter = true; + } else if (token === '(') { + if (!inFilter) return false; + } else if (token === ')') { + if (!inFilter) return false; + inFilter = false; + } + + // Check for valid operators in filters + if ( + inFilter && + ['==', '!=', '<', '<=', '>', '>=', '&&', '||'].includes(token) + ) { + if (i === 0 || i === tokens.length - 1) return false; + } + + // Check that there are no two consecutive dots outside brackets + if (token === '.' && i > 0 && tokens[i - 1] === '.' && !inBracket) + return false; } -}; + + if (depth !== 0) return false; // Unclosed brackets + if (inFilter) return false; // Unclosed filter + + return true; +} export const jsonPathSchema = z .string() diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9c5373c0..307ac8f3c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -573,9 +573,6 @@ importers: ethers: specifier: '*' version: 5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) - jsonpath: - specifier: ^1.1.1 - version: 1.1.1 semver: specifier: ^7.6.3 version: 7.6.3 @@ -586,9 +583,6 @@ importers: '@nucypher/test-utils': specifier: workspace:* version: link:../test-utils - '@types/jsonpath': - specifier: ^0.2.4 - version: 0.2.4 '@types/semver': specifier: ^7.5.8 version: 7.5.8 @@ -2610,9 +2604,6 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/jsonpath@0.2.4': - resolution: {integrity: sha512-K3hxB8Blw0qgW6ExKgMbXQv2UPZBoE2GqLpVY+yr7nMD2Pq86lsuIzyAaiQ7eMqFL5B6di6pxSkogLJEyEHoGA==} - '@types/keyv@3.1.4': resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} @@ -11567,8 +11558,6 @@ snapshots: '@types/json5@0.0.29': {} - '@types/jsonpath@0.2.4': {} - '@types/keyv@3.1.4': dependencies: '@types/node': 20.16.3 From 18d0add4ba1d9a641a9ab77a6a09b1ac934619b3 Mon Sep 17 00:00:00 2001 From: andresceballosm Date: Thu, 1 Aug 2024 10:47:16 -0500 Subject: [PATCH 4/6] Added Library @astronautlabs/jsonpath --- packages/taco/package.json | 1 + packages/taco/src/conditions/base/json-api.ts | 69 +++---------------- pnpm-lock.yaml | 10 +++ 3 files changed, 19 insertions(+), 61 deletions(-) diff --git a/packages/taco/package.json b/packages/taco/package.json index f41506c8a..f9a68b827 100644 --- a/packages/taco/package.json +++ b/packages/taco/package.json @@ -39,6 +39,7 @@ "typedoc": "typedoc" }, "dependencies": { + "@astronautlabs/jsonpath": "^1.1.2", "@nucypher/nucypher-core": "*", "@nucypher/shared": "workspace:*", "@nucypher/taco-auth": "workspace:*", diff --git a/packages/taco/src/conditions/base/json-api.ts b/packages/taco/src/conditions/base/json-api.ts index 01ac73e77..22032cb34 100644 --- a/packages/taco/src/conditions/base/json-api.ts +++ b/packages/taco/src/conditions/base/json-api.ts @@ -1,3 +1,4 @@ +import { JSONPath } from '@astronautlabs/jsonpath'; import { z } from 'zod'; import { Condition } from '../condition'; @@ -5,68 +6,14 @@ import { OmitConditionType, returnValueTestSchema } from '../shared'; export const JsonApiConditionType = 'json-api'; -function tokenize(expression: string): string[] { - const regex = - /(\$|@|\.\.|\.|[[\]]|\?|\(|\)|==|!=|<=|>=|<|>|&&|\|\||[a-zA-Z_][\w]*|\d+|'[^']*')/g; - return expression.match(regex) || []; -} - -function validateJSONPath(expression: string): boolean { - const tokens = tokenize(expression); - - let depth = 0; - let inBracket = false; - let inFilter = false; - let lastTokenWasCloseBracket = false; - - for (let i = 0; i < tokens.length; i++) { - const token = tokens[i]; - - if (token === '$' && i !== 0) return false; // $ only at the beginning - if (token === '@' && !inFilter) return false; // @ only in filters - - if (token === '[') { - if (lastTokenWasCloseBracket) return false; // Don't allow [...][] - depth++; - inBracket = true; - lastTokenWasCloseBracket = false; - } else if (token === ']') { - if (depth === 0) return false; - depth--; - inBracket = false; - lastTokenWasCloseBracket = true; - } else { - lastTokenWasCloseBracket = false; - } - - if (token === '?') { - if (!inBracket) return false; - inFilter = true; - } else if (token === '(') { - if (!inFilter) return false; - } else if (token === ')') { - if (!inFilter) return false; - inFilter = false; - } - - // Check for valid operators in filters - if ( - inFilter && - ['==', '!=', '<', '<=', '>', '>=', '&&', '||'].includes(token) - ) { - if (i === 0 || i === tokens.length - 1) return false; - } - - // Check that there are no two consecutive dots outside brackets - if (token === '.' && i > 0 && tokens[i - 1] === '.' && !inBracket) - return false; +const validateJSONPath = (jsonPath: string): boolean => { + try { + JSONPath.parse(jsonPath); + return true; + } catch (error) { + return false; } - - if (depth !== 0) return false; // Unclosed brackets - if (inFilter) return false; // Unclosed filter - - return true; -} +}; export const jsonPathSchema = z .string() diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 307ac8f3c..376a3bde1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -561,6 +561,9 @@ importers: packages/taco: dependencies: + '@astronautlabs/jsonpath': + specifier: ^1.1.2 + version: 1.1.2 '@nucypher/nucypher-core': specifier: ^0.14.5 version: 0.14.5 @@ -659,6 +662,9 @@ packages: resolution: {integrity: sha512-QS4BlivXQy/uJgXcNOfXNjv8l+MSd+qQ256mY/Jc6iaWbfn69nRYh6chjSyLot4fHA49QxlZlWh1mJLlfNdtow==} engines: {node: '>=11.0.0'} + '@astronautlabs/jsonpath@1.1.2': + resolution: {integrity: sha512-FqL/muoreH7iltYC1EB5Tvox5E8NSOOPGkgns4G+qxRKl6k5dxEVljUjB5NcKESzkqwnUqWjSZkL61XGYOuV+A==} + '@babel/code-frame@7.23.5': resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} engines: {node: '>=6.9.0'} @@ -9078,6 +9084,10 @@ snapshots: transitivePeerDependencies: - debug + '@astronautlabs/jsonpath@1.1.2': + dependencies: + static-eval: 2.0.2 + '@babel/code-frame@7.23.5': dependencies: '@babel/highlight': 7.23.4 From 282ae2a511be582ead5cee5f83a08d8701250624 Mon Sep 17 00:00:00 2001 From: andresceballosm Date: Thu, 1 Aug 2024 14:31:32 -0500 Subject: [PATCH 5/6] Updated unit test --- .../taco/test/conditions/base/json-api.test.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/packages/taco/test/conditions/base/json-api.test.ts b/packages/taco/test/conditions/base/json-api.test.ts index 94b07af81..d71d1ee3f 100644 --- a/packages/taco/test/conditions/base/json-api.test.ts +++ b/packages/taco/test/conditions/base/json-api.test.ts @@ -7,33 +7,18 @@ describe('JSONPath Validation', () => { const invalidPath = '$.store.book[?(@.price < ]'; const result = jsonPathSchema.safeParse(invalidPath); expect(result.success).toBe(false); - if (!result.success) { - expect(result.error.errors[0].message).toBe( - 'Invalid JSONPath expression', - ); - } }); it('Invalid JSONPath: Incorrect use of brackets', () => { const invalidPath = '$[store][book]'; const result = jsonPathSchema.safeParse(invalidPath); expect(result.success).toBe(false); - if (!result.success) { - expect(result.error.errors[0].message).toBe( - 'Invalid JSONPath expression', - ); - } }); it('Invalid JSONPath: Unclosed wildcard asterisk', () => { const invalidPath = '$.store.book[*'; const result = jsonPathSchema.safeParse(invalidPath); expect(result.success).toBe(false); - if (!result.success) { - expect(result.error.errors[0].message).toBe( - 'Invalid JSONPath expression', - ); - } }); it('Valid JSONPath expression', () => { From 80c9dceceb07ea7f9c5d02194e08b48e523d94b0 Mon Sep 17 00:00:00 2001 From: andresceballosm Date: Thu, 1 Aug 2024 14:53:41 -0500 Subject: [PATCH 6/6] Update in unit tests --- packages/taco/test/conditions/base/json-api.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/taco/test/conditions/base/json-api.test.ts b/packages/taco/test/conditions/base/json-api.test.ts index d71d1ee3f..09c622e6e 100644 --- a/packages/taco/test/conditions/base/json-api.test.ts +++ b/packages/taco/test/conditions/base/json-api.test.ts @@ -7,18 +7,21 @@ describe('JSONPath Validation', () => { const invalidPath = '$.store.book[?(@.price < ]'; const result = jsonPathSchema.safeParse(invalidPath); expect(result.success).toBe(false); + expect(result.error!.errors[0].message).toBe('Invalid JSONPath expression'); }); it('Invalid JSONPath: Incorrect use of brackets', () => { const invalidPath = '$[store][book]'; const result = jsonPathSchema.safeParse(invalidPath); expect(result.success).toBe(false); + expect(result.error!.errors[0].message).toBe('Invalid JSONPath expression'); }); it('Invalid JSONPath: Unclosed wildcard asterisk', () => { const invalidPath = '$.store.book[*'; const result = jsonPathSchema.safeParse(invalidPath); expect(result.success).toBe(false); + expect(result.error!.errors[0].message).toBe('Invalid JSONPath expression'); }); it('Valid JSONPath expression', () => {