ultrafast_mcp_core/utils/
uri.rs

1use crate::error::{MCPError, MCPResult, ResourceError};
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5/// A simple URI wrapper for MCP resources
6#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub struct Uri(String);
8
9impl Uri {
10    /// Create a new URI from a string
11    pub fn new(uri: impl Into<String>) -> Self {
12        Self(uri.into())
13    }
14
15    /// Get the URI as a string slice
16    pub fn as_str(&self) -> &str {
17        &self.0
18    }
19
20    /// Check if this is a file URI
21    pub fn is_file(&self) -> bool {
22        self.0.starts_with("file://")
23    }
24
25    /// Check if this is an HTTP/HTTPS URI
26    pub fn is_http(&self) -> bool {
27        self.0.starts_with("http://") || self.0.starts_with("https://")
28    }
29
30    /// Get the scheme component of the URI
31    pub fn scheme(&self) -> Option<&str> {
32        self.0.split("://").next()
33    }
34
35    /// Validate the URI format
36    pub fn validate(&self) -> MCPResult<()> {
37        if self.0.is_empty() {
38            return Err(MCPError::Resource(ResourceError::InvalidUri(
39                "URI cannot be empty".to_string(),
40            )));
41        }
42
43        // Basic validation - must have a scheme or be a relative path
44        if !self.0.contains("://") && (self.0.starts_with('/') || !self.0.starts_with('/')) {
45            return Ok(());
46        }
47
48        // Must have valid scheme
49        if let Some(scheme) = self.scheme() {
50            if scheme.is_empty()
51                || !scheme
52                    .chars()
53                    .all(|c| c.is_ascii_alphanumeric() || c == '+' || c == '-' || c == '.')
54            {
55                return Err(MCPError::Resource(ResourceError::InvalidUri(format!(
56                    "Invalid URI scheme: {scheme}"
57                ))));
58            }
59        }
60
61        Ok(())
62    }
63}
64
65impl fmt::Display for Uri {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        write!(f, "{}", self.0)
68    }
69}
70
71impl From<String> for Uri {
72    fn from(s: String) -> Self {
73        Uri::new(s)
74    }
75}
76
77impl From<&str> for Uri {
78    fn from(s: &str) -> Self {
79        Uri::new(s)
80    }
81}
82
83impl AsRef<str> for Uri {
84    fn as_ref(&self) -> &str {
85        &self.0
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn test_uri_creation() {
95        let uri = Uri::new("file:///path/to/file.txt");
96        assert_eq!(uri.as_str(), "file:///path/to/file.txt");
97    }
98
99    #[test]
100    fn test_uri_scheme() {
101        let file_uri = Uri::new("file:///path/to/file.txt");
102        assert_eq!(file_uri.scheme(), Some("file"));
103        assert!(file_uri.is_file());
104
105        let http_uri = Uri::new("https://example.com/path");
106        assert_eq!(http_uri.scheme(), Some("https"));
107        assert!(http_uri.is_http());
108    }
109
110    #[test]
111    fn test_uri_validation() {
112        assert!(Uri::new("file:///path").validate().is_ok());
113        assert!(Uri::new("https://example.com").validate().is_ok());
114        assert!(Uri::new("/absolute/path").validate().is_ok());
115        assert!(Uri::new("relative/path").validate().is_ok());
116        assert!(Uri::new("").validate().is_err());
117    }
118}