Skip to content

Feat/dynamic context validation in sdk #1703

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 40 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
dbb89d2
feat(sdk): dynamic-context-validation
tractorss Oct 8, 2024
85f3668
chore(web): add-graph-api-key-env
tractorss Oct 8, 2024
cdbbcf2
feat(subgraph): fetch-dispute-request-event-data-within-subgraph
tractorss Oct 11, 2024
c59e102
feat(kleros-sdk): get-dispute-function
tractorss Oct 14, 2024
220ea2f
chore(web): update-dispute-population-flow
tractorss Oct 14, 2024
6974fdb
chore(web): configure-sdk-with-web3-context
tractorss Oct 14, 2024
88963e7
chore(kleros-sdk): make-configuring-sdk-explicit
tractorss Oct 14, 2024
3ad42d5
refactor(kleros-sdk): implement-rabbit-ai-feedback
tractorss Oct 14, 2024
d2c8f9c
Merge branch 'dev' into feat/dynamic-context-validation-in-sdk
tractorss Oct 15, 2024
25063c7
chore: fixed the SDK tests, minor tweaks
jaybuidl Oct 15, 2024
3abfbc5
fix(kleros-sdk): public-client-null-check
tractorss Oct 15, 2024
9140518
chore(subgraph): redeploy-subgraphs
tractorss Oct 15, 2024
e8cd12d
fix(sdk): types and unit tests
jaybuidl Oct 15, 2024
d3e4b59
chore(sdk): release configuration for NPM, tsconfig tweaks
jaybuidl Oct 16, 2024
1a11c84
chore(sdk): release @kleros/[email protected]
jaybuidl Oct 16, 2024
98c2907
chore(sdk): release @kleros/[email protected]
jaybuidl Oct 16, 2024
b851f2a
chore(sdk): release @kleros/[email protected]
jaybuidl Oct 16, 2024
57711ba
docs(sdk): readme
jaybuidl Oct 16, 2024
cbdd6d1
chore: clean up
jaybuidl Oct 16, 2024
77b57e4
chore(kleros-sdk): define-entry-points-for-files
tractorss Oct 16, 2024
71f851b
chore(sdk): release @kleros/[email protected]
jaybuidl Oct 16, 2024
ecc9edf
refactor(kleros-sdk): remove-path-aliasing
tractorss Oct 18, 2024
e698548
refactor(kleros-sdk): update-get-dispute-function-parameter-type
tractorss Oct 21, 2024
93e59a1
fix(web): typing
tractorss Oct 21, 2024
18ae12f
Merge branch 'dev' into feat/dynamic-context-validation-in-sdk
tractorss Oct 21, 2024
0a8422f
chore: update-yarn-lock
tractorss Oct 21, 2024
04c80db
chore(kleros-sdk): update-get-dispute-id-spec
tractorss Oct 21, 2024
9b4e9d2
feat(kleros-sdk): better-error-handling-and-optimisations
tractorss Oct 22, 2024
7d5ed21
refactor(kleros-sdk): sonar-cloud-fixes
tractorss Oct 22, 2024
542a8d9
refactor(kleros-sdk): remoev-unused-import
tractorss Oct 22, 2024
3d42edc
refactor(kleros-sdk): address-coderabbit-feedback
tractorss Oct 22, 2024
a387e77
refactor(kleros-sdk): refactor-error-classes
tractorss Oct 22, 2024
56853b9
fix: test mocks
jaybuidl Oct 23, 2024
52b31a3
fix(kleros-sdk): replace-graphql-request-library-with-native-fetch
tractorss Oct 23, 2024
cab784b
chore(kleros-sdk): use-urql-for-gql-queries
tractorss Oct 24, 2024
0562712
feat(kleros-sdk): gql-client-caching
tractorss Oct 24, 2024
078b233
chore(sdk): release @kleros/[email protected]
jaybuidl Oct 25, 2024
128e1e5
chore(sdk): publish script
jaybuidl Oct 25, 2024
d2cb260
chore(sdk): release @kleros/[email protected]
jaybuidl Oct 25, 2024
7b2ccd3
Merge branch 'dev' into feat/dynamic-context-validation-in-sdk
jaybuidl Oct 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat(kleros-sdk): better-error-handling-and-optimisations
  • Loading branch information
