agentzero_tools/
url_validation.rs1use agentzero_core::common::url_policy::UrlAccessPolicy;
2use agentzero_core::common::util::parse_http_url_with_policy;
3use agentzero_core::{Tool, ToolContext, ToolResult};
4use anyhow::anyhow;
5use async_trait::async_trait;
6
7#[derive(Default)]
8pub struct UrlValidationTool {
9 url_policy: UrlAccessPolicy,
10}
11
12impl UrlValidationTool {
13 pub fn with_url_policy(mut self, policy: UrlAccessPolicy) -> Self {
14 self.url_policy = policy;
15 self
16 }
17}
18
19#[async_trait]
20impl Tool for UrlValidationTool {
21 fn name(&self) -> &'static str {
22 "url_validation"
23 }
24
25 fn description(&self) -> &'static str {
26 "Validate a URL against the access policy (private IPs, domain allowlist, etc.)."
27 }
28
29 fn input_schema(&self) -> Option<serde_json::Value> {
30 Some(serde_json::json!({
31 "type": "object",
32 "required": ["url"],
33 "properties": {
34 "url": {"type": "string", "description": "URL to validate"}
35 }
36 }))
37 }
38
39 async fn execute(&self, input: &str, _ctx: &ToolContext) -> anyhow::Result<ToolResult> {
40 let trimmed = input.trim();
41 if trimmed.is_empty() {
42 return Err(anyhow!("url input is required"));
43 }
44
45 let parsed = parse_http_url_with_policy(trimmed, &self.url_policy)?;
46
47 Ok(ToolResult {
48 output: parsed.to_string(),
49 })
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use super::UrlValidationTool;
56 use agentzero_core::{Tool, ToolContext};
57
58 #[tokio::test]
59 async fn url_validation_accepts_https_success_path() {
60 let tool = UrlValidationTool::default();
61 let result = tool
62 .execute(
63 "https://example.com/path?q=1",
64 &ToolContext::new(".".to_string()),
65 )
66 .await
67 .expect("validation should succeed");
68 assert!(result.output.starts_with("https://example.com/path"));
69 }
70
71 #[tokio::test]
72 async fn url_validation_rejects_unsupported_scheme_negative_path() {
73 let tool = UrlValidationTool::default();
74 let err = tool
75 .execute("file:///tmp/test.txt", &ToolContext::new(".".to_string()))
76 .await
77 .expect_err("unsupported scheme should fail");
78 assert!(err.to_string().contains("unsupported url scheme"));
79 }
80
81 #[tokio::test]
82 async fn url_validation_blocks_private_ip_negative_path() {
83 let tool = UrlValidationTool::default();
84 let err = tool
85 .execute(
86 "http://172.16.0.1/admin",
87 &ToolContext::new(".".to_string()),
88 )
89 .await
90 .expect_err("private IP should be blocked");
91 assert!(err.to_string().contains("URL access denied"));
92 }
93}