Skip to content

Commit

Permalink
Add consistent error codes
Browse files Browse the repository at this point in the history
  • Loading branch information
0237h committed May 1, 2024
1 parent 30c9c19 commit b6501c6
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 36 deletions.
8 changes: 2 additions & 6 deletions src/fetch/GET.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import head from "./head.js";
import balance from "./balance.js";
import supply from "./supply.js";
import * as prometheus from "../prometheus.js";
import { logger } from "../logger.js";
import swaggerHtml from "../../swagger/index.html"
import swaggerFavicon from "../../swagger/favicon.png"
import transfers from "./transfers.js";
import { toJSON } from "./utils.js";
import { APIError, toJSON } from "./utils.js";
import { APP_VERSION } from "../config.js";

export default async function (req: Request) {
Expand All @@ -32,8 +31,5 @@ export default async function (req: Request) {
if (pathname === "/balance") return balance(req);
if (pathname === "/transfers") return transfers(req);

logger.warn(`Not found: ${pathname}`);
prometheus.request_error.inc({ pathname, status: 404 });

return new Response("Not found", { status: 404 });
return APIError(pathname, 404, "path_not_found", "Invalid pathname");
}
19 changes: 15 additions & 4 deletions src/fetch/balance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { makeQuery } from "../clickhouse/makeQuery.js";
import { logger } from "../logger.js";
import { getBalanceChanges } from "../queries.js";
import * as prometheus from "../prometheus.js";
import { addMetadata, toJSON } from "./utils.js";
import { APIError, addMetadata, toJSON } from "./utils.js";
import { parseLimit, parsePage } from "../utils.js";

function verifyParams(searchParams: URLSearchParams) {
Expand All @@ -14,12 +14,23 @@ function verifyParams(searchParams: URLSearchParams) {

export default async function (req: Request) {
try {
const { searchParams } = new URL(req.url);
const { pathname, searchParams } = new URL(req.url);
logger.info({ searchParams: Object.fromEntries(Array.from(searchParams)) });

verifyParams(searchParams);
try {
verifyParams(searchParams);
} catch (e: any) {
return APIError(pathname, 400, "bad_query_input", e.message);
}

const query = getBalanceChanges(searchParams);
const response = await makeQuery(query)
let response;

try {
response = await makeQuery(query);
} catch (e: any) {
return APIError(pathname, 500, "failed_database_query", e.message);
}

return toJSON(
addMetadata(
Expand Down
17 changes: 11 additions & 6 deletions src/fetch/head.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { addMetadata, toJSON } from "./utils.js";
import { APIError, addMetadata, toJSON } from "./utils.js";
import { makeQuery } from "../clickhouse/makeQuery.js";

export default async function (req: Request) {
return toJSON(
addMetadata(
await makeQuery("SELECT block_num FROM cursors ORDER BY block_num DESC LIMIT 1")
)
);
let query = "SELECT block_num FROM cursors ORDER BY block_num DESC LIMIT 1";
let response;

try {
response = await makeQuery(query);
} catch (e: any) {
return APIError(new URL(req.url).pathname, 500, "failed_database_query", e.message);
}

return toJSON(addMetadata(response));
}
4 changes: 2 additions & 2 deletions src/fetch/health.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export default async function (_req: Request) {
return new Response("Unknown response from ClickHouse");
} catch (e: any) {
logger.error(e);
prometheus.request_error.inc({ pathname: "/health", status: 500 });
prometheus.request_error.inc({ pathname: "/health", status: 503 });

return new Response(e.message, { status: 500 });
return new Response(e.message, { status: 503 });
}
}
29 changes: 25 additions & 4 deletions src/fetch/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { config } from "../config.js";
import { registry } from "../prometheus.js";
import { makeQuery } from "../clickhouse/makeQuery.js";
import { getBalanceChanges, getTotalSupply, getTransfers } from "../queries.js";
import { addMetadata } from "./utils.js";
import { APIError, addMetadata } from "./utils.js";
const TAGS = {
MONITORING: "Monitoring",
HEALTH: "Health",
Expand All @@ -28,9 +28,30 @@ const head_example = addMetadata({
bytes_read: 32
}
});
const supply_example = await makeQuery(getTotalSupply(new URLSearchParams({ limit: "1" }), true)).then(res => addMetadata(res, 1, 1));
const balance_example = await makeQuery(getBalanceChanges(new URLSearchParams({ limit: "2" }), true)).then(res => addMetadata(res, 2, 1));
const transfers_example = await makeQuery(getTransfers(new URLSearchParams({ limit: "5" }), true)).then(res => addMetadata(res, 5, 1));

const supply_example = await makeQuery(
getTotalSupply(new URLSearchParams({ limit: "1" }), true)
).then(
res => addMetadata(res, 1, 1)
).catch(
e => APIError("/openapi", 500, "failed_database_query", e.message)
);

const balance_example = await makeQuery(
getBalanceChanges(new URLSearchParams({ limit: "2" }), true)
).then(
res => addMetadata(res, 2, 1)
).catch(
e => APIError("/openapi", 500, "failed_database_query", e.message)
);

const transfers_example = await makeQuery(
getTotalSupply(new URLSearchParams({ limit: "5" }), true)
).then(
res => addMetadata(res, 5, 1)
).catch(
e => APIError("/openapi", 500, "failed_database_query", e.message)
);

const timestampSchema: SchemaObject = {
anyOf: [
Expand Down
19 changes: 15 additions & 4 deletions src/fetch/supply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { makeQuery } from "../clickhouse/makeQuery.js";
import { logger } from "../logger.js";
import { getTotalSupply } from "../queries.js";
import * as prometheus from "../prometheus.js";
import { addMetadata, toJSON } from "./utils.js";
import { APIError, addMetadata, toJSON } from "./utils.js";
import { parseLimit, parsePage } from "../utils.js";

function verifyParams(searchParams: URLSearchParams) {
Expand All @@ -14,12 +14,23 @@ function verifyParams(searchParams: URLSearchParams) {

export default async function (req: Request) {
try {
const { searchParams } = new URL(req.url);
const { pathname, searchParams } = new URL(req.url);
logger.info({ searchParams: Object.fromEntries(Array.from(searchParams)) });

verifyParams(searchParams);
try {
verifyParams(searchParams);
} catch (e: any) {
return APIError(pathname, 400, "bad_query_input", e.message);
}

const query = getTotalSupply(searchParams);
const response = await makeQuery(query)
let response;

try {
response = await makeQuery(query);
} catch (e: any) {
return APIError(pathname, 500, "failed_database_query", e.message);
}

return toJSON(
addMetadata(
Expand Down
12 changes: 9 additions & 3 deletions src/fetch/transfers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@ import { makeQuery } from "../clickhouse/makeQuery.js";
import { logger } from "../logger.js";
import { getTransfers } from "../queries.js";
import * as prometheus from "../prometheus.js";
import { addMetadata, toJSON } from "./utils.js";
import { APIError, addMetadata, toJSON } from "./utils.js";
import { parseLimit, parsePage } from "../utils.js";

export default async function (req: Request) {
try {
const { searchParams } = new URL(req.url);
const { pathname, searchParams } = new URL(req.url);
logger.info({ searchParams: Object.fromEntries(Array.from(searchParams)) });

const query = getTransfers(searchParams);
const response = await makeQuery(query)
let response;

try {
response = await makeQuery(query);
} catch (e: any) {
return APIError(pathname, 500, "failed_database_query", e.message);
}

return toJSON(
addMetadata(
Expand Down
10 changes: 5 additions & 5 deletions src/fetch/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const mock_query_reponse: Query<any> = {
meta: [],
data: Array(limit),
rows: limit,
rows_before_limit_at_least: 5*limit, // Simulate query with more total results than the query limit making pagination relevant
rows_before_limit_at_least: 5 * limit, // Simulate query with more total results than the query limit making pagination relevant
statistics: {
elapsed: 0,
rows_read: 0,
Expand All @@ -20,26 +20,26 @@ test("addMetadata pagination", () => {
expect(first_page.meta.next_page).toBe(2);
expect(first_page.meta.previous_page).toBe(1); // Previous page should be set to 1 on first page
expect(first_page.meta.total_pages).toBe(5);
expect(first_page.meta.total_results).toBe(5*limit);
expect(first_page.meta.total_results).toBe(5 * limit);

const odd_page = addMetadata(mock_query_reponse, limit, 3);
expect(odd_page.meta.next_page).toBe(4);
expect(odd_page.meta.previous_page).toBe(2);
expect(odd_page.meta.total_pages).toBe(5);
expect(odd_page.meta.total_results).toBe(5*limit);
expect(odd_page.meta.total_results).toBe(5 * limit);

const even_page = addMetadata(mock_query_reponse, limit, 4);
expect(even_page.meta.next_page).toBe(5);
expect(even_page.meta.previous_page).toBe(3);
expect(even_page.meta.total_pages).toBe(5);
expect(even_page.meta.total_results).toBe(5*limit);
expect(even_page.meta.total_results).toBe(5 * limit);

const last_page = addMetadata(mock_query_reponse, limit, 5);
// @ts-ignore
expect(last_page.meta.next_page).toBe(last_page.meta.total_pages); // Next page should be capped to total_pages on last page
expect(last_page.meta.previous_page).toBe(4);
expect(last_page.meta.total_pages).toBe(5);
expect(last_page.meta.total_results).toBe(5*limit);
expect(last_page.meta.total_results).toBe(5 * limit);

// TODO: Expect error message on beyond last page
// const beyond_last_page = addMetadata(mock_query_reponse.data, mock_query_reponse.rows_before_limit_at_least, limit, 6);
Expand Down
24 changes: 22 additions & 2 deletions src/fetch/utils.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,39 @@
import { Query } from "../clickhouse/makeQuery.js";
import { logger } from "../logger.js";
import * as prometheus from "../prometheus.js";

interface APIError {
status: number,
code?: string,
detail?: string
}

export function APIError(pathname: string, status: number, code?: string, detail?: string) {
const api_error: APIError = {
status,
code: code ? code : "unknown",
detail: detail ? detail : ""
}

logger.error(api_error);
prometheus.request_error.inc({ pathname, status });
return toJSON(api_error, status);
}

export function toJSON(data: any, status: number = 200) {
return new Response(JSON.stringify(data), { status, headers: { "Content-Type": "application/json" } });
}

export function addMetadata(response: Query<any>, req_limit?: number, req_page?: number) {
// TODO: Catch page number greater than total_pages and return error
if (typeof(req_limit) !== 'undefined' && typeof(req_page) !== 'undefined')
if (typeof (req_limit) !== 'undefined' && typeof (req_page) !== 'undefined')
return {
data: response.data,
meta: {
statistics: response.statistics,
"next_page": (req_page * req_limit >= response.rows_before_limit_at_least) ? req_page : req_page + 1,
"previous_page": (req_page <= 1) ? req_page : req_page - 1,
"total_pages": Math.ceil( response.rows_before_limit_at_least / req_limit),
"total_pages": Math.ceil(response.rows_before_limit_at_least / req_limit),
"total_results": response.rows_before_limit_at_least
}
}
Expand Down

0 comments on commit b6501c6

Please sign in to comment.