diff --git a/packages/taco/package.json b/packages/taco/package.json index 88bf9143..f9a68b82 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:*", @@ -48,7 +49,7 @@ }, "devDependencies": { "@nucypher/test-utils": "workspace:*", - "@types/semver": "^7.5.8" + "@types/semver": "^7.5.8", }, "engines": { "node": ">=18", diff --git a/packages/taco/src/conditions/base/json-api.ts b/packages/taco/src/conditions/base/json-api.ts index 0a55b20b..22032cb3 100644 --- a/packages/taco/src/conditions/base/json-api.ts +++ b/packages/taco/src/conditions/base/json-api.ts @@ -1,18 +1,31 @@ +import { JSONPath } from '@astronautlabs/jsonpath'; import { z } from 'zod'; import { Condition } from '../condition'; -import { - OmitConditionType, - returnValueTestSchema, -} from '../shared'; +import { OmitConditionType, returnValueTestSchema } from '../shared'; export const JsonApiConditionType = 'json-api'; +const validateJSONPath = (jsonPath: string): boolean => { + try { + JSONPath.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 00000000..09c622e6 --- /dev/null +++ b/packages/taco/test/conditions/base/json-api.test.ts @@ -0,0 +1,44 @@ +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); + 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', () => { + 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); + }); +}); diff --git a/packages/taco/test/conditions/base/json.test.ts b/packages/taco/test/conditions/base/json.test.ts index 9c26a7f7..0166485a 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 4defa46c..66bf4829 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 60ebab59..546b7c69 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 307ac8f3..376a3bde 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