use serde::{Deserialize, Serialize}; use crate::cob; use crate::cob::common::Label; use crate::cob::issue; use crate::cob::store::HistoryAction; use crate::cob::thread; use crate::cob::{store, ActorId, TypeName}; use crate::prelude::ReadRepository; /// Issue operation. pub type Op = cob::Op; /// Error type. pub type Error = issue::Error; /// Issue state. Accumulates [`Action`]. #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct Issue(issue::Issue); impl From for issue::Issue { fn from(issue: Issue) -> issue::Issue { issue.0 } } impl store::FromHistory for Issue { type Action = Action; type Error = Error; fn type_name() -> &'static TypeName { &issue::TYPENAME } fn validate(&self) -> Result<(), Self::Error> { if self.0.title.is_empty() { return Err(Error::Validate("title is empty")); } if self.0.thread.validate().is_err() { return Err(Error::Validate("invalid thread")); } Ok(()) } fn apply(&mut self, op: Op, repo: &R) -> Result<(), Error> { let issue = &mut self.0; for action in op.actions { match action { Action::Assign { add, remove } => { for assignee in add { issue.assignees.insert(assignee.into()); } for assignee in remove { issue.assignees.remove(&assignee.into()); } } Action::Edit { title } => { issue.title = title; } Action::Lifecycle { state } => { issue.state = state; } Action::Tag { add, remove } => { for tag in add { issue.labels.insert(tag); } for tag in remove { issue.labels.remove(&tag); } } Action::Thread { action } => { issue.thread.apply( cob::Op::new( op.id, action, op.author, op.timestamp, op.identity, op.manifest.clone(), ), repo, )?; } } } Ok(()) } } /// Issue operation. #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "camelCase")] pub enum Action { Assign { add: Vec, remove: Vec, }, Edit { title: String, }, Lifecycle { state: issue::State, }, Tag { add: Vec