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