1use governor_core::domain::version::SemanticVersion;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct GitTag {
9 pub name: String,
11 pub target: String,
13 pub message: Option<String>,
15 pub tagger: Option<TaggerInfo>,
17 pub version: Option<SemanticVersion>,
19}
20
21impl GitTag {
22 #[must_use]
24 pub fn new(name: String, target: String) -> Self {
25 let version = Self::parse_version(&name);
26
27 Self {
28 name,
29 target,
30 message: None,
31 tagger: None,
32 version,
33 }
34 }
35
36 fn parse_version(name: &str) -> Option<SemanticVersion> {
38 let version_str = name.trim_start_matches('v');
39 SemanticVersion::parse(version_str).ok()
40 }
41
42 #[must_use]
44 pub fn with_message(mut self, message: String) -> Self {
45 self.message = Some(message);
46 self
47 }
48
49 #[must_use]
51 pub fn with_tagger(mut self, tagger: TaggerInfo) -> Self {
52 self.tagger = Some(tagger);
53 self
54 }
55
56 #[must_use]
58 pub const fn is_version_tag(&self) -> bool {
59 self.version.is_some()
60 }
61
62 #[must_use]
64 pub const fn version(&self) -> Option<&SemanticVersion> {
65 self.version.as_ref()
66 }
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct TaggerInfo {
72 pub name: String,
74 pub email: String,
76 pub timestamp: i64,
78}
79
80impl TaggerInfo {
81 #[must_use]
83 pub const fn new(name: String, email: String, timestamp: i64) -> Self {
84 Self {
85 name,
86 email,
87 timestamp,
88 }
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn test_new() {
98 let tag = GitTag::new("v1.0.0".to_string(), "abc123".to_string());
99 assert_eq!(tag.name, "v1.0.0");
100 assert_eq!(tag.target, "abc123");
101 assert!(tag.message.is_none());
102 assert!(tag.tagger.is_none());
103 }
104
105 #[test]
106 fn test_parse_version_with_v() {
107 let tag = GitTag::new("v1.2.3".to_string(), "abc123".to_string());
108 assert!(tag.version.is_some());
109 assert_eq!(tag.version.as_ref().unwrap().to_string(), "1.2.3");
110 }
111
112 #[test]
113 fn test_parse_version_without_v() {
114 let tag = GitTag::new("2.0.0".to_string(), "abc123".to_string());
115 assert!(tag.version.is_some());
116 assert_eq!(tag.version.as_ref().unwrap().to_string(), "2.0.0");
117 }
118
119 #[test]
120 fn test_parse_version_invalid() {
121 let tag = GitTag::new("latest".to_string(), "abc123".to_string());
122 assert!(tag.version.is_none());
123 }
124
125 #[test]
126 fn test_parse_version_multiple_v() {
127 let tag = GitTag::new("vv1.0.0".to_string(), "abc123".to_string());
128 assert!(tag.version.is_some());
130 assert_eq!(tag.version.as_ref().unwrap().to_string(), "1.0.0");
131 }
132
133 #[test]
134 fn test_with_message() {
135 let tag = GitTag::new("v1.0.0".to_string(), "abc123".to_string())
136 .with_message("Release".to_string());
137 assert_eq!(tag.message, Some("Release".to_string()));
138 }
139
140 #[test]
141 fn test_with_tagger() {
142 let tagger = TaggerInfo::new("John".to_string(), "john@test.com".to_string(), 12345);
143 let tag = GitTag::new("v1.0.0".to_string(), "abc123".to_string()).with_tagger(tagger);
144 assert!(tag.tagger.is_some());
145 assert_eq!(tag.tagger.as_ref().unwrap().name, "John");
146 }
147
148 #[test]
149 fn test_is_version_tag_true() {
150 let tag = GitTag::new("v1.0.0".to_string(), "abc123".to_string());
151 assert!(tag.is_version_tag());
152 }
153
154 #[test]
155 fn test_is_version_tag_false() {
156 let tag = GitTag::new("stable".to_string(), "abc123".to_string());
157 assert!(!tag.is_version_tag());
158 }
159
160 #[test]
161 fn test_version_some() {
162 let tag = GitTag::new("v3.2.1".to_string(), "abc123".to_string());
163 assert!(tag.version().is_some());
164 assert_eq!(tag.version().unwrap().to_string(), "3.2.1");
165 }
166
167 #[test]
168 fn test_version_none() {
169 let tag = GitTag::new("latest".to_string(), "abc123".to_string());
170 assert!(tag.version().is_none());
171 }
172
173 #[test]
174 fn test_tagger_info_new() {
175 let tagger = TaggerInfo::new(
176 "Alice".to_string(),
177 "alice@test.com".to_string(),
178 9_876_543_210,
179 );
180 assert_eq!(tagger.name, "Alice");
181 assert_eq!(tagger.email, "alice@test.com");
182 assert_eq!(tagger.timestamp, 9_876_543_210);
183 }
184
185 #[test]
186 fn test_chained_builders() {
187 let tagger = TaggerInfo::new("Bob".to_string(), "bob@test.com".to_string(), 1_111_111_111);
188 let tag = GitTag::new("v2.0.0".to_string(), "def456".to_string())
189 .with_message("Version 2.0.0".to_string())
190 .with_tagger(tagger);
191
192 assert_eq!(tag.name, "v2.0.0");
193 assert_eq!(tag.message, Some("Version 2.0.0".to_string()));
194 assert!(tag.tagger.is_some());
195 assert_eq!(tag.tagger.as_ref().unwrap().name, "Bob");
196 }
197
198 #[test]
199 fn test_serialize() {
200 let tag = GitTag::new("v1.0.0".to_string(), "abc123".to_string());
201 let json = serde_json::to_string(&tag);
202 assert!(json.is_ok());
203 }
204
205 #[test]
206 fn test_deserialize() {
207 let json =
208 r#"{"name":"v1.0.0","target":"abc123","message":null,"tagger":null,"version":null}"#;
209 let tag: Result<GitTag, _> = serde_json::from_str(json);
210 assert!(tag.is_ok());
211 let tag = tag.unwrap();
212 assert_eq!(tag.name, "v1.0.0");
213 assert_eq!(tag.target, "abc123");
214 }
215}