Skip to main content

commit_wizard/engine/models/policy/
versioning.rs

1use crate::engine::models::policy::enforcement::BumpLevel;
2use crate::engine::models::runtime::ResolvedConfig;
3
4#[derive(Debug, Clone)]
5pub struct Version {
6    pub major: u64,
7    pub minor: u64,
8    pub patch: u64,
9}
10
11impl Version {
12    /// Parse a semantic version from a tag string with optional prefix.
13    /// Tries to parse with prefix first, then without prefix.
14    /// Examples: "v1.2.3" with prefix "v" -> Some(Version { 1, 2, 3 })
15    ///           "1.2.3" with prefix "v" -> Some(Version { 1, 2, 3 }) (fallback)
16    pub fn parse(tag_str: &str, tag_prefix: &str) -> Option<Self> {
17        // Try with prefix first
18        if let Some(version_part) = tag_str.strip_prefix(tag_prefix)
19            && let Some(version) = Self::parse_semver(version_part)
20        {
21            return Some(version);
22        }
23
24        // Fallback: try without prefix
25        Self::parse_semver(tag_str)
26    }
27
28    /// Parse semantic version from "major.minor.patch" string
29    fn parse_semver(version_str: &str) -> Option<Self> {
30        let parts: Vec<&str> = version_str.split('.').collect();
31
32        if parts.len() != 3 {
33            return None;
34        }
35
36        let major = parts[0].parse::<u64>().ok()?;
37        let minor = parts[1].parse::<u64>().ok()?;
38        let patch = parts[2].parse::<u64>().ok()?;
39
40        Some(Version {
41            major,
42            minor,
43            patch,
44        })
45    }
46
47    /// Format version as a semantic version string with tag prefix.
48    /// Examples: Version { 1, 2, 3 } with prefix "v" -> "v1.2.3"
49    pub fn format(&self, tag_prefix: &str) -> String {
50        format!("{}{}", tag_prefix, self.to_semver())
51    }
52
53    /// Format version as semantic version string without prefix.
54    /// Examples: Version { 1, 2, 3 } -> "1.2.3"
55    pub fn to_semver(&self) -> String {
56        format!("{}.{}.{}", self.major, self.minor, self.patch)
57    }
58
59    /// Increment version based on bump level.
60    /// - Major: increment major, reset minor and patch
61    /// - Minor: increment minor, reset patch
62    /// - Patch: increment patch
63    /// - None: return current version unchanged
64    pub fn with_bump(&self, bump: BumpLevel) -> Self {
65        match bump {
66            BumpLevel::Major => Version {
67                major: self.major + 1,
68                minor: 0,
69                patch: 0,
70            },
71            BumpLevel::Minor => Version {
72                major: self.major,
73                minor: self.minor + 1,
74                patch: 0,
75            },
76            BumpLevel::Patch => Version {
77                major: self.major,
78                minor: self.minor,
79                patch: self.patch + 1,
80            },
81            BumpLevel::None => self.clone(),
82        }
83    }
84}
85
86#[derive(Debug, Clone)]
87pub struct VersioningModel {
88    pub tag_prefix: String,
89    pub initial_version: Version,
90}
91
92impl Default for VersioningModel {
93    fn default() -> Self {
94        Self {
95            tag_prefix: "v".to_string(),
96            initial_version: Version {
97                major: 0,
98                minor: 1,
99                patch: 0,
100            },
101        }
102    }
103}
104
105impl VersioningModel {
106    pub fn from_config(config: &ResolvedConfig) -> Self {
107        let base = &config.base;
108
109        Self {
110            tag_prefix: base.versioning_tag_prefix(),
111            initial_version: Version {
112                major: 0,
113                minor: 1,
114                patch: 0,
115            },
116        }
117    }
118}