From d8a46461352f0af81af113afe7d2146f734cec25 Mon Sep 17 00:00:00 2001 From: Hyoung-yoon Kim Date: Mon, 17 Aug 2026 23:19:47 -0400 Subject: [PATCH 1/2] fix(proxy): restore path param substitution in route jsonPath --- .../src/controllers/proxy/execute-route.ts | 6 +- workspace/data-proxy/src/proxy-server.test.ts | 142 ++++++++++++++++++ 2 files changed, 146 insertions(+), 2 deletions(-) diff --git a/workspace/data-proxy/src/controllers/proxy/execute-route.ts b/workspace/data-proxy/src/controllers/proxy/execute-route.ts index df3b2aa..b1e381f 100644 --- a/workspace/data-proxy/src/controllers/proxy/execute-route.ts +++ b/workspace/data-proxy/src/controllers/proxy/execute-route.ts @@ -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"; @@ -177,10 +178,11 @@ 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"), diff --git a/workspace/data-proxy/src/proxy-server.test.ts b/workspace/data-proxy/src/proxy-server.test.ts index 5667319..8a6faaa 100644 --- a/workspace/data-proxy/src/proxy-server.test.ts +++ b/workspace/data-proxy/src/proxy-server.test.ts @@ -491,6 +491,148 @@ 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("JSONPath $.[1][99] returned null"); + 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"; From 9fc7ce30f9f894e45b39cbd9766c1625f4f05d23 Mon Sep 17 00:00:00 2001 From: Hyoung-yoon Kim Date: Tue, 18 Aug 2026 10:00:11 -0400 Subject: [PATCH 2/2] fix(proxy): stop nesting QueryJsonError originator prefixes --- .../data-proxy/src/controllers/proxy/execute-route.ts | 2 +- .../src/controllers/proxy/handle-proxy-request.ts | 4 +--- workspace/data-proxy/src/errors.ts | 2 +- workspace/data-proxy/src/proxy-server.test.ts | 10 ++++++++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/workspace/data-proxy/src/controllers/proxy/execute-route.ts b/workspace/data-proxy/src/controllers/proxy/execute-route.ts index b1e381f..3410b65 100644 --- a/workspace/data-proxy/src/controllers/proxy/execute-route.ts +++ b/workspace/data-proxy/src/controllers/proxy/execute-route.ts @@ -189,7 +189,7 @@ export const executeRoute = ({ Effect.mapError( (error) => new QueryJsonError({ - error: error.message, + error: error.error, data: error.data, type: "config", status: 500, diff --git a/workspace/data-proxy/src/controllers/proxy/handle-proxy-request.ts b/workspace/data-proxy/src/controllers/proxy/handle-proxy-request.ts index ccc07c2..3c9d709 100644 --- a/workspace/data-proxy/src/controllers/proxy/handle-proxy-request.ts +++ b/workspace/data-proxy/src/controllers/proxy/handle-proxy-request.ts @@ -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 diff --git a/workspace/data-proxy/src/errors.ts b/workspace/data-proxy/src/errors.ts index 58d617f..acfc368 100644 --- a/workspace/data-proxy/src/errors.ts +++ b/workspace/data-proxy/src/errors.ts @@ -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}`; } diff --git a/workspace/data-proxy/src/proxy-server.test.ts b/workspace/data-proxy/src/proxy-server.test.ts index 8a6faaa..68b3182 100644 --- a/workspace/data-proxy/src/proxy-server.test.ts +++ b/workspace/data-proxy/src/proxy-server.test.ts @@ -628,7 +628,10 @@ describe("proxy server", () => { expect(response.status).toBe(500); const raw = await response.text(); - expect(raw).toContain("JSONPath $.[1][99] returned null"); + expect(raw).toContain( + "Query JSON (originator: config) error: JSONPath $.[1][99] returned null", + ); + expect(raw).not.toContain("originator: unknown"); expect(raw).not.toContain("{:index}"); }); }); @@ -710,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); });