use crate::pack::PackIdentity;
use crate::{Error, Result};
use lazy_regex::regex;
use serde::Deserialize;
#[derive(Deserialize)]
pub struct PartialPackToml {
pub pack: Option<PartialPackInfo>,
}
#[derive(Deserialize)]
pub struct PartialPackInfo {
pub version: Option<String>,
pub namespace: Option<String>,
pub name: Option<String>,
}
#[derive(Debug, Clone)]
pub struct PackToml {
pub version: String,
pub namespace: String,
pub name: String,
}
pub(super) fn parse_validate_pack_toml(toml_content: &str, toml_path: &str) -> Result<PackToml> {
let partial_config: PartialPackToml = toml::from_str(toml_content)?;
let pack_info = match partial_config.pack {
Some(p) => p,
None => return Err(Error::custom(format!("Missing [pack] section in {}", toml_path))),
};
let version = match &pack_info.version {
Some(v) if !v.trim().is_empty() => v.clone(),
_ => return Err(Error::VersionMissing(toml_path.to_string())),
};
validate_version(&version, toml_path)?;
let namespace = match &pack_info.namespace {
Some(n) if !n.trim().is_empty() => n.clone(),
_ => return Err(Error::NamespaceMissing(toml_path.to_string())),
};
let name = match &pack_info.name {
Some(n) if !n.trim().is_empty() => n.clone(),
_ => return Err(Error::NameMissing(toml_path.to_string())),
};
validate_names(&namespace, &name, toml_path)?;
Ok(PackToml {
version,
namespace,
name,
})
}
pub(super) fn validate_version(version: &str, toml_path: &str) -> Result<()> {
let re = regex!(r"^(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z][\w-]*)(?:\.(\d+))?)?$");
if !re.is_match(version) {
return Err(Error::custom(format!(
"Invalid version format in {}. Version must follow semver format (e.g., 1.0.0, 1.0.0-alpha.1)",
toml_path
)));
}
Ok(())
}
pub(super) fn validate_names(namespace: &str, name: &str, _toml_path: &str) -> Result<()> {
PackIdentity::validate_namespace(namespace)?;
PackIdentity::validate_name(name)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use camino::Utf8PathBuf;
type Result<T> = core::result::Result<T, Box<dyn std::error::Error>>;
#[test]
fn test_packer_pack_toml_validate_simple() -> Result<()> {
let valid_toml = r#"
[pack]
version = "1.0.0"
namespace = "test"
name = "pack"
"#;
let toml_path = Utf8PathBuf::from("dummy/path/pack.toml");
let pack_toml = parse_validate_pack_toml(valid_toml, toml_path.as_str())?;
assert_eq!(pack_toml.version, "1.0.0");
assert_eq!(pack_toml.namespace, "test");
assert_eq!(pack_toml.name, "pack");
Ok(())
}
#[test]
fn test_packer_pack_toml_validate_missing_fields() -> Result<()> {
let toml_path = Utf8PathBuf::from("dummy/path/pack.toml");
let toml_missing_pack = r#"
version = "1.0.0"
namespace = "test"
name = "pack"
"#;
let result = parse_validate_pack_toml(toml_missing_pack, toml_path.as_str());
assert!(result.is_err());
let toml_missing_version = r#"
[pack]
namespace = "test"
name = "pack"
"#;
let result = parse_validate_pack_toml(toml_missing_version, toml_path.as_str());
assert!(matches!(result, Err(Error::VersionMissing(_))));
let toml_missing_namespace = r#"
[pack]
version = "1.0.0"
name = "pack"
"#;
let result = parse_validate_pack_toml(toml_missing_namespace, toml_path.as_str());
assert!(matches!(result, Err(Error::NamespaceMissing(_))));
let toml_missing_name = r#"
[pack]
version = "1.0.0"
namespace = "test"
"#;
let result = parse_validate_pack_toml(toml_missing_name, toml_path.as_str());
assert!(matches!(result, Err(Error::NameMissing(_))));
let toml_empty_values = r#"
[pack]
version = ""
namespace = "test"
name = "pack"
"#;
let result = parse_validate_pack_toml(toml_empty_values, toml_path.as_str());
assert!(matches!(result, Err(Error::VersionMissing(_))));
Ok(())
}
}