From 770d3977fa1a4102f08272111948dbcf442978c4 Mon Sep 17 00:00:00 2001 From: Celal Zamanoglu <95054566+celalzamanoglu@users.noreply.github.com> Date: Mon, 26 Jan 2026 17:54:43 +0300 Subject: [PATCH] Add JSON and URL validation for HTTP Request Block (#4544) --- .../nodes/HttpRequestNode/HttpRequestNode.tsx | 10 ++++- .../nodes/HttpRequestNode/HttpUtils.tsx | 42 ++++++++++++------- .../nodes/HttpRequestNode/httpValidation.ts | 36 ++++++++++++++++ .../workflows/editor/workflowEditorUtils.ts | 31 +++++++++++++- 4 files changed, 102 insertions(+), 17 deletions(-) create mode 100644 skyvern-frontend/src/routes/workflows/editor/nodes/HttpRequestNode/httpValidation.ts diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/HttpRequestNode/HttpRequestNode.tsx b/skyvern-frontend/src/routes/workflows/editor/nodes/HttpRequestNode/HttpRequestNode.tsx index 6296b55e..6698417d 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/HttpRequestNode/HttpRequestNode.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/HttpRequestNode/HttpRequestNode.tsx @@ -39,7 +39,12 @@ import { CodeIcon, PlusIcon, MagicWandIcon } from "@radix-ui/react-icons"; import { WorkflowBlockParameterSelect } from "../WorkflowBlockParameterSelect"; import { CurlImportDialog } from "./CurlImportDialog"; import { QuickHeadersDialog } from "./QuickHeadersDialog"; -import { MethodBadge, UrlValidator, RequestPreview } from "./HttpUtils"; +import { + MethodBadge, + UrlValidator, + RequestPreview, + JsonValidator, +} from "./HttpUtils"; import { useRerender } from "@/hooks/useRerender"; import { useRecordingStore } from "@/store/useRecordingStore"; import { cn } from "@/util/utils"; @@ -271,6 +276,7 @@ function HttpRequestNode({ id, data, type }: NodeProps) { minHeight="80px" maxHeight="160px" /> + {/* Body Section */} @@ -312,6 +318,7 @@ function HttpRequestNode({ id, data, type }: NodeProps) { minHeight="100px" maxHeight="200px" /> + )} @@ -333,6 +340,7 @@ function HttpRequestNode({ id, data, type }: NodeProps) { minHeight="80px" maxHeight="160px" /> + )} diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/HttpRequestNode/HttpUtils.tsx b/skyvern-frontend/src/routes/workflows/editor/nodes/HttpRequestNode/HttpUtils.tsx index 9f94732d..df4d746f 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/HttpRequestNode/HttpUtils.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/HttpRequestNode/HttpUtils.tsx @@ -10,6 +10,7 @@ import { useState } from "react"; import { toast } from "@/components/ui/use-toast"; import { copyText } from "@/util/copyText"; import { cn } from "@/util/utils"; +import { validateUrl, validateJson } from "./httpValidation"; // HTTP Method Badge Component export function MethodBadge({ @@ -56,21 +57,7 @@ export function MethodBadge({ // URL Validation Component export function UrlValidator({ url }: { url: string }) { - const isValidUrl = (urlString: string) => { - if (!urlString.trim()) return { valid: false, message: "URL is required" }; - - try { - const url = new URL(urlString); - if (!["http:", "https:"].includes(url.protocol)) { - return { valid: false, message: "URL must use HTTP or HTTPS protocol" }; - } - return { valid: true, message: "Valid URL" }; - } catch { - return { valid: false, message: "Invalid URL format" }; - } - }; - - const validation = isValidUrl(url); + const validation = validateUrl(url); if (!url.trim()) return null; @@ -93,6 +80,31 @@ export function UrlValidator({ url }: { url: string }) { ); } +// JSON Validation Component +export function JsonValidator({ value }: { value: string }) { + const validation = validateJson(value); + + if (validation.message === null) return null; + + return ( +
+ {validation.valid ? ( + + ) : ( + + )} + {validation.message} +
+ ); +} + // Copy to Curl Component export function CopyToCurlButton({ method, diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/HttpRequestNode/httpValidation.ts b/skyvern-frontend/src/routes/workflows/editor/nodes/HttpRequestNode/httpValidation.ts new file mode 100644 index 00000000..61080b47 --- /dev/null +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/HttpRequestNode/httpValidation.ts @@ -0,0 +1,36 @@ +import { TSON } from "@/util/tson"; + +// URL Validation Helper +export function validateUrl(url: string): { valid: boolean; message: string } { + const trimmed = url.trim(); + if (!trimmed) { + return { valid: false, message: "URL is required" }; + } + + try { + const parsed = new URL(trimmed); + if (!["http:", "https:"].includes(parsed.protocol)) { + return { valid: false, message: "URL must use HTTP or HTTPS protocol" }; + } + return { valid: true, message: "Valid URL" }; + } catch { + return { valid: false, message: "Invalid URL format" }; + } +} + +// JSON Validation Helper +export function validateJson(value: string): { + valid: boolean; + message: string | null; +} { + const trimmed = value.trim(); + if (!trimmed || trimmed === "{}" || trimmed === "[]") { + return { valid: true, message: null }; + } + + const result = TSON.parse(trimmed); + if (result.success) { + return { valid: true, message: "Valid JSON" }; + } + return { valid: false, message: result.error || "Invalid JSON" }; +} diff --git a/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts b/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts index 058f73e3..894ca6a6 100644 --- a/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts +++ b/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts @@ -122,7 +122,14 @@ import { import { taskv2NodeDefaultData } from "./nodes/Taskv2Node/types"; import { urlNodeDefaultData } from "./nodes/URLNode/types"; import { fileUploadNodeDefaultData } from "./nodes/FileUploadNode/types"; -import { httpRequestNodeDefaultData } from "./nodes/HttpRequestNode/types"; +import { + httpRequestNodeDefaultData, + isHttpRequestNode, +} from "./nodes/HttpRequestNode/types"; +import { + validateUrl, + validateJson, +} from "./nodes/HttpRequestNode/httpValidation"; import { printPageNodeDefaultData } from "./nodes/PrintPageNode/types"; export const NEW_NODE_LABEL_PREFIX = "block_"; @@ -3985,6 +3992,28 @@ function getWorkflowErrors(nodes: Array): Array { } }); + const httpRequestNodes = nodes.filter(isHttpRequestNode); + httpRequestNodes.forEach((node) => { + // Validate URL - required and must be valid format + const urlValidation = validateUrl(node.data.url); + if (!urlValidation.valid) { + errors.push(`${node.data.label}: ${urlValidation.message}`); + } + + // Validate JSON fields - optional but must be valid if provided + const jsonFields = [ + { value: node.data.headers, name: "Headers" }, + { value: node.data.body, name: "Body" }, + { value: node.data.files, name: "Files" }, + ]; + jsonFields.forEach(({ value, name }) => { + const result = validateJson(value); + if (!result.valid && result.message) { + errors.push(`${node.data.label}: ${name} is not valid JSON.`); + } + }); + }); + return errors; }