Add JSON and URL validation for HTTP Request Block (#4544)
This commit is contained in:
@@ -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>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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" };
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user