From cca09243c121ea5576c32808e5a21d37cf64cdca Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Fri, 19 Sep 2025 01:55:03 +0300 Subject: [PATCH 1/2] fix: show resource validation error as response Sometimes users are able to save broken resource for example without header value. We need to prevent failing all requests and instead fail only broken one. --- .../app/routes/rest.resources-loader.ts | 43 ++++++++++--------- apps/builder/app/shared/resources.ts | 29 +++++++------ 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/apps/builder/app/routes/rest.resources-loader.ts b/apps/builder/app/routes/rest.resources-loader.ts index 0954d19295ac..94c239a89b31 100644 --- a/apps/builder/app/routes/rest.resources-loader.ts +++ b/apps/builder/app/routes/rest.resources-loader.ts @@ -25,32 +25,35 @@ export const action = async ({ request }: ActionFunctionArgs) => { }; const requestJson = await request.json(); + const requestList = z.array(z.unknown()).safeParse(requestJson); - const computedResourcesParsed = z - .array(ResourceRequest) - .safeParse(requestJson); - - if (computedResourcesParsed.success === false) { - console.error( - "computedResources.parse", - computedResourcesParsed.error.toString() - ); + if (requestList.success === false) { console.error("data:", requestJson); - - throw data(computedResourcesParsed.error, { + throw data(requestList.error, { status: 400, }); } - const computedResources = computedResourcesParsed.data; - - const responses = await Promise.all( - computedResources.map((resource) => loadResource(customFetch, resource)) + const output = await Promise.all( + requestList.data.map(async (item) => { + const resource = ResourceRequest.safeParse(item); + if (resource.success === false) { + return [ + getResourceKey(item as ResourceRequest), + { + ok: false, + data: resource.error.format(), + status: 403, + statusText: "Resource validation error", + }, + ]; + } + return [ + getResourceKey(resource.data), + await loadResource(customFetch, resource.data), + ]; + }) ); - const output: [string, unknown][] = []; - responses.forEach((response, index) => { - const request = computedResources[index]; - output.push([getResourceKey(request), response]); - }); + return output; }; diff --git a/apps/builder/app/shared/resources.ts b/apps/builder/app/shared/resources.ts index 815368b595b0..529fac9541c7 100644 --- a/apps/builder/app/shared/resources.ts +++ b/apps/builder/app/shared/resources.ts @@ -7,18 +7,23 @@ import { fetch } from "./fetch.client"; const MAX_PENDING_RESOURCES = 5; -export const getResourceKey = (resource: ResourceRequest) => - hash( - JSON.stringify([ - // explicitly list all fields to keep hash stable - resource.name, - resource.method, - resource.url, - resource.searchParams, - resource.headers, - resource.body, - ]) - ); +export const getResourceKey = (resource: ResourceRequest) => { + try { + return hash( + JSON.stringify([ + // explicitly list all fields to keep hash stable + resource.name, + resource.method, + resource.url, + resource.searchParams, + resource.headers, + resource.body, + ]) + ); + } catch { + // guard from invalid resources + } +}; const queue = new Map(); const pending = new Map(); From 6e7c924c070d96b48652dfffe47f3857edcc9ad7 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Fri, 19 Sep 2025 02:05:35 +0300 Subject: [PATCH 2/2] Fix types --- apps/builder/app/shared/resources.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/builder/app/shared/resources.ts b/apps/builder/app/shared/resources.ts index 529fac9541c7..3f4078eb6fc6 100644 --- a/apps/builder/app/shared/resources.ts +++ b/apps/builder/app/shared/resources.ts @@ -22,6 +22,7 @@ export const getResourceKey = (resource: ResourceRequest) => { ); } catch { // guard from invalid resources + return ""; } };