Skip to content

Commit

Permalink
[Hotfix] Simplify router._handleResponse (#58)
Browse files Browse the repository at this point in the history
This was not working for queryId=3881689.
Still unclear why, but this simplification makes sense anyway.
  • Loading branch information
bh2smith committed Jul 2, 2024
1 parent 99c5b22 commit bf50b19
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 58 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@duneanalytics/client-sdk",
"version": "0.2.0",
"version": "0.2.1",
"author": "Ben Smith <[email protected]>",
"description": "Node Client for Dune Analytics' officially supported API.",
"repository": "[email protected]:duneanalytics/ts-dune-client.git",
Expand Down
31 changes: 5 additions & 26 deletions src/api/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,39 +54,18 @@ export class Router {
}

protected async _handleResponse<T>(responsePromise: Promise<Response>): Promise<T> {
let result;
try {
const response = await responsePromise;

if (!response.ok) {
log.error(
logPrefix,
`response error ${response.status} - ${response.statusText}`,
);
}
const clonedResponse = response.clone();
try {
// Attempt to parse JSON
result = await response.json();
} catch {
// Fallback to text if JSON parsing fails
// This fallback is used for CSV retrieving methods.
result = await clonedResponse.text();
const errorText = await response.text();
throw new DuneError(`HTTP - Status: ${response.status}, Message: ${errorText}`);
}

// Check for error in result after parsing
if (result.error) {
log.error(logPrefix, `error contained in response ${JSON.stringify(result)}`);
// Assuming DuneError is a custom Error you'd like to throw
throw new DuneError(
result.error instanceof Object ? result.error.type : result.error,
);
}
return (await response.json()) as T;
} catch (error) {
log.error(logPrefix, `caught unhandled response error ${JSON.stringify(error)}`);
log.error(logPrefix, error);
throw new DuneError(`Response ${error}`);
}
return result;
}

protected async _request<T>(
Expand Down Expand Up @@ -119,7 +98,7 @@ export class Router {
/// Build Url Search Parameters on GET
if (method === "GET" && payload) {
const searchParams = new URLSearchParams(payloadSearchParams(payload)).toString();
pathParams = `?${searchParams}`;
pathParams = searchParams ? `?${searchParams}` : "";
}
log.debug("Final request URL", url + pathParams);
const response = fetch(url + pathParams, requestData);
Expand Down
7 changes: 7 additions & 0 deletions src/types/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,13 @@ export interface ResultsResponse extends TimeData {
state: ExecutionState;
// only present when state is COMPLETE
result?: ExecutionResult;
error?: ErrorResult;
}

export interface ErrorResult {
type: string;
message: string;
metadata?: { line: number; column: number };
}

export interface LatestResultsResponse {
Expand Down
54 changes: 29 additions & 25 deletions tests/e2e/execution.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,55 +229,59 @@ describe("ExecutionAPI: Errors", () => {

it("returns invalid API key", async () => {
const bad_client = new ExecutionAPI("Bad Key");
await expectAsyncThrow(bad_client.executeQuery(1), "Response Error: invalid API Key");
await expectAsyncThrow(
bad_client.executeQuery(1),
`Response Error: HTTP - Status: 401, Message: {"error":"invalid API Key"}`,
);
});

it("returns query not found error", async () => {
await expectAsyncThrow(
client.executeQuery(999999999),
"Response Error: Query not found",
);
await expectAsyncThrow(client.executeQuery(0), "Response Error: Query not found");
const message = `Response Error: HTTP - Status: 404, Message: {"error":"Query not found"}`;
await expectAsyncThrow(client.executeQuery(999999999), message);
await expectAsyncThrow(client.executeQuery(0), message);
});

it("returns invalid job id", async () => {
const invalidJobID = "Wonky Job ID";
const expectedErrorMessage = `Response Error: The requested execution ID (ID: ${invalidJobID}) is invalid.`;
await expectAsyncThrow(client.getExecutionStatus(invalidJobID), expectedErrorMessage);
await expectAsyncThrow(
client.getExecutionResults(invalidJobID),
expectedErrorMessage,
);
await expectAsyncThrow(client.cancelExecution(invalidJobID), expectedErrorMessage);
const errorMessage = `Response Error: HTTP - Status: 400, Message: {"error":"The requested execution ID (ID: ${invalidJobID}) is invalid."}`;
await expectAsyncThrow(client.getExecutionStatus(invalidJobID), errorMessage);
await expectAsyncThrow(client.getExecutionResults(invalidJobID), errorMessage);
await expectAsyncThrow(client.cancelExecution(invalidJobID), errorMessage);
});
it("fails execute with unknown query parameter", async () => {
const queryID = 1215383;
const invalidParameterName = "Invalid Parameter Name";
const invalidParameterName = "Invalid Parameter";
const errorMessage = `Response Error: HTTP - Status: 400, Message: {"error":"unknown parameters (${invalidParameterName})"}`;
await expectAsyncThrow(
client.executeQuery(queryID, {
query_parameters: [QueryParameter.text(invalidParameterName, "")],
}),
`Response Error: unknown parameters (${invalidParameterName})`,
errorMessage,
);
});
it("does not allow to execute private queries for other accounts.", async () => {
await expectAsyncThrow(
client.executeQuery(1348384),
"Response Error: Query not found",
`Response Error: HTTP - Status: 404, Message: {"error":"Query not found"}`,
);
});

it("fails with unhandled FAILED_TYPE_UNSPECIFIED when query won't compile", async () => {
// Execute and check state
// V1 query: 1348966
await expectAsyncThrow(
client.getExecutionResults("01GEHG4AY1Z9JBR3BYB20E7RGH"),
"Response Error: FAILED_TYPE_EXECUTION_FAILED",
);
// V2 -query: :1349019
await expectAsyncThrow(
client.getExecutionResults("01GEHGXHQ25XWMVFJ4G2HZ5MGS"),
"Response Error: FAILED_TYPE_EXECUTION_FAILED",
);
const result = await client.getExecutionResults("01GEHG4AY1Z9JBR3BYB20E7RGH");
expect(result.error).to.be.deep.equal({
type: "FAILED_TYPE_EXECUTION_FAILED",
message:
'column "x" does not exist at line 1, position 8 [Execution ID: 01GEHG4AY1Z9JBR3BYB20E7RGH]',
metadata: { line: 1, column: 8 },
});

const result2 = await client.getExecutionResults("01GEHGXHQ25XWMVFJ4G2HZ5MGS");
expect(result2.error).to.be.deep.equal({
type: "FAILED_TYPE_EXECUTION_FAILED",
message:
"{42000} [Simba][Hardy] (80) Syntax or semantic analysis error thrown in server while executing query. Error message from server: org.apache.hive.service.cli.HiveSQLException: Error running query: [UNRESOLVED_COLUMN] org.apache.spark.sql.AnalysisException: [UNRESOLVED_COLUMN] A column or function parameter with name `x` cannot be resolved. Did you mean one of the following? []; line [Execution ID: 01GEHGXHQ25XWMVFJ4G2HZ5MGS]",
});
});
});
7 changes: 1 addition & 6 deletions tests/e2e/query.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ import { expect } from "chai";
import { QueryParameter, QueryAPI } from "../../src";
import { PLUS_KEY, BASIC_KEY, expectAsyncThrow } from "./util";

const PREMIUM_PLAN_MESSAGE =
"Response Error: Query management endpoints are only available in our paid plans. Please upgrade to a paid plan to use it.";

describe("QueryAPI: Premium - CRUD Operations", () => {
let plusClient: QueryAPI;

Expand Down Expand Up @@ -32,8 +29,6 @@ describe("QueryAPI: Premium - CRUD Operations", () => {
it("unarchive, make public, make private, rearchive", async () => {
const queryId = 3530410;
let query = await plusClient.readQuery(queryId);
expect(query.is_archived).to.be.equal(true);
expect(query.is_private).to.be.equal(true);

await plusClient.unarchiveQuery(queryId);
await plusClient.makePublic(queryId);
Expand Down Expand Up @@ -62,7 +57,7 @@ describe("QueryAPI: Errors", () => {
name: "Query Name",
query_sql: "select 1",
}),
PREMIUM_PLAN_MESSAGE,
`Response Error: HTTP - Status: 403, Message: {"error":"Query management endpoints are only available in our paid plans. Please upgrade to a paid plan to use it."}`,
);
});
});

0 comments on commit bf50b19

Please sign in to comment.