Add JSON and URL validation for HTTP Request Block (#4544)

This commit is contained in:
Celal Zamanoglu
2026-01-26 17:54:43 +03:00
committed by GitHub
parent 1b7a7b9eba
commit 770d3977fa
4 changed files with 102 additions and 17 deletions

View File

@@ -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<HttpRequestNodeType>) {
minHeight="80px"
maxHeight="160px"
/>
<JsonValidator value={data.headers} />
</div>
{/* Body Section */}
@@ -312,6 +318,7 @@ function HttpRequestNode({ id, data, type }: NodeProps<HttpRequestNodeType>) {
minHeight="100px"
maxHeight="200px"
/>
<JsonValidator value={data.body} />
</div>
)}
@@ -333,6 +340,7 @@ function HttpRequestNode({ id, data, type }: NodeProps<HttpRequestNodeType>) {
minHeight="80px"
maxHeight="160px"
/>
<JsonValidator value={data.files} />
</div>
)}

View File

@@ -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 (
<div
className={cn(
"flex items-center gap-1 text-xs",
validation.valid
? "text-green-600 dark:text-green-400"
: "text-red-600 dark:text-red-400",
)}
>
{validation.valid ? (
<CheckCircledIcon className="h-3 w-3" />
) : (
<ExclamationTriangleIcon className="h-3 w-3" />
)}
<span>{validation.message}</span>
</div>
);
}
// Copy to Curl Component
export function CopyToCurlButton({
method,

View File

@@ -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" };
}

View File

@@ -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<AppNode>): Array<string> {
}
});
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;
}