Skip to content

Commit 9b4e9d2

Browse files
committed
feat(kleros-sdk): better-error-handling-and-optimisations
1 parent 04c80db commit 9b4e9d2

File tree

13 files changed

+148
-45
lines changed

13 files changed

+148
-45
lines changed

kleros-sdk/src/dataMappings/actions/callAction.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import { parseAbiItem } from "viem";
22
import { AbiCallMapping } from "../utils/actionTypes";
33
import { createResultObject } from "../utils/createResultObject";
44
import { getPublicClient } from "../../sdk";
5+
import { SdkNotConfiguredError } from "../../errors";
56

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

910
if (!publicClient) {
10-
throw new Error("SDK not configured. Please call `configureSDK` before using.");
11+
throw new SdkNotConfiguredError();
1112
}
1213

1314
const { abi: source, address, functionName, args, seek, populate } = mapping;

kleros-sdk/src/dataMappings/actions/eventAction.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import { parseAbiItem, type AbiEvent } from "viem";
22
import { AbiEventMapping } from "../utils/actionTypes";
33
import { createResultObject } from "../utils/createResultObject";
44
import { getPublicClient } from "../../sdk";
5+
import { SdkNotConfiguredError } from "../../errors";
56

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

910
if (!publicClient) {
10-
throw new Error("SDK not configured. Please call `configureSDK` before using.");
11+
throw new SdkNotConfiguredError();
1112
}
1213

1314
const { abi: source, address, eventFilter, seek, populate } = mapping;

kleros-sdk/src/dataMappings/actions/fetchIpfsJsonAction.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { MAX_BYTE_SIZE } from "../../consts";
2+
import { RequestError } from "../../errors";
23
import { FetchIpfsJsonMapping } from "../utils/actionTypes";
34
import { createResultObject } from "../utils/createResultObject";
45

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

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

2122
if (!response.ok) {
22-
throw new Error("Failed to fetch data from IPFS");
23+
throw new RequestError("Failed to fetch data from IPFS", httpUri);
2324
}
2425

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

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

3536
const data = await response.json();

kleros-sdk/src/dataMappings/executeActions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { UnsupportedActionError } from "../errors";
12
import { callAction } from "./actions/callAction";
23
import { eventAction } from "./actions/eventAction";
34
import { fetchIpfsJsonAction } from "./actions/fetchIpfsJsonAction";
@@ -41,7 +42,7 @@ export const executeAction = async (
4142
mapping = validateRealityMapping(mapping);
4243
return await retrieveRealityData(mapping.realityQuestionID, context.arbitrableAddress as Address);
4344
default:
44-
throw new Error(`Unsupported action type: ${JSON.stringify(mapping)}`);
45+
throw new UnsupportedActionError(`Unsupported action type: ${JSON.stringify(mapping)}`);
4546
}
4647
};
4748

kleros-sdk/src/dataMappings/retrieveRealityData.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { InvalidContextError, NotFoundError } from "../errors";
12
import { executeAction } from "./executeActions";
23
import { AbiEventMapping } from "./utils/actionTypes";
34

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

