use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Dependency {
pub issue_id: String,
pub depends_on_id: String,
#[serde(rename = "type")]
pub dep_type: DependencyType,
pub created_at: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_by: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thread_id: Option<String>,
}
impl Dependency {
pub fn blocks(issue_id: impl Into<String>, depends_on_id: impl Into<String>) -> Self {
Self {
issue_id: issue_id.into(),
depends_on_id: depends_on_id.into(),
dep_type: DependencyType::Blocks,
created_at: Utc::now(),
created_by: None,
metadata: None,
thread_id: None,
}
}
pub fn parent_child(child_id: impl Into<String>, parent_id: impl Into<String>) -> Self {
Self {
issue_id: child_id.into(),
depends_on_id: parent_id.into(),
dep_type: DependencyType::ParentChild,
created_at: Utc::now(),
created_by: None,
metadata: None,
thread_id: None,
}
}
pub fn with_creator(mut self, creator: impl Into<String>) -> Self {
self.created_by = Some(creator.into());
self
}
pub fn with_metadata(mut self, metadata: impl Into<String>) -> Self {
self.metadata = Some(metadata.into());
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum DependencyType {
#[default]
Blocks,
ParentChild,
ConditionalBlocks,
WaitsFor,
Related,
DiscoveredFrom,
RepliesTo,
RelatesTo,
Duplicates,
Supersedes,
AuthoredBy,
AssignedTo,
ApprovedBy,
Attests,
Tracks,
Until,
CausedBy,
Validates,
DelegatedFrom,
}
impl DependencyType {
pub fn all() -> &'static [DependencyType] {
&[
DependencyType::Blocks,
DependencyType::ParentChild,
DependencyType::ConditionalBlocks,
DependencyType::WaitsFor,
DependencyType::Related,
DependencyType::DiscoveredFrom,
DependencyType::RepliesTo,
DependencyType::RelatesTo,
DependencyType::Duplicates,
DependencyType::Supersedes,
DependencyType::AuthoredBy,
DependencyType::AssignedTo,
DependencyType::ApprovedBy,
DependencyType::Attests,
DependencyType::Tracks,
DependencyType::Until,
DependencyType::CausedBy,
DependencyType::Validates,
DependencyType::DelegatedFrom,
]
}
pub fn is_blocking(&self) -> bool {
matches!(
self,
DependencyType::Blocks
| DependencyType::ParentChild
| DependencyType::ConditionalBlocks
| DependencyType::WaitsFor
)
}
pub fn check_cycles(&self) -> bool {
!matches!(self, DependencyType::RelatesTo)
}
pub fn as_str(&self) -> &'static str {
match self {
DependencyType::Blocks => "blocks",
DependencyType::ParentChild => "parent_child",
DependencyType::ConditionalBlocks => "conditional_blocks",
DependencyType::WaitsFor => "waits_for",
DependencyType::Related => "related",
DependencyType::DiscoveredFrom => "discovered_from",
DependencyType::RepliesTo => "replies_to",
DependencyType::RelatesTo => "relates_to",
DependencyType::Duplicates => "duplicates",
DependencyType::Supersedes => "supersedes",
DependencyType::AuthoredBy => "authored_by",
DependencyType::AssignedTo => "assigned_to",
DependencyType::ApprovedBy => "approved_by",
DependencyType::Attests => "attests",
DependencyType::Tracks => "tracks",
DependencyType::Until => "until",
DependencyType::CausedBy => "caused_by",
DependencyType::Validates => "validates",
DependencyType::DelegatedFrom => "delegated_from",
}
}
}
impl fmt::Display for DependencyType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl FromStr for DependencyType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().replace('-', "_").as_str() {
"blocks" => Ok(DependencyType::Blocks),
"parent_child" => Ok(DependencyType::ParentChild),
"conditional_blocks" => Ok(DependencyType::ConditionalBlocks),
"waits_for" => Ok(DependencyType::WaitsFor),
"related" => Ok(DependencyType::Related),
"discovered_from" => Ok(DependencyType::DiscoveredFrom),
"replies_to" => Ok(DependencyType::RepliesTo),
"relates_to" => Ok(DependencyType::RelatesTo),
"duplicates" => Ok(DependencyType::Duplicates),
"supersedes" => Ok(DependencyType::Supersedes),
"authored_by" => Ok(DependencyType::AuthoredBy),
"assigned_to" => Ok(DependencyType::AssignedTo),
"approved_by" => Ok(DependencyType::ApprovedBy),
"attests" => Ok(DependencyType::Attests),
"tracks" => Ok(DependencyType::Tracks),
"until" => Ok(DependencyType::Until),
"caused_by" => Ok(DependencyType::CausedBy),
"validates" => Ok(DependencyType::Validates),
"delegated_from" => Ok(DependencyType::DelegatedFrom),
_ => Err(format!("unknown dependency type: {}", s)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dependency_type_roundtrip() {
for dep_type in DependencyType::all() {
let s = dep_type.as_str();
let parsed: DependencyType = s.parse().unwrap();
assert_eq!(*dep_type, parsed);
}
}
#[test]
fn test_blocking_types() {
assert!(DependencyType::Blocks.is_blocking());
assert!(DependencyType::ParentChild.is_blocking());
assert!(!DependencyType::RelatesTo.is_blocking());
assert!(!DependencyType::Related.is_blocking());
}
}