use chrono::{DateTime, Utc};
use std::collections::BTreeMap;
use crate::error::KanbusError;
use crate::models::{IssueData, ProjectConfiguration};
pub fn get_workflow_for_issue_type<'a>(
configuration: &'a ProjectConfiguration,
issue_type: &str,
) -> Result<&'a BTreeMap<String, Vec<String>>, KanbusError> {
if let Some(workflow) = configuration.workflows.get(issue_type) {
return Ok(workflow);
}
configuration
.workflows
.get("default")
.ok_or_else(|| KanbusError::Configuration("default workflow not defined".to_string()))
}
pub fn validate_status_transition(
configuration: &ProjectConfiguration,
issue_type: &str,
current_status: &str,
new_status: &str,
) -> Result<(), KanbusError> {
let workflow = get_workflow_for_issue_type(configuration, issue_type)?;
let allowed_transitions = workflow
.get(current_status)
.map(Vec::as_slice)
.unwrap_or(&[]);
if !allowed_transitions
.iter()
.any(|status| status == new_status)
{
return Err(KanbusError::InvalidTransition(format!(
"invalid transition from '{current_status}' to '{new_status}' for type '{issue_type}'"
)));
}
Ok(())
}
pub fn validate_status_value(
configuration: &ProjectConfiguration,
_issue_type: &str,
status: &str,
) -> Result<(), KanbusError> {
let valid_statuses: std::collections::BTreeSet<&str> = configuration
.statuses
.iter()
.map(|entry| entry.key.as_str())
.collect();
if !valid_statuses.contains(status) {
return Err(KanbusError::InvalidTransition("unknown status".to_string()));
}
Ok(())
}
pub fn apply_transition_side_effects(
issue: &IssueData,
new_status: &str,
current_utc_time: DateTime<Utc>,
) -> IssueData {
let mut updated_issue = issue.clone();
if new_status == "closed" {
updated_issue.closed_at = Some(current_utc_time);
} else if issue.status == "closed" && new_status != "closed" {
updated_issue.closed_at = None;
}
updated_issue
}