1213
export const retrieveRealityData = async (realityQuestionID: string, arbitrable?: `0x${string}`) => {
1314
if (!arbitrable) {
14-
throw new Error("No arbitrable address provided");
15+
throw new InvalidContextError("No arbitrable address provided");
1516
}
1617
const questionMapping: AbiEventMapping = {
1718
type: "abi/event",
@@ -67,8 +68,12 @@ export const retrieveRealityData = async (realityQuestionID: string, arbitrable?
6768
const templateData = await executeAction(templateMapping);
6869
console.log("templateData", templateData);
6970

70-
if (!templateData || !questionData) {
71-
throw new Error("Failed to retrieve template or question data");
71+
if (!templateData) {
72+
throw new NotFoundError("Template Data", "Failed to retrieve template data");
73+
}
74+
75+
if (!questionData) {
76+
throw new NotFoundError("Question Data", "Failed to retrieve question data");
7277
}
7378

7479
const rc_question = require("@reality.eth/reality-eth-lib/formatters/question.js");
Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { InvalidMappingError } from "../../errors";
12
import {
23
SubgraphMapping,
34
AbiEventMapping,
@@ -9,43 +10,37 @@ import {
910
} from "./actionTypes";
1011

1112
export const validateSubgraphMapping = (mapping: ActionMapping) => {
12-
if ((mapping as SubgraphMapping).endpoint === undefined) {
13-
throw new Error("Invalid mapping for graphql action.");
14-
}
15-
return mapping as SubgraphMapping;
13+
return validateMapping(mapping as SubgraphMapping, ["endpoint"]);
1614
};
1715

1816
export const validateAbiEventMapping = (mapping: ActionMapping) => {
19-
if ((mapping as AbiEventMapping).abi === undefined || (mapping as AbiEventMapping).eventFilter === undefined) {
20-
throw new Error("Invalid mapping for abi/event action.");
21-
}
22-
return mapping as AbiEventMapping;
17+
return validateMapping(mapping as AbiEventMapping, ["abi", "eventFilter"]);
2318
};
2419

2520
export const validateAbiCallMapping = (mapping: ActionMapping) => {
26-
if ((mapping as AbiCallMapping).abi === undefined || (mapping as AbiCallMapping).functionName === undefined) {
27-
throw new Error("Invalid mapping for abi/call action.");
28-
}
29-
return mapping as AbiCallMapping;
21+
return validateMapping(mapping as AbiCallMapping, ["abi", "functionName"]);
3022
};
3123

3224
export const validateJsonMapping = (mapping: ActionMapping) => {
33-
if ((mapping as JsonMapping).value === undefined) {
34-
throw new Error("Invalid mapping for json action.");
35-
}
36-
return mapping as JsonMapping;
25+
return validateMapping(mapping as JsonMapping, ["value"]);
3726
};
3827

3928
export const validateFetchIpfsJsonMapping = (mapping: ActionMapping) => {
40-
if ((mapping as FetchIpfsJsonMapping).ipfsUri === undefined) {
41-
throw new Error("Invalid mapping for fetch/ipfs/json action.");
42-
}
43-
return mapping as FetchIpfsJsonMapping;
29+
return validateMapping(mapping as FetchIpfsJsonMapping, ["ipfsUri"]);
4430
};
4531

4632
export const validateRealityMapping = (mapping: ActionMapping) => {
4733
if (mapping.type !== "reality" || typeof (mapping as RealityMapping).realityQuestionID !== "string") {
48-
throw new Error("Invalid mapping for reality action.");
34+
throw new InvalidMappingError("Expected field 'realityQuestionID' to be a string.");
4935
}
5036
return mapping as RealityMapping;
5137
};
38+
39+
const validateMapping = <T extends ActionMapping>(mapping: T, requiredFields: (keyof T)[]) => {
40+
for (const field of requiredFields) {
41+
if (mapping[field] === undefined) {
42+
throw new InvalidMappingError(`${field.toString()} is required for ${mapping.type}`);
43+
}
44+
}
45+
return mapping;
46+
};

kleros-sdk/src/dataMappings/utils/populateTemplate.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import mustache from "mustache";
22
import { DisputeDetails } from "./disputeDetailsTypes";
33
import DisputeDetailsSchema from "./disputeDetailsSchema";
4+
import { InvalidFormatError } from "../../errors";
45

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

kleros-sdk/src/dataMappings/utils/replacePlaceholdersWithValues.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import mustache from "mustache";
22
import retrieveVariables from "./retrieveVariables";
33
import { ActionMapping } from "./actionTypes";
4+
import { InvalidContextError } from "../../errors";
45

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

3637
variables.forEach((variable) => {
37-
if (!context[variable]) throw new Error(`Expected key : "${variable}" to be provided in context.`);
38+
if (!context[variable]) throw new InvalidContextError(`Expected key "${variable}" to be provided in context.`);
3839
});
3940
return true;
4041
};

kleros-sdk/src/errors/index.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
export class InvalidContextError extends Error {
2+
constructor(message: string) {
3+
super(message);
4+
this.name = "InvalidContextError";
5+
6+
if (Error.captureStackTrace) {
7+
Error.captureStackTrace(this, this.constructor);
8+
}
9+
}
10+
}
11+
12+
export class InvalidMappingError extends Error {
13+
constructor(message: string) {
14+
super(message);
15+
this.name = "InvalidMappingError";
16+
17+
if (Error.captureStackTrace) {
18+
Error.captureStackTrace(this, this.constructor);
19+
}
20+
}
21+
}
22+
23+
export class NotFoundError extends Error {
24+
public resourceName: string;
25+
26+
constructor(resourceName: string, message: string) {
27+
super(message);
28+
this.name = "NotFoundError";
29+
this.resourceName = resourceName;
30+
31+
if (Error.captureStackTrace) {
32+
Error.captureStackTrace(this, this.constructor);
33+
}
34+
}
35+
}
36+
37+
export class RequestError extends Error {
38+
public endpoint: string | undefined;
39+
40+
constructor(message: string, endpoint?: string) {
41+
super(message);
42+
this.name = "RequestError";
43+
this.endpoint = endpoint;
44+
45+
if (Error.captureStackTrace) {
46+
Error.captureStackTrace(this, this.constructor);
47+
}
48+
}
49+
}
50+
51+
export class UnsupportedActionError extends Error {
52+
constructor(message: string) {
53+
super(message);
54+
this.name = "UnsupportedActionError";
55+
56+
if (Error.captureStackTrace) {
57+
Error.captureStackTrace(this, this.constructor);
58+
}
59+
}
60+
}
61+
62+
export class InvalidFormatError extends Error {
63+
constructor(message: string) {
64+
super(message);
65+
this.name = "InvalidFormatError";
66+
67+
if (Error.captureStackTrace) {
68+
Error.captureStackTrace(this, this.constructor);
69+
}
70+
}
71+
}
72+
73+
export class SdkNotConfiguredError extends Error {
74+
constructor() {
75+
super("SDK not configured. Please call `configureSDK` before using.");
76+
this.name = "SdkNotConfiguredError";
77+
78+
if (Error.captureStackTrace) {
79+
Error.captureStackTrace(this, this.constructor);
80+
}
81+
}
82+
}

kleros-sdk/src/requests/fetchDisputeDetails.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { request } from "graphql-request";
2+
import { RequestError } from "../errors";
23

34
type DisputeDetailsQueryResponse = {
45
dispute: {
@@ -13,8 +14,8 @@ type DisputeDetailsQueryResponse = {
1314

1415
const fetchDisputeDetails = async (endpoint: string, id: bigint) => {
1516
const query = `
16-
query DisputeDetails {
17-
dispute(id: ${Number(id)}) {
17+
query DisputeDetails($id: ID!) {
18+
dispute(id: $id) {
1819
arbitrated {
1920
id
2021
}
@@ -24,11 +25,15 @@ const fetchDisputeDetails = async (endpoint: string, id: bigint) => {
2425
}
2526
}
2627
`;
28+
const variables = { id: Number(id) };
2729

2830
try {
29-
return await request<DisputeDetailsQueryResponse>(endpoint, query);
31+
return await request<DisputeDetailsQueryResponse>(endpoint, query, variables);
3032
} catch (error: any) {
31-
throw new Error(`Error querying Dispute Details , endpoint : ${endpoint}, message : ${error?.message}`);
33+
if (error instanceof Error) {
34+
throw new RequestError(`Error querying Dispute Details: ${error.message}`, endpoint);
35+
}
36+
throw new RequestError("An unknown error occurred while querying Dispute Details", endpoint);
3237
}
3338
};
3439

0 commit comments

Comments
 (0)