Skip to content

Commit

Permalink
Fix block range query parameter parsing (#76)
Browse files Browse the repository at this point in the history
* Fix block range query parameter parsing

* Fix `openapi` schema type
  • Loading branch information
0237h authored Sep 25, 2024
1 parent 9d9024b commit 27df478
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 149 deletions.
14 changes: 12 additions & 2 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Hono, type Context } from "hono";
import { type RootResolver, graphqlServer, getGraphQLParams } from '@hono/graphql-server';
import { type RootResolver, graphqlServer } from '@hono/graphql-server';
import { buildSchema } from 'graphql';
import { SafeParseSuccess, z } from 'zod';

Expand Down Expand Up @@ -86,7 +86,17 @@ async function AntelopeTokenAPI() {
async (ctx: Context) => {
// Use `unknown` for undefined schemas definitions in `zod.gen.ts`
const path_params_schema = paths[endpoint]["get"]["parameters"]["path"] ?? z.unknown();
const query_params_schema = paths[endpoint]["get"]["parameters"]["query"] ?? z.unknown();
const query_params_schema = z.preprocess(
(q: any) => {
// Preprocess block ranges to array so Zod can parse it
if (q.block_range)
q.block_range = q.block_range.split(',');

return q;
},
paths[endpoint]["get"]["parameters"]["query"] ?? z.unknown()
);

const path_params = path_params_schema.safeParse(ctx.req.param());
const query_params = query_params_schema.safeParse(ctx.req.query());

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "antelope-token-api",
"description": "Token balances, supply and transfers from the Antelope blockchains",
"version": "6.0.0",
"version": "6.0.1",
"homepage": "https://github.com/pinax-network/antelope-token-api",
"license": "MIT",
"authors": [
Expand Down
48 changes: 25 additions & 23 deletions src/types/zod.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@ export const apiErrorSchema = z.object({ "status": z.union([z.literal(500), z.li
export type ApiErrorSchema = z.infer<typeof apiErrorSchema>;


export const balanceSchema = z.object({ "last_updated_block": z.coerce.number(), "contract": z.coerce.string(), "symcode": z.coerce.string(), "balance": z.coerce.number() });
export const balanceSchema = z.object({ "last_updated_block": z.coerce.number().int(), "contract": z.coerce.string(), "symcode": z.coerce.string(), "balance": z.coerce.number() });
export type BalanceSchema = z.infer<typeof balanceSchema>;


export const balanceChangeSchema = z.object({ "trx_id": z.coerce.string(), "action_index": z.coerce.number(), "contract": z.coerce.string(), "symcode": z.coerce.string(), "precision": z.coerce.number(), "amount": z.coerce.number(), "value": z.coerce.number(), "block_num": z.coerce.number(), "timestamp": z.coerce.string(), "account": z.coerce.string(), "balance": z.coerce.string(), "balance_delta": z.coerce.number() });
export const balanceChangeSchema = z.object({ "trx_id": z.coerce.string(), "action_index": z.coerce.number().int(), "contract": z.coerce.string(), "symcode": z.coerce.string(), "precision": z.coerce.number().int(), "amount": z.coerce.number().int(), "value": z.coerce.number(), "block_num": z.coerce.number().int(), "timestamp": z.coerce.string(), "account": z.coerce.string(), "balance": z.coerce.string(), "balance_delta": z.coerce.number().int() });
export type BalanceChangeSchema = z.infer<typeof balanceChangeSchema>;


export const blockRangeSchema = z.array(z.coerce.number().int()).max(2);
export type BlockRangeSchema = z.infer<typeof blockRangeSchema>;


export const holderSchema = z.object({ "account": z.coerce.string(), "balance": z.coerce.number() });
export type HolderSchema = z.infer<typeof holderSchema>;

Expand All @@ -21,31 +25,31 @@ export const modelsScopeSchema = z.object({ "contract": z.coerce.string(), "symc
export type ModelsScopeSchema = z.infer<typeof modelsScopeSchema>;


export const paginationSchema = z.object({ "next_page": z.coerce.number(), "previous_page": z.coerce.number(), "total_pages": z.coerce.number(), "total_results": z.coerce.number() });
export const paginationSchema = z.object({ "next_page": z.coerce.number().int(), "previous_page": z.coerce.number().int(), "total_pages": z.coerce.number().int(), "total_results": z.coerce.number().int() });
export type PaginationSchema = z.infer<typeof paginationSchema>;


export const queryStatisticsSchema = z.object({ "elapsed": z.coerce.number(), "rows_read": z.coerce.number(), "bytes_read": z.coerce.number() });
export const queryStatisticsSchema = z.object({ "elapsed": z.coerce.number(), "rows_read": z.coerce.number().int(), "bytes_read": z.coerce.number().int() });
export type QueryStatisticsSchema = z.infer<typeof queryStatisticsSchema>;


export const responseMetadataSchema = z.object({ "statistics": z.lazy(() => queryStatisticsSchema).nullable(), "next_page": z.coerce.number(), "previous_page": z.coerce.number(), "total_pages": z.coerce.number(), "total_results": z.coerce.number() });
export const responseMetadataSchema = z.object({ "statistics": z.lazy(() => queryStatisticsSchema).nullable(), "next_page": z.coerce.number().int(), "previous_page": z.coerce.number().int(), "total_pages": z.coerce.number().int(), "total_results": z.coerce.number().int() });
export type ResponseMetadataSchema = z.infer<typeof responseMetadataSchema>;


export const supplySchema = z.object({ "trx_id": z.coerce.string(), "action_index": z.coerce.number(), "contract": z.coerce.string(), "symcode": z.coerce.string(), "precision": z.coerce.number(), "amount": z.coerce.number(), "value": z.coerce.number(), "block_num": z.coerce.number(), "timestamp": z.coerce.string(), "issuer": z.coerce.string(), "max_supply": z.coerce.string(), "supply": z.coerce.string(), "supply_delta": z.coerce.number() });
export const supplySchema = z.object({ "trx_id": z.coerce.string(), "action_index": z.coerce.number().int(), "contract": z.coerce.string(), "symcode": z.coerce.string(), "precision": z.coerce.number().int(), "amount": z.coerce.number().int(), "value": z.coerce.number(), "block_num": z.coerce.number().int(), "timestamp": z.coerce.string(), "issuer": z.coerce.string(), "max_supply": z.coerce.string(), "supply": z.coerce.string(), "supply_delta": z.coerce.number().int() });
export type SupplySchema = z.infer<typeof supplySchema>;


export const transferSchema = z.object({ "trx_id": z.coerce.string(), "action_index": z.coerce.number(), "contract": z.coerce.string(), "symcode": z.coerce.string(), "precision": z.coerce.number(), "amount": z.coerce.number(), "value": z.coerce.number(), "block_num": z.coerce.number(), "timestamp": z.coerce.string(), "from": z.coerce.string(), "to": z.coerce.string(), "quantity": z.coerce.string(), "memo": z.coerce.string() });
export const transferSchema = z.object({ "trx_id": z.coerce.string(), "action_index": z.coerce.number().int(), "contract": z.coerce.string(), "symcode": z.coerce.string(), "precision": z.coerce.number().int(), "amount": z.coerce.number().int(), "value": z.coerce.number(), "block_num": z.coerce.number().int(), "timestamp": z.coerce.string(), "from": z.coerce.string(), "to": z.coerce.string(), "quantity": z.coerce.string(), "memo": z.coerce.string() });
export type TransferSchema = z.infer<typeof transferSchema>;


export const versionSchema = z.object({ "version": z.coerce.string().regex(new RegExp("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$")), "commit": z.coerce.string().regex(new RegExp("^[0-9a-f]{7}$")) });
export type VersionSchema = z.infer<typeof versionSchema>;


export const usageBalanceQueryParamsSchema = z.object({ "account": z.coerce.string(), "contract": z.coerce.string().optional(), "symcode": z.coerce.string().optional(), "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() });
export const usageBalanceQueryParamsSchema = z.object({ "account": z.coerce.string(), "contract": z.coerce.string().optional(), "symcode": z.coerce.string().optional(), "limit": z.coerce.number().int().default(10).optional(), "page": z.coerce.number().int().default(1).optional() });
export type UsageBalanceQueryParamsSchema = z.infer<typeof usageBalanceQueryParamsSchema>;
/**
* @description Array of balances.
Expand All @@ -64,7 +68,7 @@ export const usageBalanceQueryResponseSchema = z.object({ "data": z.array(z.lazy
export type UsageBalanceQueryResponseSchema = z.infer<typeof usageBalanceQueryResponseSchema>;


export const usageBalanceHistoricalQueryParamsSchema = z.object({ "account": z.coerce.string(), "block_num": z.coerce.number(), "contract": z.coerce.string().optional(), "symcode": z.coerce.string().optional(), "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() });
export const usageBalanceHistoricalQueryParamsSchema = z.object({ "account": z.coerce.string(), "block_num": z.coerce.number().int(), "contract": z.coerce.string().optional(), "symcode": z.coerce.string().optional(), "limit": z.coerce.number().int().default(10).optional(), "page": z.coerce.number().int().default(1).optional() });
export type UsageBalanceHistoricalQueryParamsSchema = z.infer<typeof usageBalanceHistoricalQueryParamsSchema>;
/**
* @description Array of balances.
Expand All @@ -83,12 +87,12 @@ export const usageBalanceHistoricalQueryResponseSchema = z.object({ "data": z.ar
export type UsageBalanceHistoricalQueryResponseSchema = z.infer<typeof usageBalanceHistoricalQueryResponseSchema>;


export const usageHeadQueryParamsSchema = z.object({ "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() }).optional();
export const usageHeadQueryParamsSchema = z.object({ "limit": z.coerce.number().int().default(10).optional(), "page": z.coerce.number().int().default(1).optional() }).optional();
export type UsageHeadQueryParamsSchema = z.infer<typeof usageHeadQueryParamsSchema>;
/**
* @description Head block information.
*/
export const usageHead200Schema = z.object({ "data": z.array(z.object({ "block_num": z.coerce.number(), "block_id": z.coerce.string() })), "meta": z.lazy(() => responseMetadataSchema) });
export const usageHead200Schema = z.object({ "data": z.array(z.object({ "block_num": z.coerce.number().int(), "block_id": z.coerce.string() })), "meta": z.lazy(() => responseMetadataSchema) });
export type UsageHead200Schema = z.infer<typeof usageHead200Schema>;
/**
* @description An unexpected error response.
Expand All @@ -98,7 +102,7 @@ export type UsageHeadErrorSchema = z.infer<typeof usageHeadErrorSchema>;
/**
* @description Head block information.
*/
export const usageHeadQueryResponseSchema = z.object({ "data": z.array(z.object({ "block_num": z.coerce.number(), "block_id": z.coerce.string() })), "meta": z.lazy(() => responseMetadataSchema) });
export const usageHeadQueryResponseSchema = z.object({ "data": z.array(z.object({ "block_num": z.coerce.number().int(), "block_id": z.coerce.string() })), "meta": z.lazy(() => responseMetadataSchema) });
export type UsageHeadQueryResponseSchema = z.infer<typeof usageHeadQueryResponseSchema>;

/**
Expand All @@ -118,7 +122,7 @@ export const monitoringHealthQueryResponseSchema = z.coerce.string();
export type MonitoringHealthQueryResponseSchema = z.infer<typeof monitoringHealthQueryResponseSchema>;


export const usageHoldersQueryParamsSchema = z.object({ "contract": z.coerce.string(), "symcode": z.coerce.string(), "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() });
export const usageHoldersQueryParamsSchema = z.object({ "contract": z.coerce.string(), "symcode": z.coerce.string(), "limit": z.coerce.number().int().default(10).optional(), "page": z.coerce.number().int().default(1).optional() });
export type UsageHoldersQueryParamsSchema = z.infer<typeof usageHoldersQueryParamsSchema>;
/**
* @description Array of accounts.
Expand Down Expand Up @@ -155,21 +159,19 @@ export type MonitoringMetricsQueryResponseSchema = z.infer<typeof monitoringMetr
/**
* @description The OpenAPI JSON spec
*/
export const docsOpenapi200Schema = z.object({});
export const docsOpenapi200Schema = z.any();
export type DocsOpenapi200Schema = z.infer<typeof docsOpenapi200Schema>;
/**
* @description An unexpected error response.
*/
export const docsOpenapiErrorSchema = z.lazy(() => apiErrorSchema);
export type DocsOpenapiErrorSchema = z.infer<typeof docsOpenapiErrorSchema>;
/**
* @description The OpenAPI JSON spec
*/
export const docsOpenapiQueryResponseSchema = z.object({});

export const docsOpenapiQueryResponseSchema = z.any();
export type DocsOpenapiQueryResponseSchema = z.infer<typeof docsOpenapiQueryResponseSchema>;


export const usageSupplyQueryParamsSchema = z.object({ "block_num": z.coerce.number().optional(), "issuer": z.coerce.string().optional(), "contract": z.coerce.string(), "symcode": z.coerce.string(), "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() });
export const usageSupplyQueryParamsSchema = z.object({ "block_num": z.coerce.number().int().optional(), "issuer": z.coerce.string().optional(), "contract": z.coerce.string(), "symcode": z.coerce.string(), "limit": z.coerce.number().int().default(10).optional(), "page": z.coerce.number().int().default(1).optional() });
export type UsageSupplyQueryParamsSchema = z.infer<typeof usageSupplyQueryParamsSchema>;
/**
* @description Array of supplies.
Expand All @@ -188,7 +190,7 @@ export const usageSupplyQueryResponseSchema = z.object({ "data": z.array(z.lazy(
export type UsageSupplyQueryResponseSchema = z.infer<typeof usageSupplyQueryResponseSchema>;


export const usageTokensQueryParamsSchema = z.object({ "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() }).optional();
export const usageTokensQueryParamsSchema = z.object({ "limit": z.coerce.number().int().default(10).optional(), "page": z.coerce.number().int().default(1).optional() }).optional();
export type UsageTokensQueryParamsSchema = z.infer<typeof usageTokensQueryParamsSchema>;
/**
* @description Array of token identifier.
Expand All @@ -207,7 +209,7 @@ export const usageTokensQueryResponseSchema = z.object({ "data": z.array(z.lazy(
export type UsageTokensQueryResponseSchema = z.infer<typeof usageTokensQueryResponseSchema>;


export const usageTransfersQueryParamsSchema = z.object({ "block_range": z.array(z.coerce.number()).optional(), "contract": z.coerce.string(), "symcode": z.coerce.string(), "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() });
export const usageTransfersQueryParamsSchema = z.object({ "block_range": z.lazy(() => blockRangeSchema).optional(), "contract": z.coerce.string(), "symcode": z.coerce.string(), "limit": z.coerce.number().int().default(10).optional(), "page": z.coerce.number().int().default(1).optional() });
export type UsageTransfersQueryParamsSchema = z.infer<typeof usageTransfersQueryParamsSchema>;
/**
* @description Array of transfers.
Expand All @@ -226,7 +228,7 @@ export const usageTransfersQueryResponseSchema = z.object({ "data": z.array(z.la
export type UsageTransfersQueryResponseSchema = z.infer<typeof usageTransfersQueryResponseSchema>;


export const usageTransfersAccountQueryParamsSchema = z.object({ "account": z.coerce.string(), "block_range": z.array(z.coerce.number()).optional(), "from": z.coerce.string().optional(), "to": z.coerce.string().optional(), "contract": z.coerce.string().optional(), "symcode": z.coerce.string().optional(), "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() });
export const usageTransfersAccountQueryParamsSchema = z.object({ "account": z.coerce.string(), "block_range": z.lazy(() => blockRangeSchema).optional(), "from": z.coerce.string().optional(), "to": z.coerce.string().optional(), "contract": z.coerce.string().optional(), "symcode": z.coerce.string().optional(), "limit": z.coerce.number().int().default(10).optional(), "page": z.coerce.number().int().default(1).optional() });
export type UsageTransfersAccountQueryParamsSchema = z.infer<typeof usageTransfersAccountQueryParamsSchema>;
/**
* @description Array of transfers.
Expand All @@ -245,7 +247,7 @@ export const usageTransfersAccountQueryResponseSchema = z.object({ "data": z.arr
export type UsageTransfersAccountQueryResponseSchema = z.infer<typeof usageTransfersAccountQueryResponseSchema>;


export const usageTransferIdQueryParamsSchema = z.object({ "trx_id": z.coerce.string(), "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() });
export const usageTransferIdQueryParamsSchema = z.object({ "trx_id": z.coerce.string(), "limit": z.coerce.number().int().default(10).optional(), "page": z.coerce.number().int().default(1).optional() });
export type UsageTransferIdQueryParamsSchema = z.infer<typeof usageTransferIdQueryParamsSchema>;
/**
* @description Array of transfers.
Expand Down
12 changes: 7 additions & 5 deletions src/typespec/openapi3.tsp
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import "@typespec/http";
import "@typespec/openapi";
import "./models.tsp";
import "./config.js";

using TypeSpec.Http;
using TypeSpec.OpenAPI;
Expand All @@ -13,7 +12,7 @@ using TypeSpec.OpenAPI;
name: "MIT",
url: "https://github.com/pinax-network/antelope-token-api/blob/4f4bf36341b794c0ccf5b7a14fdf810be06462d2/LICENSE"
},
version: "6.0.0"
version: "6.0.1"
}) // From @typespec/openapi
//@server("https://eos.api.pinax.network/v1", "EOS V1 Api Endpoint")
namespace AntelopeTokenApi;
Expand Down Expand Up @@ -83,6 +82,9 @@ model UsageResponse<T> {
meta: ResponseMetadata;
}

@maxItems(2)
model BlockRange is BlockInfo.block_num[];

// Alias will *not* be present in the OpenAPI components.
// This also helps preventing self-references in generated `components` for codegen to work properly.
alias ApiResponse<T> = T | ApiError;
Expand Down Expand Up @@ -189,7 +191,7 @@ interface Usage {
@get
@useAuth(ApiKeyAuth<ApiKeyLocation.header, ApiKeyHeader>)
transfers(
@query({ format: "csv"}) block_range?: BlockInfo.block_num[],
@query(#{ explode: false }) block_range?: BlockRange,
@query contract: TokenIdentifier.contract,
@query symcode: TokenIdentifier.symcode,
...PaginationQueryParams,
Expand All @@ -205,7 +207,7 @@ interface Usage {
@useAuth(ApiKeyAuth<ApiKeyLocation.header, ApiKeyHeader>)
transfersAccount(
@query account: BalanceChange.account,
@query({ format: "csv"}) block_range?: BlockInfo.block_num[],
@query(#{ explode: false }) block_range?: BlockRange,
@query from?: Transfer.from,
@query to?: Transfer.to,
@query contract?: TokenIdentifier.contract,
Expand Down Expand Up @@ -244,7 +246,7 @@ interface Docs {
@summary("OpenAPI JSON spec")
@route("/openapi")
@get
openapi(): ApiResponse<Record<unknown>>;
openapi(): ApiResponse<{}>;

/**
Api version and Git short commit hash.
Expand Down
2 changes: 1 addition & 1 deletion src/usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export async function makeUsageQuery(ctx: Context, endpoint: UsageEndpoints, use
let query = "";
let additional_query_params: AdditionalQueryParams = {};

// Parse block range for endpoints that uses it. Check for single value or two comma-separated values.
// Parse block range for endpoints that uses it. Check for single value or two values.
if (endpoint == "/transfers" || endpoint == "/transfers/account") {
const q = query_params as ValidUserParams<typeof endpoint>;
if (q.block_range) {
Expand Down
12 changes: 0 additions & 12 deletions static/@openapi-to-graphql/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,6 @@ type Query {
"""
metrics: String

"""
Reflection endpoint to return OpenAPI JSON spec. Also used by Swagger to generate the frontpage.
Equivalent to GET /openapi
"""
openapi: JSON

"""
Total supply for a token.
Expand Down Expand Up @@ -162,11 +155,6 @@ type Holder {
balance: Float!
}

"""
The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
"""
scalar JSON @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf")

type Supply {
data: [Supply2]!
meta: ResponseMetadata!
Expand Down
Loading

0 comments on commit 27df478

Please sign in to comment.