joy_core/model/
milestone.rs1use chrono::NaiveDate;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub struct Milestone {
9 pub id: String,
10 pub title: String,
11 #[serde(default, skip_serializing_if = "Option::is_none")]
12 pub date: Option<NaiveDate>,
13 #[serde(default, skip_serializing_if = "Option::is_none")]
14 pub description: Option<String>,
15}
16
17impl Milestone {
18 pub fn new(id: String, title: String) -> Self {
19 Self {
20 id,
21 title,
22 date: None,
23 description: None,
24 }
25 }
26}
27
28pub fn milestone_filename(id: &str, title: &str) -> String {
30 use crate::model::item::slugify;
31 format!("{}-{}.yaml", id, slugify(title))
32}
33
34#[cfg(test)]
35mod tests {
36 use super::*;
37
38 #[test]
39 fn milestone_roundtrip() {
40 let ms = Milestone {
41 id: "MS-01".into(),
42 title: "Beta Release".into(),
43 date: Some(NaiveDate::from_ymd_opt(2026, 6, 1).unwrap()),
44 description: Some("First public beta.".into()),
45 };
46
47 let yaml = serde_yaml_ng::to_string(&ms).unwrap();
48 let parsed: Milestone = serde_yaml_ng::from_str(&yaml).unwrap();
49 assert_eq!(ms, parsed);
50 }
51
52 #[test]
53 fn milestone_filename_basic() {
54 assert_eq!(
55 milestone_filename("MS-01", "Beta Release"),
56 "MS-01-beta-release.yaml"
57 );
58 }
59}