279 lines
9.2 KiB
Rust

use std::path::PathBuf;
use crate::error::{ErrorCode, LlamacppError, ServerResult};
#[cfg(windows)]
use jan_utils::path::get_short_path;
/// Validate that a binary path exists and is accessible
pub fn validate_binary_path(backend_path: &str) -> ServerResult<PathBuf> {
let server_path_buf = PathBuf::from(backend_path);
if !server_path_buf.exists() {
let err_msg = format!("Binary not found at {:?}", backend_path);
log::error!(
"Server binary not found at expected path: {:?}",
backend_path
);
return Err(LlamacppError::new(
ErrorCode::BinaryNotFound,
"The llama.cpp server binary could not be found.".into(),
Some(err_msg),
)
.into());
}
Ok(server_path_buf)
}
/// Validate model path exists and update args with platform-appropriate path format
pub fn validate_model_path(args: &mut Vec<String>) -> ServerResult<PathBuf> {
let model_path_index = args.iter().position(|arg| arg == "-m").ok_or_else(|| {
LlamacppError::new(
ErrorCode::ModelLoadFailed,
"Model path argument '-m' is missing.".into(),
None,
)
})?;
let model_path = args.get(model_path_index + 1).cloned().ok_or_else(|| {
LlamacppError::new(
ErrorCode::ModelLoadFailed,
"Model path was not provided after '-m' flag.".into(),
None,
)
})?;
let model_path_pb = PathBuf::from(&model_path);
if !model_path_pb.exists() {
let err_msg = format!(
"Invalid or inaccessible model path: {}",
model_path_pb.display()
);
log::error!("{}", &err_msg);
return Err(LlamacppError::new(
ErrorCode::ModelFileNotFound,
"The specified model file does not exist or is not accessible.".into(),
Some(err_msg),
)
.into());
}
// Update the path in args with appropriate format for the platform
#[cfg(windows)]
{
// use short path on Windows
if let Some(short) = get_short_path(&model_path_pb) {
args[model_path_index + 1] = short;
} else {
args[model_path_index + 1] = model_path_pb.display().to_string();
}
}
#[cfg(not(windows))]
{
args[model_path_index + 1] = model_path_pb.display().to_string();
}
Ok(model_path_pb)
}
/// Validate mmproj path exists and update args with platform-appropriate path format
pub fn validate_mmproj_path(args: &mut Vec<String>) -> ServerResult<Option<PathBuf>> {
let mmproj_path_index = match args.iter().position(|arg| arg == "--mmproj") {
Some(index) => index,
None => return Ok(None), // mmproj is optional
};
let mmproj_path = args.get(mmproj_path_index + 1).cloned().ok_or_else(|| {
LlamacppError::new(
ErrorCode::ModelLoadFailed,
"Mmproj path was not provided after '--mmproj' flag.".into(),
None,
)
})?;
let mmproj_path_pb = PathBuf::from(&mmproj_path);
if !mmproj_path_pb.exists() {
let err_msg = format!(
"Invalid or inaccessible mmproj path: {}",
mmproj_path_pb.display()
);
log::error!("{}", &err_msg);
return Err(LlamacppError::new(
ErrorCode::ModelFileNotFound,
"The specified mmproj file does not exist or is not accessible.".into(),
Some(err_msg),
)
.into());
}
#[cfg(windows)]
{
// use short path on Windows
if let Some(short) = get_short_path(&mmproj_path_pb) {
args[mmproj_path_index + 1] = short;
} else {
args[mmproj_path_index + 1] = mmproj_path_pb.display().to_string();
}
}
#[cfg(not(windows))]
{
args[mmproj_path_index + 1] = mmproj_path_pb.display().to_string();
}
Ok(Some(mmproj_path_pb))
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::NamedTempFile;
#[test]
fn test_validate_binary_path_existing() {
let temp_file = NamedTempFile::new().unwrap();
let path = temp_file.path().to_str().unwrap();
let result = validate_binary_path(path);
assert!(result.is_ok());
assert_eq!(result.unwrap(), PathBuf::from(path));
}
#[test]
fn test_validate_binary_path_nonexistent() {
let nonexistent_path = "/tmp/definitely_does_not_exist_123456789";
let result = validate_binary_path(nonexistent_path);
assert!(result.is_err());
}
#[test]
fn test_validate_model_path_valid() {
let temp_file = NamedTempFile::new().unwrap();
let path = temp_file.path().to_str().unwrap();
let mut args = vec!["-m".to_string(), path.to_string(), "--verbose".to_string()];
let result = validate_model_path(&mut args);
assert!(result.is_ok());
assert_eq!(result.unwrap(), PathBuf::from(path));
// Args should be updated with the path
#[cfg(windows)]
{
// On Windows, the path might be converted to short path format
// Just verify that the path in args[1] points to the same file
assert!(PathBuf::from(&args[1]).exists());
}
#[cfg(not(windows))]
{
assert_eq!(args[1], temp_file.path().display().to_string());
}
}
#[test]
fn test_validate_model_path_missing_flag() {
let mut args = vec!["--verbose".to_string(), "value".to_string()];
let result = validate_model_path(&mut args);
assert!(result.is_err());
}
#[test]
fn test_validate_model_path_missing_value() {
let mut args = vec!["-m".to_string()];
let result = validate_model_path(&mut args);
assert!(result.is_err());
}
#[test]
fn test_validate_model_path_nonexistent_file() {
let nonexistent_path = "/tmp/nonexistent_model_123456789.gguf";
let mut args = vec!["-m".to_string(), nonexistent_path.to_string()];
let result = validate_model_path(&mut args);
assert!(result.is_err());
}
#[test]
fn test_validate_mmproj_path_valid() {
let temp_file = NamedTempFile::new().unwrap();
let path = temp_file.path().to_str().unwrap();
let mut args = vec!["--mmproj".to_string(), path.to_string(), "--verbose".to_string()];
let result = validate_mmproj_path(&mut args);
assert!(result.is_ok());
assert!(result.unwrap().is_some());
// Args should be updated with the path
#[cfg(windows)]
{
// On Windows, the path might be converted to short path format
// Just verify that the path in args[1] points to the same file
assert!(PathBuf::from(&args[1]).exists());
}
#[cfg(not(windows))]
{
assert_eq!(args[1], temp_file.path().display().to_string());
}
}
#[test]
fn test_validate_mmproj_path_missing() {
let mut args = vec!["--verbose".to_string(), "value".to_string()];
let result = validate_mmproj_path(&mut args);
assert!(result.is_ok());
assert!(result.unwrap().is_none()); // mmproj is optional
}
#[test]
fn test_validate_mmproj_path_missing_value() {
let mut args = vec!["--mmproj".to_string()];
let result = validate_mmproj_path(&mut args);
assert!(result.is_err());
}
#[test]
fn test_validate_mmproj_path_nonexistent_file() {
let nonexistent_path = "/tmp/nonexistent_mmproj_123456789.gguf";
let mut args = vec!["--mmproj".to_string(), nonexistent_path.to_string()];
let result = validate_mmproj_path(&mut args);
assert!(result.is_err());
}
#[test]
fn test_validate_model_path_multiple_m_flags() {
let temp_file = NamedTempFile::new().unwrap();
let path = temp_file.path().to_str().unwrap();
// Multiple -m flags - should use the first one
let mut args = vec![
"-m".to_string(),
path.to_string(),
"--verbose".to_string(),
"-m".to_string(),
"another_path".to_string()
];
let result = validate_model_path(&mut args);
assert!(result.is_ok());
assert_eq!(result.unwrap(), PathBuf::from(path));
}
#[test]
fn test_validate_mmproj_path_multiple_flags() {
let temp_file = NamedTempFile::new().unwrap();
let path = temp_file.path().to_str().unwrap();
// Multiple --mmproj flags - should use the first one
let mut args = vec![
"--mmproj".to_string(),
path.to_string(),
"--verbose".to_string(),
"--mmproj".to_string(),
"another_path".to_string()
];
let result = validate_mmproj_path(&mut args);
assert!(result.is_ok());
let result_path = result.unwrap();
assert!(result_path.is_some());
assert_eq!(result_path.unwrap(), PathBuf::from(path));
}
}