use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Issue {
pub id: String,
pub identifier: String,
pub title: String,
pub description: Option<String>,
pub priority: Option<i32>,
pub state: String,
pub branch_name: Option<String>,
pub url: Option<String>,
pub labels: Vec<String>,
pub blocked_by: Vec<BlockerRef>,
pub created_at: Option<DateTime<Utc>>,
pub updated_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockerRef {
pub id: Option<String>,
pub identifier: Option<String>,
pub state: Option<String>,
}
impl Issue {
pub fn workspace_key(&self) -> String {
self.identifier
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || c == '.' || c == '_' || c == '-' {
c
} else {
'_'
}
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn workspace_key_sanitizes_identifier() {
let issue = Issue {
id: "id1".into(),
identifier: "ABC-123".into(),
title: "Test".into(),
description: None,
priority: Some(1),
state: "Todo".into(),
branch_name: None,
url: None,
labels: vec![],
blocked_by: vec![],
created_at: None,
updated_at: None,
};
assert_eq!(issue.workspace_key(), "ABC-123");
}
#[test]
fn workspace_key_replaces_special_chars() {
let issue = Issue {
id: "id2".into(),
identifier: "PROJ/feat#42".into(),
title: "Test".into(),
description: None,
priority: None,
state: "Todo".into(),
branch_name: None,
url: None,
labels: vec![],
blocked_by: vec![],
created_at: None,
updated_at: None,
};
assert_eq!(issue.workspace_key(), "PROJ_feat_42");
}
}