pulseengine_mcp_cli/
utils.rs

1//! Utility functions for CLI operations
2
3use crate::CliError;
4use std::path::Path;
5
6/// Parse Cargo.toml to extract package information
7#[cfg(feature = "cli")]
8pub fn parse_cargo_toml<P: AsRef<Path>>(path: P) -> Result<CargoToml, CliError> {
9    use std::fs;
10
11    let content = fs::read_to_string(path)
12        .map_err(|e| CliError::configuration(format!("Failed to read Cargo.toml: {e}")))?;
13
14    let cargo_toml: CargoToml = toml::from_str(&content)
15        .map_err(|e| CliError::configuration(format!("Failed to parse Cargo.toml: {e}")))?;
16
17    Ok(cargo_toml)
18}
19
20/// Find Cargo.toml in current directory or parent directories
21pub fn find_cargo_toml() -> Result<std::path::PathBuf, CliError> {
22    let mut current_dir = std::env::current_dir()
23        .map_err(|e| CliError::configuration(format!("Failed to get current directory: {e}")))?;
24
25    loop {
26        let cargo_toml = current_dir.join("Cargo.toml");
27        if cargo_toml.exists() {
28            return Ok(cargo_toml);
29        }
30
31        if !current_dir.pop() {
32            break;
33        }
34    }
35
36    Err(CliError::configuration(
37        "Cargo.toml not found in current directory or parents",
38    ))
39}
40
41/// Parsed Cargo.toml structure
42#[cfg(feature = "cli")]
43#[derive(Debug, serde::Deserialize)]
44pub struct CargoToml {
45    pub package: Option<Package>,
46}
47
48#[cfg(feature = "cli")]
49#[derive(Debug, serde::Deserialize)]
50pub struct Package {
51    pub name: Option<String>,
52    pub version: Option<String>,
53    pub description: Option<String>,
54    pub authors: Option<Vec<String>>,
55}
56
57#[cfg(feature = "cli")]
58impl CargoToml {
59    pub fn get_name(&self) -> Option<&str> {
60        self.package.as_ref()?.name.as_deref()
61    }
62
63    pub fn get_version(&self) -> Option<&str> {
64        self.package.as_ref()?.version.as_deref()
65    }
66
67    pub fn get_description(&self) -> Option<&str> {
68        self.package.as_ref()?.description.as_deref()
69    }
70}
71
72/// Validate configuration values
73pub mod validation {
74    use crate::CliError;
75
76    /// Validate port number
77    pub fn validate_port(port: u16) -> Result<(), CliError> {
78        if port == 0 {
79            return Err(CliError::configuration("Port cannot be 0"));
80        }
81        if port < 1024 {
82            tracing::warn!(
83                "Using privileged port {}, this may require elevated permissions",
84                port
85            );
86        }
87        Ok(())
88    }
89
90    /// Validate URL format
91    pub fn validate_url(url: &str) -> Result<(), CliError> {
92        url::Url::parse(url)
93            .map_err(|e| CliError::configuration(format!("Invalid URL '{url}': {e}")))?;
94        Ok(())
95    }
96
97    /// Validate file path exists
98    pub fn validate_file_exists(path: &str) -> Result<(), CliError> {
99        if !std::path::Path::new(path).exists() {
100            return Err(CliError::configuration(format!(
101                "File does not exist: {path}"
102            )));
103        }
104        Ok(())
105    }
106
107    /// Validate directory exists
108    pub fn validate_dir_exists(path: &str) -> Result<(), CliError> {
109        let path = std::path::Path::new(path);
110        if !path.exists() {
111            return Err(CliError::configuration(format!(
112                "Directory does not exist: {}",
113                path.display()
114            )));
115        }
116        if !path.is_dir() {
117            return Err(CliError::configuration(format!(
118                "Path is not a directory: {}",
119                path.display()
120            )));
121        }
122        Ok(())
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn test_validate_port() {
132        use validation::*;
133
134        assert!(validate_port(8080).is_ok());
135        assert!(validate_port(80).is_ok()); // Should warn but not error
136        assert!(validate_port(0).is_err());
137    }
138
139    #[test]
140    fn test_validate_url() {
141        use validation::*;
142
143        assert!(validate_url("https://example.com").is_ok());
144        assert!(validate_url("http://localhost:8080").is_ok());
145        assert!(validate_url("invalid-url").is_err());
146    }
147}