diff --git a/packages/taco/src/conditions/base/json-api.ts b/packages/taco/src/conditions/base/json-api.ts new file mode 100644 index 000000000..0a55b20b8 --- /dev/null +++ b/packages/taco/src/conditions/base/json-api.ts @@ -0,0 +1,28 @@ +import { z } from 'zod'; + +import { Condition } from '../condition'; +import { + OmitConditionType, + returnValueTestSchema, +} from '../shared'; + +export const JsonApiConditionType = 'json-api'; + +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(), + returnValueTest: returnValueTestSchema, // Update to allow multiple return values after expanding supported methods +}); + +export type JsonApiConditionProps = z.infer; + +export class JsonApiCondition extends Condition { + constructor(value: OmitConditionType) { + super(JsonApiConditionSchema, { + conditionType: JsonApiConditionType, + ...value, + }); + } +} diff --git a/packages/taco/src/conditions/condition-factory.ts b/packages/taco/src/conditions/condition-factory.ts index 93e34452f..273cbe742 100644 --- a/packages/taco/src/conditions/condition-factory.ts +++ b/packages/taco/src/conditions/condition-factory.ts @@ -3,6 +3,11 @@ import { ContractConditionProps, ContractConditionType, } from './base/contract'; +import { + JsonApiCondition, + JsonApiConditionProps, + JsonApiConditionType, +} from './base/json-api'; import { RpcCondition, RpcConditionProps, RpcConditionType } from './base/rpc'; import { TimeCondition, @@ -30,6 +35,8 @@ export class ConditionFactory { return new ContractCondition(props as ContractConditionProps); case CompoundConditionType: return new CompoundCondition(props as CompoundConditionProps); + case JsonApiConditionType: + return new JsonApiCondition(props as JsonApiConditionProps); default: throw new Error(ERR_INVALID_CONDITION_TYPE(props.conditionType)); } diff --git a/packages/taco/test/conditions/base/json.test.ts b/packages/taco/test/conditions/base/json.test.ts new file mode 100644 index 000000000..9c26a7f7a --- /dev/null +++ b/packages/taco/test/conditions/base/json.test.ts @@ -0,0 +1,63 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { describe, expect, it } from 'vitest'; + +import { + JsonApiCondition, + JsonApiConditionSchema, +} from '../../../src/conditions/base/json-api'; +import { testJsonApiConditionObj } from '../../test-utils'; + +describe('JsonApiCondition', () => { + describe('validation', () => { + it('accepts a valid schema', () => { + const result = JsonApiCondition.validate( + JsonApiConditionSchema, + testJsonApiConditionObj, + ); + + expect(result.error).toBeUndefined(); + expect(result.data).toEqual(testJsonApiConditionObj); + }); + + it('rejects an invalid schema', () => { + const badJsonApiObj = { + ...testJsonApiConditionObj, + endpoint: 'not-a-url', + }; + + const result = JsonApiCondition.validate(JsonApiConditionSchema, badJsonApiObj); + + expect(result.error).toBeDefined(); + expect(result.data).toBeUndefined(); + expect(result.error?.format()).toMatchObject({ + endpoint: { + _errors: ['Invalid url'], + }, + }); + }); + + describe('parameters', () => { + it('accepts conditions without query path', () => { + const { query, ...noQueryObj} = testJsonApiConditionObj; + const result = JsonApiCondition.validate( + JsonApiConditionSchema, + noQueryObj + ); + + expect(result.error).toBeUndefined(); + expect(result.data).toEqual(noQueryObj); + }); + + it('accepts conditions without parameters', () => { + const { query, ...noParamsObj} = testJsonApiConditionObj; + const result = JsonApiCondition.validate( + JsonApiConditionSchema, + 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 bf4fe8946..4defa46cf 100644 --- a/packages/taco/test/conditions/condition-expr.test.ts +++ b/packages/taco/test/conditions/condition-expr.test.ts @@ -9,6 +9,7 @@ import { ContractCondition, ContractConditionProps, } from '../../src/conditions/base/contract'; +import { JsonApiCondition } from '../../src/conditions/base/json-api'; import { RpcCondition, RpcConditionType } from '../../src/conditions/base/rpc'; import { TimeCondition, @@ -20,6 +21,7 @@ import { ERC721Balance } from '../../src/conditions/predefined/erc721'; import { testContractConditionObj, testFunctionAbi, + testJsonApiConditionObj, testReturnValueTest, testRpcConditionObj, testTimeConditionObj, @@ -56,6 +58,7 @@ describe('condition set', () => { const rpcCondition = new RpcCondition(testRpcConditionObj); const timeCondition = new TimeCondition(testTimeConditionObj); + const jsonApiCondition = new JsonApiCondition(testJsonApiConditionObj); const compoundCondition = new CompoundCondition({ operator: 'and', operands: [ @@ -401,6 +404,23 @@ describe('condition set', () => { expect(conditionExprFromJson.condition).toBeInstanceOf(RpcCondition); }); + 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('parameters'); + expect(conditionExprJson).toContain('query'); + expect(conditionExprJson).toContain('$.ethereum.usd'); + expect(conditionExprJson).toContain('returnValueTest'); + + const conditionExprFromJson = ConditionExpression.fromJSON(conditionExprJson); + expect(conditionExprFromJson).toBeDefined(); + expect(conditionExprFromJson.condition).toBeInstanceOf(JsonApiCondition); + }); + it('compound condition serialization', () => { const conditionExpr = new ConditionExpression(compoundCondition); const compoundConditionObj = compoundCondition.toObj(); diff --git a/packages/taco/test/test-utils.ts b/packages/taco/test/test-utils.ts index 02de484f3..78db4f911 100644 --- a/packages/taco/test/test-utils.ts +++ b/packages/taco/test/test-utils.ts @@ -39,6 +39,10 @@ import { ContractConditionType, FunctionAbiProps, } from '../src/conditions/base/contract'; +import { + JsonApiConditionProps, + JsonApiConditionType +} from '../src/conditions/base/json-api'; import { RpcConditionProps, RpcConditionType, @@ -222,6 +226,17 @@ export const testTimeConditionObj: TimeConditionProps = { chain: TEST_CHAIN_ID, }; +export const testJsonApiConditionObj = { + conditionType: JsonApiConditionType, + endpoint: 'https://_this_would_totally_fail.com', + parameters: { + 'ids': 'ethereum', + 'vs_currencies': 'usd', + }, + query: '$.ethereum.usd', + returnValueTest: testReturnValueTest, +}; + export const testRpcConditionObj: RpcConditionProps = { conditionType: RpcConditionType, chain: TEST_CHAIN_ID,