use crate::config::schema::ArgumentType;
use crate::error::{ParseError, Result};
use std::path::PathBuf;
pub fn parse_value(value: &str, arg_type: ArgumentType) -> Result<String> {
match arg_type {
ArgumentType::String => {
Ok(value.to_string())
}
ArgumentType::Integer => {
parse_integer(value)?;
Ok(value.to_string())
}
ArgumentType::Float => {
parse_float(value)?;
Ok(value.to_string())
}
ArgumentType::Bool => {
parse_bool(value)?;
Ok(value.to_string())
}
ArgumentType::Path => {
parse_path(value)?;
Ok(value.to_string())
}
}
}
pub fn parse_integer(value: &str) -> Result<i64> {
value.parse::<i64>().map_err(|e| {
ParseError::TypeParseError {
arg_name: "value".to_string(),
expected_type: "integer".to_string(),
value: value.to_string(),
details: Some(e.to_string()),
}
.into()
})
}
pub fn parse_float(value: &str) -> Result<f64> {
value.parse::<f64>().map_err(|e| {
ParseError::TypeParseError {
arg_name: "value".to_string(),
expected_type: "float".to_string(),
value: value.to_string(),
details: Some(e.to_string()),
}
.into()
})
}
pub fn parse_bool(value: &str) -> Result<bool> {
let normalized = value.trim().to_lowercase();
match normalized.as_str() {
"true" | "yes" | "y" | "1" | "on" => Ok(true),
"false" | "no" | "n" | "0" | "off" => Ok(false),
_ => Err(ParseError::TypeParseError {
arg_name: "value".to_string(),
expected_type: "bool".to_string(),
value: value.to_string(),
details: Some("expected true/false, yes/no, 1/0, on/off".to_string()),
}
.into()),
}
}
pub fn parse_path(value: &str) -> Result<PathBuf> {
let path = PathBuf::from(value);
Ok(path)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_value_string() {
let result = parse_value("hello world", ArgumentType::String).unwrap();
assert_eq!(result, "hello world");
let result = parse_value("", ArgumentType::String).unwrap();
assert_eq!(result, "");
}
#[test]
fn test_parse_value_integer_valid() {
assert!(parse_value("42", ArgumentType::Integer).is_ok());
assert!(parse_value("-123", ArgumentType::Integer).is_ok());
assert!(parse_value("0", ArgumentType::Integer).is_ok());
}
#[test]
fn test_parse_value_integer_invalid() {
assert!(parse_value("abc", ArgumentType::Integer).is_err());
assert!(parse_value("12.5", ArgumentType::Integer).is_err());
assert!(parse_value("", ArgumentType::Integer).is_err());
}
#[test]
fn test_parse_value_float_valid() {
assert!(parse_value("3.14", ArgumentType::Float).is_ok());
assert!(parse_value("42", ArgumentType::Float).is_ok());
assert!(parse_value("-1.5", ArgumentType::Float).is_ok());
}
#[test]
fn test_parse_value_float_invalid() {
assert!(parse_value("abc", ArgumentType::Float).is_err());
assert!(parse_value("", ArgumentType::Float).is_err());
}
#[test]
fn test_parse_value_bool_valid() {
assert!(parse_value("true", ArgumentType::Bool).is_ok());
assert!(parse_value("false", ArgumentType::Bool).is_ok());
assert!(parse_value("yes", ArgumentType::Bool).is_ok());
assert!(parse_value("no", ArgumentType::Bool).is_ok());
}
#[test]
fn test_parse_value_bool_invalid() {
assert!(parse_value("maybe", ArgumentType::Bool).is_err());
assert!(parse_value("2", ArgumentType::Bool).is_err());
}
#[test]
fn test_parse_value_path_valid() {
assert!(parse_value("/tmp/file", ArgumentType::Path).is_ok());
assert!(parse_value("./relative", ArgumentType::Path).is_ok());
}
#[test]
fn test_parse_integer_positive() {
assert_eq!(parse_integer("42").unwrap(), 42);
assert_eq!(parse_integer("0").unwrap(), 0);
assert_eq!(parse_integer("999999").unwrap(), 999999);
}
#[test]
fn test_parse_integer_negative() {
assert_eq!(parse_integer("-42").unwrap(), -42);
assert_eq!(parse_integer("-1").unwrap(), -1);
}
#[test]
fn test_parse_integer_invalid() {
assert!(parse_integer("abc").is_err());
assert!(parse_integer("12.5").is_err());
assert!(parse_integer("").is_err());
assert!(parse_integer("12a").is_err());
}
#[test]
fn test_parse_integer_overflow() {
let too_large = "9223372036854775808";
assert!(parse_integer(too_large).is_err());
}
#[test]
fn test_parse_float_integer() {
assert_eq!(parse_float("42").unwrap(), 42.0);
assert_eq!(parse_float("0").unwrap(), 0.0);
}
#[test]
fn test_parse_float_decimal() {
assert_eq!(parse_float("3.14").unwrap(), 3.14);
assert_eq!(parse_float("-1.5").unwrap(), -1.5);
assert_eq!(parse_float("0.5").unwrap(), 0.5);
}
#[test]
fn test_parse_float_scientific() {
assert_eq!(parse_float("1e3").unwrap(), 1000.0);
assert_eq!(parse_float("1.5e2").unwrap(), 150.0);
assert_eq!(parse_float("1e-3").unwrap(), 0.001);
}
#[test]
fn test_parse_float_special_values() {
assert!(parse_float("inf").unwrap().is_infinite());
assert!(parse_float("-inf").unwrap().is_infinite());
assert!(parse_float("NaN").unwrap().is_nan());
}
#[test]
fn test_parse_float_invalid() {
assert!(parse_float("abc").is_err());
assert!(parse_float("").is_err());
assert!(parse_float("12.34.56").is_err());
}
#[test]
fn test_parse_bool_true_variants() {
assert_eq!(parse_bool("true").unwrap(), true);
assert_eq!(parse_bool("True").unwrap(), true);
assert_eq!(parse_bool("TRUE").unwrap(), true);
assert_eq!(parse_bool("yes").unwrap(), true);
assert_eq!(parse_bool("YES").unwrap(), true);
assert_eq!(parse_bool("y").unwrap(), true);
assert_eq!(parse_bool("Y").unwrap(), true);
assert_eq!(parse_bool("1").unwrap(), true);
assert_eq!(parse_bool("on").unwrap(), true);
assert_eq!(parse_bool("ON").unwrap(), true);
}
#[test]
fn test_parse_bool_false_variants() {
assert_eq!(parse_bool("false").unwrap(), false);
assert_eq!(parse_bool("False").unwrap(), false);
assert_eq!(parse_bool("FALSE").unwrap(), false);
assert_eq!(parse_bool("no").unwrap(), false);
assert_eq!(parse_bool("NO").unwrap(), false);
assert_eq!(parse_bool("n").unwrap(), false);
assert_eq!(parse_bool("N").unwrap(), false);
assert_eq!(parse_bool("0").unwrap(), false);
assert_eq!(parse_bool("off").unwrap(), false);
assert_eq!(parse_bool("OFF").unwrap(), false);
}
#[test]
fn test_parse_bool_with_whitespace() {
assert_eq!(parse_bool(" true ").unwrap(), true);
assert_eq!(parse_bool("\tfalse\n").unwrap(), false);
}
#[test]
fn test_parse_bool_invalid() {
assert!(parse_bool("maybe").is_err());
assert!(parse_bool("2").is_err());
assert!(parse_bool("").is_err());
assert!(parse_bool("tr").is_err());
}
#[test]
fn test_parse_path_unix_style() {
let path = parse_path("/tmp/file.txt").unwrap();
assert_eq!(path.to_str().unwrap(), "/tmp/file.txt");
}
#[test]
fn test_parse_path_relative() {
let path = parse_path("./relative/path").unwrap();
assert!(path.to_str().unwrap().contains("relative"));
}
#[test]
fn test_parse_path_windows_style() {
let path = parse_path("C:\\Windows\\System32").unwrap();
assert!(path.to_str().is_some());
}
#[test]
fn test_parse_path_empty() {
let path = parse_path("").unwrap();
assert_eq!(path, PathBuf::from(""));
}
#[test]
fn test_parse_path_with_spaces() {
let path = parse_path("/path/with spaces/file.txt").unwrap();
assert!(path.to_str().unwrap().contains("spaces"));
}
#[test]
fn test_all_types_roundtrip() {
let test_cases = vec![
("hello", ArgumentType::String),
("42", ArgumentType::Integer),
("3.14", ArgumentType::Float),
("true", ArgumentType::Bool),
("/tmp/file", ArgumentType::Path),
];
for (value, arg_type) in test_cases {
let result = parse_value(value, arg_type);
assert!(
result.is_ok(),
"Failed to parse '{}' as {:?}",
value,
arg_type
);
assert_eq!(result.unwrap(), value);
}
}
#[test]
fn test_error_messages_contain_details() {
let result = parse_integer("not_a_number");
assert!(result.is_err());
let error = result.unwrap_err();
let error_msg = format!("{}", error);
assert!(error_msg.contains("integer"));
assert!(error_msg.contains("not_a_number"));
}
}