semver_common/models/
commit.rs1use crate::models::Alert;
2use chrono::{DateTime, FixedOffset};
3use derive_getters::Getters;
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use std::fmt::{self, Display, Formatter};
6
7static COMMIT_TIME_FORMAT: &str = "%a %b %d %H:%M:%S %Y %z";
8static ALTERNATE_TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S %z";
9
10mod datetime_ser {
11 use serde::de;
12
13 use super::*;
14
15 pub fn serialize<S>(ext: &DateTime<FixedOffset>, serializer: S) -> Result<S::Ok, S::Error>
16 where
17 S: Serializer,
18 {
19 serializer.serialize_str(&ext.to_string())
20 }
21
22 pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<FixedOffset>, D::Error>
23 where
24 D: Deserializer<'de>,
25 {
26 let s = String::deserialize(deserializer)?;
27 let dt = match DateTime::parse_from_str(&s, COMMIT_TIME_FORMAT) {
28 Ok(v) => v,
29 Err(_) => match DateTime::parse_from_str(&s, ALTERNATE_TIME_FORMAT) {
30 Ok(v) => v,
31 Err(e) => return Err(de::Error::custom(e.to_string())),
32 },
33 };
34 Ok(dt)
35 }
36}
37
38#[derive(Serialize, Deserialize, Clone, Debug, Getters)]
39pub struct Commit {
40 id: String,
41 author: String,
42
43 #[serde(with = "datetime_ser")]
44 timestamp: DateTime<FixedOffset>,
45
46 message: String,
47}
48
49impl Ord for Commit {
50 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
51 if self.message > other.message {
52 return std::cmp::Ordering::Greater;
53 } else if self.message < other.message {
54 return std::cmp::Ordering::Less;
55 }
56 std::cmp::Ordering::Equal
57 }
58}
59
60impl Eq for Commit {}
61
62impl PartialOrd for Commit {
63 fn ge(&self, other: &Self) -> bool {
64 self.message >= other.message
65 }
66
67 fn gt(&self, other: &Self) -> bool {
68 self.message > other.message
69 }
70
71 fn le(&self, other: &Self) -> bool {
72 self.message <= other.message
73 }
74
75 fn lt(&self, other: &Self) -> bool {
76 self.message < other.message
77 }
78
79 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
80 Some(self.cmp(other))
81 }
82}
83
84impl PartialEq for Commit {
85 fn eq(&self, other: &Self) -> bool {
86 self.message == other.message
87 }
88}
89
90impl Display for Commit {
91 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
92 writeln!(f, "{}", self.message)
93 }
94}
95
96impl Commit {
97 pub fn new(id: &str, author: &str, timestamp: DateTime<FixedOffset>, message: &str) -> Self {
98 Commit {
99 id: id.to_string(),
100 author: author.to_string(),
101 timestamp,
102 message: message.to_string(),
103 }
104 }
105
106 pub fn new_from_str(
108 id: &str,
109 author: &str,
110 timestamp: &str,
111 message: &str,
112 ) -> Result<Self, Alert> {
113 let parsed_timestamp = DateTime::parse_from_str(timestamp, COMMIT_TIME_FORMAT)?;
114 Ok(Commit::new(id, author, parsed_timestamp, message))
115 }
116
117 pub fn new_from_commit(commit: String) -> Result<Self, Alert> {
144 let lines: Vec<&str> = commit.split("\n").collect();
145 if lines.len() > 3 {
146 let id_line: (&str, &str) = lines[0].split_once(" ").unwrap_or((lines[0], ""));
147 let commit_id = id_line.0.trim();
148 let author_line: (&str, &str) = lines[1]
149 .split_once(":")
150 .ok_or("Could not parse author line of commit.")?;
151 let author = author_line.1.trim();
152 let date_line: (&str, &str) = lines[2]
153 .split_once(":")
154 .ok_or("Could not parse date line of commit.")?;
155 let date = date_line.1.trim();
156 let commit_end_line: usize = lines.len() - 1;
157 let commit_message_untrimmed = lines[4..commit_end_line].join("\n");
158 let commit_message = commit_message_untrimmed.trim();
159 let object = Commit::new_from_str(commit_id, author, date, commit_message)?;
160 return Ok(object);
161 }
162 Err(Alert::from("Commit is not valid"))
163 }
164
165 pub fn msg(&self) -> &str {
166 &self.message
167 }
168}
169#[cfg(test)]
170mod test {
171 use super::*;
172
173 #[test]
174 fn test_commit_new_top_commit() {
175 let c = String::from(
176 "490049bf36b19b30d23b4be5a4u94f71b5c6475c (HEAD -> master)
177Author: Some Author <myemail@email.com>
178Date: Tue Apr 14 17:35:15 2026 -0400
179
180 feat: added feature to get commit list
181",
182 );
183 let commit =
184 Commit::new_from_commit(c).expect("Commit could not be instantiated during test.");
185 assert_eq!(commit.id, "490049bf36b19b30d23b4be5a4u94f71b5c6475c");
186 assert_eq!(commit.author, "Some Author <myemail@email.com>");
187 assert_eq!(
188 commit.timestamp,
189 DateTime::parse_from_str("Tue Apr 14 17:35:15 2026 -0400", COMMIT_TIME_FORMAT).unwrap()
190 );
191 assert_eq!(commit.message, "feat: added feature to get commit list");
192 }
193
194 #[test]
195 fn test_commit_new_commit() {
196 let c = String::from(
197 "490049bf36b19b30d23b4be5a4u94f71b5c6475c
198Author: Some Author <myemail@email.com>
199Date: Tue Apr 14 17:35:15 2026 -0400
200
201 feat: added feature to get commit list
202",
203 );
204 let commit =
205 Commit::new_from_commit(c).expect("Commit could not be instantiated during test.");
206 assert_eq!(commit.id, "490049bf36b19b30d23b4be5a4u94f71b5c6475c");
207 assert_eq!(commit.author, "Some Author <myemail@email.com>");
208 assert_eq!(
209 commit.timestamp,
210 DateTime::parse_from_str("Tue Apr 14 17:35:15 2026 -0400", COMMIT_TIME_FORMAT).unwrap()
211 );
212 assert_eq!(commit.message, "feat: added feature to get commit list");
213 }
214}