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 { WorkflowBlockParameterSelect } from "../WorkflowBlockParameterSelect";
|
||||||
import { CurlImportDialog } from "./CurlImportDialog";
|
import { CurlImportDialog } from "./CurlImportDialog";
|
||||||
import { QuickHeadersDialog } from "./QuickHeadersDialog";
|
import { QuickHeadersDialog } from "./QuickHeadersDialog";
|
||||||
import { MethodBadge, UrlValidator, RequestPreview } from "./HttpUtils";
|
import {
|
||||||
|
MethodBadge,
|
||||||
|
UrlValidator,
|
||||||
|
RequestPreview,
|
||||||
|
JsonValidator,
|
||||||
|
} from "./HttpUtils";
|
||||||
import { useRerender } from "@/hooks/useRerender";
|
import { useRerender } from "@/hooks/useRerender";
|
||||||
import { useRecordingStore } from "@/store/useRecordingStore";
|
import { useRecordingStore } from "@/store/useRecordingStore";
|
||||||
import { cn } from "@/util/utils";
|
import { cn } from "@/util/utils";
|
||||||
@@ -271,6 +276,7 @@ function HttpRequestNode({ id, data, type }: NodeProps<HttpRequestNodeType>) {
|
|||||||
minHeight="80px"
|
minHeight="80px"
|
||||||
maxHeight="160px"
|
maxHeight="160px"
|
||||||
/>
|
/>
|
||||||
|
<JsonValidator value={data.headers} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Body Section */}
|
{/* Body Section */}
|
||||||
@@ -312,6 +318,7 @@ function HttpRequestNode({ id, data, type }: NodeProps<HttpRequestNodeType>) {
|
|||||||
minHeight="100px"
|
minHeight="100px"
|
||||||
maxHeight="200px"
|
maxHeight="200px"
|
||||||
/>
|
/>
|
||||||
|
<JsonValidator value={data.body} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -333,6 +340,7 @@ function HttpRequestNode({ id, data, type }: NodeProps<HttpRequestNodeType>) {
|
|||||||
minHeight="80px"
|
minHeight="80px"
|
||||||
maxHeight="160px"
|
maxHeight="160px"
|
||||||
/>
|
/>
|
||||||
|
<JsonValidator value={data.files} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { useState } from "react";
|
|||||||
import { toast } from "@/components/ui/use-toast";
|
import { toast } from "@/components/ui/use-toast";
|
||||||
import { copyText } from "@/util/copyText";
|
import { copyText } from "@/util/copyText";
|
||||||
import { cn } from "@/util/utils";
|
import { cn } from "@/util/utils";
|
||||||
|
import { validateUrl, validateJson } from "./httpValidation";
|
||||||
|
|
||||||
// HTTP Method Badge Component
|
// HTTP Method Badge Component
|
||||||
export function MethodBadge({
|
export function MethodBadge({
|
||||||
@@ -56,21 +57,7 @@ export function MethodBadge({
|
|||||||
|
|
||||||
// URL Validation Component
|
// URL Validation Component
|
||||||
export function UrlValidator({ url }: { url: string }) {
|
export function UrlValidator({ url }: { url: string }) {
|
||||||
const isValidUrl = (urlString: string) => {
|
const validation = validateUrl(url);
|
||||||
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);
|
|
||||||
|
|
||||||
if (!url.trim()) return null;
|
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
|
// Copy to Curl Component
|
||||||
export function CopyToCurlButton({
|
export function CopyToCurlButton({
|
||||||
method,
|
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 { taskv2NodeDefaultData } from "./nodes/Taskv2Node/types";
|
||||||
import { urlNodeDefaultData } from "./nodes/URLNode/types";
|
import { urlNodeDefaultData } from "./nodes/URLNode/types";
|
||||||
import { fileUploadNodeDefaultData } from "./nodes/FileUploadNode/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";
|
import { printPageNodeDefaultData } from "./nodes/PrintPageNode/types";
|
||||||
|
|
||||||
export const NEW_NODE_LABEL_PREFIX = "block_";
|
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;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user