Skip to main content

joy_core/model/
milestone.rs

1// Copyright (c) 2026 Joydev GmbH (joydev.com)
2// SPDX-License-Identifier: MIT
3
4use 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
28/// Generate a slug from a milestone title.
29pub 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}