use regex::Regex;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResolvedTarget {
pub module_path: String,
pub qualname: String,
}
pub fn resolve_target(target: &str) -> Result<ResolvedTarget, String> {
let last_colon = target.rfind(':').ok_or_else(|| {
format!("Invalid target format: \"{target}\". Expected \"module_path:qualname\".")
})?;
let module_path = &target[..last_colon];
let qualname = &target[last_colon + 1..];
if module_path.is_empty() {
return Err(format!(
"Invalid target format: \"{target}\". Module path is empty."
));
}
if qualname.is_empty() {
return Err(format!(
"Invalid target format: \"{target}\". Qualified name is empty."
));
}
let ident_re = Regex::new(r"^[a-zA-Z_][a-zA-Z0-9_]*$").unwrap();
if !ident_re.is_match(qualname) {
return Err(format!(
"Invalid qualname \"{qualname}\" in target \"{target}\". \
Must be a valid identifier."
));
}
Ok(ResolvedTarget {
module_path: module_path.to_string(),
qualname: qualname.to_string(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resolve_target_python_style() {
let result = resolve_target("my_package.my_module:MyClass").unwrap();
assert_eq!(result.module_path, "my_package.my_module");
assert_eq!(result.qualname, "MyClass");
}
#[test]
fn test_resolve_target_rust_style() {
let result = resolve_target("my_crate::handlers::task:create_task").unwrap();
assert_eq!(result.module_path, "my_crate::handlers::task");
assert_eq!(result.qualname, "create_task");
}
#[test]
fn test_resolve_target_simple() {
let result = resolve_target("app:handler").unwrap();
assert_eq!(result.module_path, "app");
assert_eq!(result.qualname, "handler");
}
#[test]
fn test_resolve_target_typescript_style() {
let result = resolve_target("./handlers/task:createTask").unwrap();
assert_eq!(result.module_path, "./handlers/task");
assert_eq!(result.qualname, "createTask");
}
#[test]
fn test_resolve_target_no_colon() {
let result = resolve_target("no_colon_here");
assert!(result.is_err());
assert!(result.unwrap_err().contains("Invalid target format"));
}
#[test]
fn test_resolve_target_empty_module() {
let result = resolve_target(":qualname");
assert!(result.is_err());
assert!(result.unwrap_err().contains("Module path is empty"));
}
#[test]
fn test_resolve_target_empty_qualname() {
let result = resolve_target("module:");
assert!(result.is_err());
assert!(result.unwrap_err().contains("Qualified name is empty"));
}
#[test]
fn test_resolve_target_invalid_qualname() {
let result = resolve_target("module:123invalid");
assert!(result.is_err());
assert!(result.unwrap_err().contains("Must be a valid identifier"));
}
#[test]
fn test_resolve_target_qualname_with_spaces() {
let result = resolve_target("module:has spaces");
assert!(result.is_err());
}
#[test]
fn test_resolve_target_node_prefix() {
let result = resolve_target("node:path:join").unwrap();
assert_eq!(result.module_path, "node:path");
assert_eq!(result.qualname, "join");
}
#[test]
fn test_resolve_target_underscore_qualname() {
let result = resolve_target("mod:_private_func").unwrap();
assert_eq!(result.qualname, "_private_func");
}
}