use crate::core::dependency::{CiProvider, DependencyName, DependencyRef};
use regex::Regex;
use serde::Serialize;
use std::path::PathBuf;
use std::sync::LazyLock;
#[derive(Debug, Clone, Default)]
pub struct UpdateTask {
pub path: PathBuf,
pub start: usize,
pub end: usize,
pub line: usize,
pub column: usize,
pub action: DependencyName,
pub current_tag: Option<String>,
pub comment: Option<String>,
pub key: String,
pub provider: CiProvider,
}
static VERSION_COMMENT_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^#\s*(v\d[a-zA-Z0-9.\-_]*|main|\d[a-zA-Z0-9.\-_]*)\s*")
.expect("Failed to compile VERSION_COMMENT_REGEX")
});
impl UpdateTask {
pub fn logical_tag(&self) -> Option<String> {
let tag = self.current_tag.as_ref()?;
let is_sha = (tag.len() == 40 && tag.chars().all(|c| c.is_ascii_hexdigit()))
|| tag.starts_with("sha256:");
if is_sha {
if let Some(comment) = &self.comment {
if let Some(captures) = VERSION_COMMENT_REGEX.captures(comment) {
if let Some(m) = captures.get(1) {
return Some(m.as_str().to_string());
}
}
}
}
Some(tag.clone())
}
}
#[derive(Debug, Serialize, Clone)]
pub struct UpdateResult {
#[serde(skip)]
pub task: UpdateTask,
pub action: DependencyName,
pub path: PathBuf,
pub old_tag: Option<String>,
pub new_sha: DependencyRef,
pub new_tag: Option<String>,
}
#[derive(Serialize)]
pub struct JsonOutput {
pub updates: Vec<UpdateResult>,
}
#[derive(Debug, Serialize, Clone)]
pub struct UnpinnedDependency {
pub path: PathBuf,
pub action: DependencyName,
pub tag: Option<String>,
pub line: usize,
pub column: usize,
}
#[derive(Debug, Serialize, Clone, Default)]
pub struct VerificationResult {
pub unpinned: Vec<UnpinnedDependency>,
}
impl VerificationResult {
pub fn is_success(&self) -> bool {
self.unpinned.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_verification_result_success() {
let res = VerificationResult::default();
assert!(res.is_success());
}
#[test]
fn test_verification_result_failure() {
let mut res = VerificationResult::default();
res.unpinned.push(UnpinnedDependency {
path: PathBuf::from("f.yml"),
action: "a/b".into(),
tag: Some("v1".into()),
line: 1,
column: 1,
});
assert!(!res.is_success());
}
#[test]
fn test_update_result_serialization() {
let res = UpdateResult {
task: UpdateTask::default(), action: "a/b".into(),
path: PathBuf::from("f.yml"),
old_tag: Some("v1".into()),
new_sha: DependencyRef::GitSha("hash".into()),
new_tag: Some("v1".into()),
};
let json = serde_json::to_string(&res).unwrap();
assert!(!json.contains("task"));
assert!(json.contains("\"action\":\"a/b\""));
}
#[test]
fn test_logical_tag() {
let task = UpdateTask {
current_tag: Some("v3.1.2".to_string()),
comment: None,
..Default::default()
};
assert_eq!(task.logical_tag(), Some("v3.1.2".to_string()));
let task = UpdateTask {
current_tag: Some("de0fac2e4500dabe0009e67214ff5f5447ce83dd".to_string()),
comment: None,
..Default::default()
};
assert_eq!(
task.logical_tag(),
Some("de0fac2e4500dabe0009e67214ff5f5447ce83dd".to_string())
);
let task = UpdateTask {
current_tag: Some("de0fac2e4500dabe0009e67214ff5f5447ce83dd".to_string()),
comment: Some("# v6.0.2".to_string()),
..Default::default()
};
assert_eq!(task.logical_tag(), Some("v6.0.2".to_string()));
let task = UpdateTask {
current_tag: Some("de0fac2e4500dabe0009e67214ff5f5447ce83dd".to_string()),
comment: Some("# v6.0.2 # keep me".to_string()),
..Default::default()
};
assert_eq!(task.logical_tag(), Some("v6.0.2".to_string()));
}
}