use super::identity::{ActionId, CommitDate, CommitSha, Repository, Version};
use super::uses_ref::RefType;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Commit {
pub sha: CommitSha,
pub repository: Repository,
pub ref_type: Option<RefType>,
pub date: CommitDate,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[expect(
clippy::module_name_repetitions,
reason = "ResolvedRef is the canonical domain name for a resolved reference"
)]
pub enum ResolvedRef {
Tag(Version),
Branch(Version),
Commit,
}
impl ResolvedRef {
#[must_use]
pub fn from_stored(version: Version, ref_type: Option<&RefType>) -> Self {
match ref_type {
Some(RefType::Commit) => Self::Commit,
Some(RefType::Branch) => Self::Branch(version),
_ => Self::Tag(version),
}
}
#[must_use]
pub fn tag(&self) -> Option<&Version> {
match self {
Self::Tag(v) => Some(v),
Self::Branch(_) | Self::Commit => None,
}
}
#[must_use]
pub fn annotation(&self) -> Option<&Version> {
match self {
Self::Tag(v) | Self::Branch(v) => Some(v),
Self::Commit => None,
}
}
#[must_use]
pub fn label<'ref_>(&'ref_ self, sha: &'ref_ CommitSha) -> &'ref_ str {
match self {
Self::Tag(v) | Self::Branch(v) => v.as_str(),
Self::Commit => sha.as_str(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Resolved {
pub reference: ResolvedRef,
pub commit: Commit,
}
#[derive(Debug, Clone)]
#[expect(
clippy::module_name_repetitions,
reason = "ResolvedAction is the canonical domain name for workflow-output actions"
)]
pub struct ResolvedAction {
pub id: ActionId,
pub sha: CommitSha,
pub version: Option<Version>,
}
#[cfg(test)]
mod tests {
use super::{
Commit, CommitDate, CommitSha, RefType, Repository, Resolved, ResolvedRef, Version,
};
#[test]
fn resolved_holds_reference_and_commit() {
let resolved = Resolved {
reference: ResolvedRef::Tag(Version::from("v4.2.1")),
commit: Commit {
sha: CommitSha::from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
repository: Repository::from("actions/checkout"),
ref_type: Some(RefType::Tag),
date: CommitDate::from("2026-01-01T00:00:00Z"),
},
};
assert_eq!(
resolved.reference.tag().map(Version::as_str),
Some("v4.2.1")
);
assert_eq!(
resolved.commit.sha,
CommitSha::from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
);
}
#[test]
fn only_tag_is_range_constrainable() {
assert!(ResolvedRef::Tag(Version::from("v6.0.2")).tag().is_some());
assert!(ResolvedRef::Branch(Version::from("main")).tag().is_none());
assert!(ResolvedRef::Commit.tag().is_none());
}
#[test]
fn commit_has_no_annotation_but_tag_and_branch_do() {
assert_eq!(
ResolvedRef::Tag(Version::from("v4"))
.annotation()
.map(Version::as_str),
Some("v4")
);
assert_eq!(
ResolvedRef::Branch(Version::from("main"))
.annotation()
.map(Version::as_str),
Some("main")
);
assert!(ResolvedRef::Commit.annotation().is_none());
}
#[test]
fn commit_labels_round_trip_through_sha() {
let sha = CommitSha::from("6d1e696000000000000000000000000000000000");
assert_eq!(ResolvedRef::Commit.label(&sha), sha.as_str());
assert_eq!(
ResolvedRef::Tag(Version::from("v4.2.1")).label(&sha),
"v4.2.1"
);
}
#[test]
fn from_stored_maps_ref_type_to_kind() {
let v = Version::from("v6.0.2");
assert_eq!(
ResolvedRef::from_stored(v.clone(), Some(&RefType::Commit)),
ResolvedRef::Commit
);
assert_eq!(
ResolvedRef::from_stored(Version::from("main"), Some(&RefType::Branch)),
ResolvedRef::Branch(Version::from("main"))
);
assert_eq!(
ResolvedRef::from_stored(v.clone(), Some(&RefType::Tag)),
ResolvedRef::Tag(v.clone())
);
assert_eq!(
ResolvedRef::from_stored(v.clone(), Some(&RefType::Release)),
ResolvedRef::Tag(v.clone())
);
assert_eq!(
ResolvedRef::from_stored(v.clone(), None),
ResolvedRef::Tag(v)
);
}
}