use semver::VersionReq;
use crate::error::{AgmError, ErrorCode, ErrorLocation};
use crate::model::imports::ImportEntry;
#[derive(Debug, Clone)]
pub struct ValidatedImport {
pub entry: ImportEntry,
pub version_req: Option<semver::VersionReq>,
}
impl ValidatedImport {
#[must_use]
pub fn package(&self) -> &str {
&self.entry.package
}
#[must_use]
pub fn matches_version(&self, version: &semver::Version) -> bool {
match &self.version_req {
Some(req) => req.matches(version),
None => true,
}
}
}
pub fn parse_version_constraint(constraint: &str) -> Result<VersionReq, AgmError> {
let trimmed = constraint.trim();
semver::VersionReq::parse(trimmed).map_err(|e| {
AgmError::new(
ErrorCode::I002,
format!("Invalid version constraint: `{constraint}` ({e})"),
ErrorLocation::default(),
)
})
}
pub fn validate_import(entry: &ImportEntry) -> Result<ValidatedImport, AgmError> {
match &entry.version_constraint {
None => Ok(ValidatedImport {
entry: entry.clone(),
version_req: None,
}),
Some(constraint) => {
let req = parse_version_constraint(constraint)?;
Ok(ValidatedImport {
entry: entry.clone(),
version_req: Some(req),
})
}
}
}
pub fn validate_all_imports(imports: &[ImportEntry]) -> (Vec<ValidatedImport>, Vec<AgmError>) {
let mut validated = Vec::new();
let mut errors = Vec::new();
for entry in imports {
match validate_import(entry) {
Ok(v) => validated.push(v),
Err(e) => errors.push(e),
}
}
(validated, errors)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::ErrorCode;
use crate::model::imports::ImportEntry;
#[test]
fn test_parse_version_constraint_caret_returns_req() {
let req = parse_version_constraint("^1.0.0").unwrap();
assert!(req.matches(&semver::Version::parse("1.2.3").unwrap()));
assert!(!req.matches(&semver::Version::parse("2.0.0").unwrap()));
}
#[test]
fn test_parse_version_constraint_tilde_returns_req() {
let req = parse_version_constraint("~1.2.0").unwrap();
assert!(req.matches(&semver::Version::parse("1.2.5").unwrap()));
assert!(!req.matches(&semver::Version::parse("1.3.0").unwrap()));
}
#[test]
fn test_parse_version_constraint_exact_returns_req() {
let req = parse_version_constraint("=2.0.0").unwrap();
assert!(req.matches(&semver::Version::parse("2.0.0").unwrap()));
assert!(!req.matches(&semver::Version::parse("2.0.1").unwrap()));
}
#[test]
fn test_parse_version_constraint_wildcard_returns_req() {
let req = parse_version_constraint("1.*").unwrap();
assert!(req.matches(&semver::Version::parse("1.0.0").unwrap()));
assert!(req.matches(&semver::Version::parse("1.9.9").unwrap()));
assert!(!req.matches(&semver::Version::parse("2.0.0").unwrap()));
}
#[test]
fn test_parse_version_constraint_invalid_returns_error() {
let err = parse_version_constraint("not_semver").unwrap_err();
assert_eq!(err.code, ErrorCode::I002);
}
#[test]
fn test_validate_import_with_constraint_returns_validated() {
let entry = ImportEntry::new("shared.security".to_owned(), Some("^1.0.0".to_owned()));
let result = validate_import(&entry).unwrap();
assert_eq!(result.package(), "shared.security");
assert!(result.version_req.is_some());
}
#[test]
fn test_validate_import_without_constraint_returns_none_req() {
let entry = ImportEntry::new("shared.http".to_owned(), None);
let result = validate_import(&entry).unwrap();
assert_eq!(result.package(), "shared.http");
assert!(result.version_req.is_none());
}
#[test]
fn test_validate_import_invalid_constraint_returns_error() {
let entry = ImportEntry::new("pkg".to_owned(), Some("bogus".to_owned()));
let err = validate_import(&entry).unwrap_err();
assert_eq!(err.code, ErrorCode::I002);
}
#[test]
fn test_validate_all_imports_mixed_returns_partial() {
let imports = vec![
ImportEntry::new("shared.security".to_owned(), Some("^1.0.0".to_owned())),
ImportEntry::new("bad.pkg".to_owned(), Some("bogus".to_owned())),
];
let (validated, errors) = validate_all_imports(&imports);
assert_eq!(validated.len(), 1);
assert_eq!(errors.len(), 1);
assert_eq!(validated[0].package(), "shared.security");
assert_eq!(errors[0].code, ErrorCode::I002);
}
#[test]
fn test_matches_version_with_caret_matching_returns_true() {
let entry = ImportEntry::new("pkg".to_owned(), Some("^1.0.0".to_owned()));
let validated = validate_import(&entry).unwrap();
let version = semver::Version::parse("1.5.0").unwrap();
assert!(validated.matches_version(&version));
}
#[test]
fn test_matches_version_with_caret_not_matching_returns_false() {
let entry = ImportEntry::new("pkg".to_owned(), Some("^1.0.0".to_owned()));
let validated = validate_import(&entry).unwrap();
let version = semver::Version::parse("2.0.0").unwrap();
assert!(!validated.matches_version(&version));
}
#[test]
fn test_matches_version_none_constraint_returns_true() {
let entry = ImportEntry::new("pkg".to_owned(), None);
let validated = validate_import(&entry).unwrap();
let version = semver::Version::parse("99.99.99").unwrap();
assert!(validated.matches_version(&version));
}
}