use super::identity::{ActionId, CommitSha, Version};
use crate::domain::file::actions::WorkflowAction;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum RefType {
#[serde(rename = "release")]
Release,
#[serde(rename = "tag")]
Tag,
#[serde(rename = "branch")]
Branch,
#[serde(rename = "commit")]
Commit,
}
impl fmt::Display for RefType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Release => write!(f, "release"),
Self::Tag => write!(f, "tag"),
Self::Branch => write!(f, "branch"),
Self::Commit => write!(f, "commit"),
}
}
}
impl RefType {
#[must_use]
pub fn parse(s: &str) -> Option<Self> {
match s {
"release" => Some(Self::Release),
"tag" => Some(Self::Tag),
"branch" => Some(Self::Branch),
"commit" => Some(Self::Commit),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParsedRef {
Ref(Version),
Sha(CommitSha),
Pinned { sha: CommitSha, comment: Version },
}
impl ParsedRef {
#[must_use]
pub const fn pin_sha(&self) -> Option<&CommitSha> {
match self {
Self::Sha(sha) | Self::Pinned { sha, .. } => Some(sha),
Self::Ref(_) => None,
}
}
#[must_use]
pub fn label(&self) -> &str {
match self {
Self::Ref(v) | Self::Pinned { comment: v, .. } => v.as_str(),
Self::Sha(sha) => sha.as_str(),
}
}
#[must_use]
pub fn label_version(&self) -> Version {
Version::from(self.label())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UsesRef {
pub action_name: String,
pub uses_ref: String,
pub comment: Option<String>,
}
impl UsesRef {
#[must_use]
pub const fn new(action_name: String, uses_ref: String, comment: Option<String>) -> Self {
Self {
action_name,
uses_ref,
comment,
}
}
#[must_use]
pub fn interpret(&self) -> WorkflowAction {
let is_sha = CommitSha::is_valid(&self.uses_ref);
let reference = match (self.comment.as_ref(), is_sha) {
(Some(comment), true) => ParsedRef::Pinned {
sha: CommitSha::from(self.uses_ref.as_str()),
comment: Version::normalized(comment),
},
(Some(comment), false) => ParsedRef::Ref(Version::normalized(comment)),
(None, true) => ParsedRef::Sha(CommitSha::from(self.uses_ref.as_str())),
(None, false) => ParsedRef::Ref(Version::from(self.uses_ref.as_str())),
};
WorkflowAction {
id: ActionId::from(self.action_name.as_str()),
reference,
}
}
}
#[cfg(test)]
mod tests {
use super::{CommitSha, ParsedRef, RefType, UsesRef, Version};
#[test]
fn ref_type_display() {
assert_eq!(RefType::Release.to_string(), "release");
assert_eq!(RefType::Tag.to_string(), "tag");
assert_eq!(RefType::Branch.to_string(), "branch");
assert_eq!(RefType::Commit.to_string(), "commit");
}
#[test]
fn ref_type_parse() {
assert_eq!(RefType::parse("release"), Some(RefType::Release));
assert_eq!(RefType::parse("tag"), Some(RefType::Tag));
assert_eq!(RefType::parse("branch"), Some(RefType::Branch));
assert_eq!(RefType::parse("commit"), Some(RefType::Commit));
assert_eq!(RefType::parse("unknown"), None);
}
#[test]
fn ref_type_equality() {
assert_eq!(RefType::Release, RefType::Release);
assert_ne!(RefType::Release, RefType::Tag);
}
#[test]
fn uses_ref_interpret_tag_only() {
let uses_ref = UsesRef::new("actions/checkout".to_owned(), "v4".to_owned(), None);
let interpreted = uses_ref.interpret();
assert_eq!(interpreted.id.as_str(), "actions/checkout");
assert_eq!(interpreted.reference, ParsedRef::Ref(Version::from("v4")));
assert!(interpreted.reference.pin_sha().is_none());
}
#[test]
fn uses_ref_interpret_sha_with_comment() {
let uses_ref = UsesRef::new(
"actions/checkout".to_owned(),
"abc123def456789012345678901234567890abcd".to_owned(),
Some("v4".to_owned()),
);
let interpreted = uses_ref.interpret();
assert_eq!(interpreted.id.as_str(), "actions/checkout");
assert_eq!(
interpreted.reference,
ParsedRef::Pinned {
sha: CommitSha::from("abc123def456789012345678901234567890abcd"),
comment: Version::from("v4"),
}
);
assert_eq!(
interpreted.reference.pin_sha().map(CommitSha::as_str),
Some("abc123def456789012345678901234567890abcd")
);
}
#[test]
fn uses_ref_interpret_normalizes_version() {
let uses_ref = UsesRef::new(
"actions/checkout".to_owned(),
"abc123def456789012345678901234567890abcd".to_owned(),
Some("4".to_owned()), );
let interpreted = uses_ref.interpret();
assert_eq!(interpreted.reference.label(), "v4"); }
#[test]
fn uses_ref_interpret_sha_without_comment() {
let uses_ref = UsesRef::new(
"actions/checkout".to_owned(),
"abc123def456789012345678901234567890abcd".to_owned(),
None,
);
let interpreted = uses_ref.interpret();
assert_eq!(
interpreted.reference,
ParsedRef::Sha(CommitSha::from("abc123def456789012345678901234567890abcd"))
);
assert_eq!(
interpreted.reference.label(),
"abc123def456789012345678901234567890abcd"
);
}
#[test]
fn uses_ref_interpret_short_ref_with_comment() {
let uses_ref = UsesRef::new(
"actions/checkout".to_owned(),
"abc123".to_owned(),
Some("v4".to_owned()),
);
let interpreted = uses_ref.interpret();
assert_eq!(interpreted.reference, ParsedRef::Ref(Version::from("v4")));
assert!(interpreted.reference.pin_sha().is_none()); }
#[test]
fn parsed_ref_pin_sha_and_label() {
let tag = ParsedRef::Ref(Version::from("v4"));
assert!(tag.pin_sha().is_none());
assert_eq!(tag.label(), "v4");
let sha = ParsedRef::Sha(CommitSha::from("abc123def456789012345678901234567890abcd"));
assert_eq!(
sha.pin_sha().map(CommitSha::as_str),
Some("abc123def456789012345678901234567890abcd")
);
assert_eq!(sha.label(), "abc123def456789012345678901234567890abcd");
let pinned = ParsedRef::Pinned {
sha: CommitSha::from("abc123def456789012345678901234567890abcd"),
comment: Version::from("v4"),
};
assert_eq!(
pinned.pin_sha().map(CommitSha::as_str),
Some("abc123def456789012345678901234567890abcd")
);
assert_eq!(pinned.label(), "v4");
assert_eq!(pinned.label_version(), Version::from("v4"));
}
}