Skip to main content

solverforge_service/
util.rs

1use crate::error::{ServiceError, ServiceResult};
2use log::debug;
3use std::env;
4use std::net::TcpListener;
5use std::path::{Path, PathBuf};
6
7pub fn find_available_port() -> ServiceResult<u16> {
8    let listener = TcpListener::bind("127.0.0.1:0")?;
9    let port = listener.local_addr()?.port();
10    Ok(port)
11}
12
13pub fn find_java(java_home: Option<&Path>) -> ServiceResult<PathBuf> {
14    // 1. Explicit java_home parameter takes priority
15    if let Some(home) = java_home {
16        let java_path = home.join("bin").join("java");
17        debug!("Checking explicit java_home: {}", java_path.display());
18        if java_path.exists() {
19            return Ok(java_path);
20        }
21        return Err(ServiceError::JavaNotFound(format!(
22            "java not found in JAVA_HOME: {}",
23            home.display()
24        )));
25    }
26
27    // 2. Check PATH first (respects user's system configuration)
28    if let Ok(output) = std::process::Command::new("which").arg("java").output() {
29        if output.status.success() {
30            let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
31            debug!("Found java via PATH: {}", path);
32            if !path.is_empty() {
33                return Ok(PathBuf::from(path));
34            }
35        }
36    }
37
38    // 3. Fall back to JAVA_HOME env var
39    if let Ok(home) = env::var("JAVA_HOME") {
40        let java_path = PathBuf::from(&home).join("bin").join("java");
41        debug!(
42            "Checking JAVA_HOME env: {} -> {}",
43            home,
44            java_path.display()
45        );
46        if java_path.exists() {
47            return Ok(java_path);
48        }
49    }
50
51    Err(ServiceError::JavaNotFound(
52        "java not found in PATH or JAVA_HOME".to_string(),
53    ))
54}
55
56pub fn find_maven() -> ServiceResult<PathBuf> {
57    if let Ok(output) = std::process::Command::new("which").arg("mvn").output() {
58        if output.status.success() {
59            let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
60            if !path.is_empty() {
61                // Canonicalize the path to resolve symlinks.
62                // Maven's startup script uses $0 to derive MAVEN_HOME, so calling
63                // it via a symlink like /bin/mvn (which points to ../share/maven/bin/mvn)
64                // can fail if /bin doesn't have a /share sibling directory.
65                let path_buf = PathBuf::from(&path);
66                return Ok(path_buf.canonicalize().unwrap_or(path_buf));
67            }
68        }
69    }
70
71    Err(ServiceError::MavenNotFound(
72        "mvn not found in PATH".to_string(),
73    ))
74}
75
76pub fn get_cache_dir() -> PathBuf {
77    dirs::cache_dir()
78        .unwrap_or_else(|| PathBuf::from("/tmp"))
79        .join("solverforge")
80}
81
82pub fn find_submodule_dir() -> ServiceResult<PathBuf> {
83    // 1. Check SOLVERFORGE_SERVICE_DIR environment variable
84    if let Ok(dir) = env::var("SOLVERFORGE_SERVICE_DIR") {
85        let path = PathBuf::from(&dir);
86        if path.is_dir() && path.join("pom.xml").exists() {
87            debug!("Using SOLVERFORGE_SERVICE_DIR: {}", dir);
88            return Ok(path);
89        }
90    }
91
92    // 2. Walk up from current directory
93    let mut current = env::current_dir()?;
94    loop {
95        let candidate = current.join("solverforge-wasm-service");
96        if candidate.is_dir() && candidate.join("pom.xml").exists() {
97            return Ok(candidate);
98        }
99
100        if !current.pop() {
101            break;
102        }
103    }
104
105    // 3. Check CARGO_MANIFEST_DIR (for development builds)
106    if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
107        let workspace_root = PathBuf::from(manifest_dir)
108            .parent()
109            .map(|p| p.to_path_buf())
110            .unwrap_or_default();
111        let candidate = workspace_root.join("solverforge-wasm-service");
112        if candidate.is_dir() && candidate.join("pom.xml").exists() {
113            return Ok(candidate);
114        }
115    }
116
117    Err(ServiceError::SubmoduleNotFound(
118        "solverforge-wasm-service submodule not found. Set SOLVERFORGE_SERVICE_DIR or ensure JAR is available on Maven Central.".to_string(),
119    ))
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_find_available_port() {
128        let port = find_available_port().unwrap();
129        assert!(port > 0);
130
131        let port2 = find_available_port().unwrap();
132        assert!(port2 > 0);
133    }
134
135    #[test]
136    fn test_get_cache_dir() {
137        let cache = get_cache_dir();
138        assert!(cache.to_string_lossy().contains("solverforge"));
139    }
140
141    #[test]
142    fn test_find_java_with_invalid_home() {
143        let result = find_java(Some(Path::new("/nonexistent/path")));
144        assert!(result.is_err());
145    }
146}