Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 5 additions & 3 deletions workspace/data-proxy/src/controllers/proxy/execute-route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import type { ModuleHandlers } from "../../modules/module";
import { HttpClientService } from "../../services/http-client";
import { queryJson } from "../../utils/query-json";
import { replaceParams } from "../../utils/replace-params";
import { createUrlSearchParams } from "../../utils/search-params";
import { handleMultiRequest } from "./handle-multi-request";
import { handleUpstreamRequest } from "./handle-upstream-request";
Expand Down Expand Up @@ -177,17 +178,18 @@ export const executeRoute = ({
}

if (route.jsonPath) {
yield* Effect.logDebug(`Applying route JSONpath ${route.jsonPath}`);
const jsonPath = replaceParams(route.jsonPath, params);
yield* Effect.logDebug(`Applying route JSONpath ${jsonPath}`);
const data = yield* queryJson(
upstreamTextResponse,
route.jsonPath,
jsonPath,
route.useLegacyJsonPath,
).pipe(
Effect.annotateSpans("type", "route-config"),
Effect.mapError(
(error) =>
new QueryJsonError({
error: error.message,
error: error.error,
data: error.data,
type: "config",
status: 500,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,7 @@ export const handleProxyRequest = (inputParams: HandleProxyRequestParams) =>
new QueryJsonError({
// Attach result of operator supplied JSON path, which should
// limit the size of data returned to the user.
error: error.message.concat(
`for input ${JSON.stringify(responseData)}`,
),
error: `${error.error} for input ${JSON.stringify(responseData)}`,
data: error.data,
type: "header",
// Fault is from the user side
Expand Down
2 changes: 1 addition & 1 deletion workspace/data-proxy/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,5 @@ export class QueryJsonError extends Data.TaggedError("QueryJsonError")<{
type?: "config" | "header";
status?: number;
}> {
message = `Query JSON (originator: ${this.type ?? "unknown"}) error: ${this.error} `;
message = `Query JSON (originator: ${this.type ?? "unknown"}) error: ${this.error}`;
}
150 changes: 149 additions & 1 deletion workspace/data-proxy/src/proxy-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,151 @@ describe("proxy server", () => {
});
});

describe("route jsonPath path params", () => {
const assetCtxs = [{ markPx: "100" }, { markPx: "200" }, { markPx: "300" }];
const upstreamBody = [
{ universe: [{ name: "A" }, { name: "B" }, { name: "C" }] },
assetCtxs,
];

it("substitutes path params before applying jsonPath", async () => {
const { upstreamUrl, port } = registerHandler(
"post",
"/jsonpath-params-info",
async () => HttpResponse.json(upstreamBody),
);

await Effect.runPromise(
startProxyServer(
{
verificationMaxRetries: 2,
verificationRetryDelay: 1000,
routeGroup: "",
modules: [],
sedaFast: {
enable: true,
maxProofAgeMs: 1000,
allowedClients: [],
},
statusEndpoints: {
root: "status",
},
multiEndpoint: {
enable: false,
path: "multi",
maxSubRequests: 20,
concurrency: 5,
},
baseURL: Maybe.nothing(),
routes: [
{
baseURL: Maybe.nothing(),
method: "POST",
path: "/mainnet/:index",
upstreamUrl,
forwardResponseHeaders: new Set([]),
headers: {},
jsonPath: "$.[1][{:index}]",
type: "upstream",
moduleName: "upstream",
useLegacyJsonPath: true,
},
],
fastOnly: false,
},
dataProxy,
{
disableProof: true,
port,
},
)
.pipe(Effect.scoped)
.pipe(Effect.provide(HttpClientService.Default()))
.pipe(Logger.withMinimumLogLevel(LogLevel.None)),
);

const response = await fetch(`http://localhost:${port}/mainnet/1`, {
method: "POST",
headers: { "content-type": "application/json" },
body: '{"type":"metaAndAssetCtxs","dex":"xyz"}',
});

expect(response.status).toBe(200);
expect(await response.json()).toEqual(assetCtxs[1]);
});

it("includes the substituted jsonPath in QueryJsonError when the index is missing", async () => {
const { upstreamUrl, port } = registerHandler(
"post",
"/jsonpath-params-missing-index",
async () => HttpResponse.json(upstreamBody),
);

await Effect.runPromise(
startProxyServer(
{
verificationMaxRetries: 2,
verificationRetryDelay: 1000,
routeGroup: "",
modules: [],
sedaFast: {
enable: true,
maxProofAgeMs: 1000,
allowedClients: [],
},
statusEndpoints: {
root: "status",
},
multiEndpoint: {
enable: false,
path: "multi",
maxSubRequests: 20,
concurrency: 5,
},
baseURL: Maybe.nothing(),
routes: [
{
baseURL: Maybe.nothing(),
method: "POST",
path: "/mainnet/:index",
upstreamUrl,
forwardResponseHeaders: new Set([]),
headers: {},
jsonPath: "$.[1][{:index}]",
type: "upstream",
moduleName: "upstream",
useLegacyJsonPath: true,
},
],
fastOnly: false,
},
dataProxy,
{
disableProof: true,
port,
},
)
.pipe(Effect.scoped)
.pipe(Effect.provide(HttpClientService.Default()))
.pipe(Logger.withMinimumLogLevel(LogLevel.None)),
);

const response = await fetch(`http://localhost:${port}/mainnet/99`, {
method: "POST",
headers: { "content-type": "application/json" },
body: '{"type":"metaAndAssetCtxs","dex":"xyz"}',
});

expect(response.status).toBe(500);
const raw = await response.text();
expect(raw).toContain(
"Query JSON (originator: config) error: JSONPath $.[1][99] returned null",
);
expect(raw).not.toContain("originator: unknown");
expect(raw).not.toContain("{:index}");
});
});

it("when user-supplied JSON path is invalid, the result of operator-supplied JSON path should be returned with a 400 status", async () => {
const picked = "PICKED_BY_OPERATOR_SUPPLIED_JSON_PATH";
const notPicked = "NOT_PICKED_BY_OPERATOR_SUPPLIED_JSON_PATH";
Expand Down Expand Up @@ -568,7 +713,10 @@ describe("proxy server", () => {

expect(parsed).not.toHaveProperty("data");
expect(parsed._tag).toBe("QueryJsonError");
expect(raw).toContain(invalidPath);
expect(raw).toContain(
`Query JSON (originator: header) error: JSONPath ${invalidPath} returned null`,
);
expect(raw).not.toContain("originator: unknown");
expect(raw).toContain(picked);
expect(raw).not.toContain(notPicked);
});
Expand Down
Loading