tractorss committed Oct 22, 2024
commit 9b4e9d2152d4b14e16bf53504a770cbd9ab28cf7
3 changes: 2 additions & 1 deletion kleros-sdk/src/dataMappings/actions/callAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { parseAbiItem } from "viem";
import { AbiCallMapping } from "../utils/actionTypes";
import { createResultObject } from "../utils/createResultObject";
import { getPublicClient } from "../../sdk";
import { SdkNotConfiguredError } from "../../errors";

export const callAction = async (mapping: AbiCallMapping) => {
const publicClient = getPublicClient();

if (!publicClient) {
throw new Error("SDK not configured. Please call `configureSDK` before using.");
throw new SdkNotConfiguredError();
}

const { abi: source, address, functionName, args, seek, populate } = mapping;
Expand Down
3 changes: 2 additions & 1 deletion kleros-sdk/src/dataMappings/actions/eventAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { parseAbiItem, type AbiEvent } from "viem";
import { AbiEventMapping } from "../utils/actionTypes";
import { createResultObject } from "../utils/createResultObject";
import { getPublicClient } from "../../sdk";
import { SdkNotConfiguredError } from "../../errors";

export const eventAction = async (mapping: AbiEventMapping) => {
const publicClient = getPublicClient();

if (!publicClient) {
throw new Error("SDK not configured. Please call `configureSDK` before using.");
throw new SdkNotConfiguredError();
}

const { abi: source, address, eventFilter, seek, populate } = mapping;
Expand Down
9 changes: 5 additions & 4 deletions kleros-sdk/src/dataMappings/actions/fetchIpfsJsonAction.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { MAX_BYTE_SIZE } from "../../consts";
import { RequestError } from "../../errors";
import { FetchIpfsJsonMapping } from "../utils/actionTypes";
import { createResultObject } from "../utils/createResultObject";

Expand All @@ -13,23 +14,23 @@ export const fetchIpfsJsonAction = async (mapping: FetchIpfsJsonMapping) => {
} else if (!ipfsUri.startsWith("http")) {
httpUri = `https://ipfs.io/ipfs/${ipfsUri}`;
} else {
throw new Error("Invalid IPFS URI format");
throw new RequestError("Invalid IPFS URI format", httpUri);
}

const response = await fetch(httpUri, { method: "GET" });

if (!response.ok) {
throw new Error("Failed to fetch data from IPFS");
throw new RequestError("Failed to fetch data from IPFS", httpUri);
}

const contentLength = response.headers.get("content-length");
if (contentLength && parseInt(contentLength) > MAX_BYTE_SIZE) {
throw new Error("Response size is too large");
throw new RequestError("Response size is too large", httpUri);
}

const contentType = response.headers.get("content-type");
if (!contentType || !contentType.includes("application/json")) {
throw new Error("Fetched data is not JSON");
throw new RequestError("Fetched data is not JSON", httpUri);
}

const data = await response.json();
Expand Down
3 changes: 2 additions & 1 deletion kleros-sdk/src/dataMappings/executeActions.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { UnsupportedActionError } from "../errors";
import { callAction } from "./actions/callAction";
import { eventAction } from "./actions/eventAction";
import { fetchIpfsJsonAction } from "./actions/fetchIpfsJsonAction";
Expand Down Expand Up @@ -41,7 +42,7 @@ export const executeAction = async (
mapping = validateRealityMapping(mapping);
return await retrieveRealityData(mapping.realityQuestionID, context.arbitrableAddress as Address);
default:
throw new Error(`Unsupported action type: ${JSON.stringify(mapping)}`);
throw new UnsupportedActionError(`Unsupported action type: ${JSON.stringify(mapping)}`);
}
};

Expand Down
11 changes: 8 additions & 3 deletions kleros-sdk/src/dataMappings/retrieveRealityData.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { InvalidContextError, NotFoundError } from "../errors";
import { executeAction } from "./executeActions";
import { AbiEventMapping } from "./utils/actionTypes";

Expand All @@ -11,7 +12,7 @@ export type RealityAnswer = {

export const retrieveRealityData = async (realityQuestionID: string, arbitrable?: `0x${string}`) => {
if (!arbitrable) {
throw new Error("No arbitrable address provided");
throw new InvalidContextError("No arbitrable address provided");
}
const questionMapping: AbiEventMapping = {
type: "abi/event",
Expand Down Expand Up @@ -67,8 +68,12 @@ export const retrieveRealityData = async (realityQuestionID: string, arbitrable?
const templateData = await executeAction(templateMapping);
console.log("templateData", templateData);

if (!templateData || !questionData) {
throw new Error("Failed to retrieve template or question data");
if (!templateData) {
throw new NotFoundError("Template Data", "Failed to retrieve template data");
}

if (!questionData) {
throw new NotFoundError("Question Data", "Failed to retrieve question data");
}

const rc_question = require("@reality.eth/reality-eth-lib/formatters/question.js");
Expand Down
37 changes: 16 additions & 21 deletions kleros-sdk/src/dataMappings/utils/actionTypeValidators.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { InvalidMappingError } from "../../errors";
import {
SubgraphMapping,
AbiEventMapping,
Expand All @@ -9,43 +10,37 @@ import {
} from "./actionTypes";

export const validateSubgraphMapping = (mapping: ActionMapping) => {
if ((mapping as SubgraphMapping).endpoint === undefined) {
throw new Error("Invalid mapping for graphql action.");
}
return mapping as SubgraphMapping;
return validateMapping(mapping as SubgraphMapping, ["endpoint"]);
};

export const validateAbiEventMapping = (mapping: ActionMapping) => {
if ((mapping as AbiEventMapping).abi === undefined || (mapping as AbiEventMapping).eventFilter === undefined) {
throw new Error("Invalid mapping for abi/event action.");
}
return mapping as AbiEventMapping;
return validateMapping(mapping as AbiEventMapping, ["abi", "eventFilter"]);
};

export const validateAbiCallMapping = (mapping: ActionMapping) => {
if ((mapping as AbiCallMapping).abi === undefined || (mapping as AbiCallMapping).functionName === undefined) {
throw new Error("Invalid mapping for abi/call action.");
}
return mapping as AbiCallMapping;
return validateMapping(mapping as AbiCallMapping, ["abi", "functionName"]);
};

export const validateJsonMapping = (mapping: ActionMapping) => {
if ((mapping as JsonMapping).value === undefined) {
throw new Error("Invalid mapping for json action.");
}
return mapping as JsonMapping;
return validateMapping(mapping as JsonMapping, ["value"]);
};

export const validateFetchIpfsJsonMapping = (mapping: ActionMapping) => {
if ((mapping as FetchIpfsJsonMapping).ipfsUri === undefined) {
throw new Error("Invalid mapping for fetch/ipfs/json action.");
}
return mapping as FetchIpfsJsonMapping;
return validateMapping(mapping as FetchIpfsJsonMapping, ["ipfsUri"]);
};

export const validateRealityMapping = (mapping: ActionMapping) => {
if (mapping.type !== "reality" || typeof (mapping as RealityMapping).realityQuestionID !== "string") {
throw new Error("Invalid mapping for reality action.");
throw new InvalidMappingError("Expected field 'realityQuestionID' to be a string.");
}
return mapping as RealityMapping;
};

const validateMapping = <T extends ActionMapping>(mapping: T, requiredFields: (keyof T)[]) => {
for (const field of requiredFields) {
if (mapping[field] === undefined) {
throw new InvalidMappingError(`${field.toString()} is required for ${mapping.type}`);
}
}
return mapping;
};
3 changes: 2 additions & 1 deletion kleros-sdk/src/dataMappings/utils/populateTemplate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import mustache from "mustache";
import { DisputeDetails } from "./disputeDetailsTypes";
import DisputeDetailsSchema from "./disputeDetailsSchema";
import { InvalidFormatError } from "../../errors";

export const populateTemplate = (mustacheTemplate: string, data: any): DisputeDetails => {
const render = mustache.render(mustacheTemplate, data);
Expand All @@ -9,7 +10,7 @@ export const populateTemplate = (mustacheTemplate: string, data: any): DisputeDe
const validation = DisputeDetailsSchema.safeParse(dispute);
if (!validation.success) {
console.error("Validation errors:", validation.error.errors, "\n\nDispute details:", `${JSON.stringify(dispute)}`);
throw new Error("Invalid dispute details format");
throw new InvalidFormatError("Invalid dispute details format");
}
console.log(dispute);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import mustache from "mustache";
import retrieveVariables from "./retrieveVariables";
import { ActionMapping } from "./actionTypes";
import { InvalidContextError } from "../../errors";

export function replacePlaceholdersWithValues(
mapping: ActionMapping,
Expand Down Expand Up @@ -34,7 +35,7 @@ const validateContext = (template: string, context: Record<string, unknown>) =>
const variables = retrieveVariables(template);

variables.forEach((variable) => {
if (!context[variable]) throw new Error(`Expected key : "${variable}" to be provided in context.`);
if (!context[variable]) throw new InvalidContextError(`Expected key "${variable}" to be provided in context.`);
});
return true;
};
82 changes: 82 additions & 0 deletions kleros-sdk/src/errors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
export class InvalidContextError extends Error {
constructor(message: string) {
super(message);
this.name = "InvalidContextError";

if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}

export class InvalidMappingError extends Error {
constructor(message: string) {
super(message);
this.name = "InvalidMappingError";

if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}

export class NotFoundError extends Error {
public resourceName: string;

constructor(resourceName: string, message: string) {
super(message);
this.name = "NotFoundError";
this.resourceName = resourceName;

if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}

export class RequestError extends Error {
public endpoint: string | undefined;

constructor(message: string, endpoint?: string) {
super(message);
this.name = "RequestError";
this.endpoint = endpoint;

if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}

export class UnsupportedActionError extends Error {
constructor(message: string) {
super(message);
this.name = "UnsupportedActionError";

if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}

export class InvalidFormatError extends Error {
constructor(message: string) {
super(message);
this.name = "InvalidFormatError";

if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}

export class SdkNotConfiguredError extends Error {
constructor() {
super("SDK not configured. Please call `configureSDK` before using.");
this.name = "SdkNotConfiguredError";

if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}
13 changes: 9 additions & 4 deletions kleros-sdk/src/requests/fetchDisputeDetails.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { request } from "graphql-request";
import { RequestError } from "../errors";

type DisputeDetailsQueryResponse = {
dispute: {
Expand All @@ -13,8 +14,8 @@ type DisputeDetailsQueryResponse = {

const fetchDisputeDetails = async (endpoint: string, id: bigint) => {
const query = `
query DisputeDetails {
dispute(id: ${Number(id)}) {
query DisputeDetails($id: ID!) {
dispute(id: $id) {
arbitrated {
id
}
Expand All @@ -24,11 +25,15 @@ const fetchDisputeDetails = async (endpoint: string, id: bigint) => {
}
}
`;
const variables = { id: Number(id) };

try {
return await request<DisputeDetailsQueryResponse>(endpoint, query);
return await request<DisputeDetailsQueryResponse>(endpoint, query, variables);
} catch (error: any) {
throw new Error(`Error querying Dispute Details , endpoint : ${endpoint}, message : ${error?.message}`);
if (error instanceof Error) {
throw new RequestError(`Error querying Dispute Details: ${error.message}`, endpoint);
}
throw new RequestError("An unknown error occurred while querying Dispute Details", endpoint);
}
};

Expand Down
13 changes: 9 additions & 4 deletions kleros-sdk/src/requests/fetchDisputeTemplateFromId.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { request } from "graphql-request";
import { RequestError } from "../errors";

type DisputeTemplateQueryResponse = {
disputeTemplate: {
Expand All @@ -9,18 +10,22 @@ type DisputeTemplateQueryResponse = {

const fetchDisputeTemplateFromId = async (endpoint: string, id: number) => {
const query = `
query DisputeTemplate {
disputeTemplate(id: ${id}) {
query DisputeTemplate ($id: ID!) {
disputeTemplate(id: $id) {
templateData
templateDataMappings
}
}
`;

const variables = { id: id.toString() };
try {
return await request<DisputeTemplateQueryResponse>(endpoint, query);
return await request<DisputeTemplateQueryResponse>(endpoint, query, variables);
} catch (error: any) {
throw new Error(`Error querying Dispute Template Registry , endpoint : ${endpoint}, message : ${error?.message}`);
if (error instanceof Error) {
throw new RequestError(`Error querying Dispute Template: ${error.message}`, endpoint);
}
throw new RequestError("An unknown error occurred while querying Dispute Template", endpoint);
}
};

Expand Down
3 changes: 2 additions & 1 deletion kleros-sdk/src/sdk.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createPublicClient, type PublicClient } from "viem";
import { SdkConfig } from "./types";
import { SdkNotConfiguredError } from "./errors";

let publicClient: PublicClient | undefined;

Expand All @@ -11,7 +12,7 @@ export const configureSDK = (config: SdkConfig) => {

export const getPublicClient = (): PublicClient | undefined => {
if (!publicClient) {
throw new Error("SDK not configured. Please call `configureSDK` before using.");
throw new SdkNotConfiguredError();
}
return publicClient;
};
Loading