Skip to main content

stratum/domain/
actor.rs

1use chrono::{DateTime, Utc};
2use git2::Signature;
3use std::str::FromStr;
4
5use crate::Error;
6
7/// A git actor who exists for the inspected repository
8pub struct Actor {
9    inner: Signature<'static>,
10}
11
12impl FromStr for Actor {
13    type Err = Error;
14
15    /// Instantiate an Actor from an author string
16    ///
17    /// Input is expected to be of the form "name <email>", as no time
18    /// information necassarily exists, the actors signature is instantiated
19    /// to have been created at epoch i.e. the unix timestamp. This is done as
20    /// the probability of an actors signature being valid within a repository
21    /// at the time of the unix time stamp is extremely unlikely  
22    fn from_str(s: &str) -> Result<Self, Self::Err> {
23        let start_idx = s.find('<').unwrap_or_default();
24        let end_idx = s.find('>').unwrap_or_default();
25
26        let time = git2::Time::new(0, 0);
27        //TODO: if start_idx+1 is > end_idx, panics. Occurs when no <> exists.
28        let sig = Signature::new(&s[..start_idx], &s[start_idx + 1..end_idx], &time)
29            .map_err(Error::Git)?;
30
31        Ok(Self::new(sig))
32    }
33}
34
35impl Actor {
36    /// Instantiate a new Actor from their signature
37    pub fn new(signature: Signature<'_>) -> Self {
38        Self {
39            inner: signature.to_owned(),
40        }
41    }
42
43    /// Return the actors name if it exists
44    pub fn name(&self) -> Option<&str> {
45        self.inner.name()
46    }
47
48    /// Return the actors email if it exists
49    pub fn email(&self) -> Option<&str> {
50        self.inner.email()
51    }
52
53    /// Return the timestamp of actor action if it exists
54    pub fn timestamp(&self) -> Option<DateTime<Utc>> {
55        DateTime::from_timestamp_secs(self.inner.when().seconds())
56    }
57
58    /// Return the offset from the UTC timestamp in minutes
59    pub fn offset(&self) -> i32 {
60        self.inner.when().offset_minutes()
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    fn actor_factory() -> Actor {
69        let sig = Signature::new(
70            "test",
71            "test@example.com",
72            &git2::Time::new(1_600_000_000, -60),
73        )
74        .unwrap();
75
76        Actor::new(sig)
77    }
78
79    #[test]
80    fn test_name() {
81        let actor = actor_factory();
82        assert_eq!(actor.name(), Some("test"));
83    }
84
85    #[test]
86    fn test_email() {
87        let actor = actor_factory();
88        assert_eq!(actor.email(), Some("test@example.com"));
89    }
90
91    #[test]
92    fn test_time() {
93        let actor = actor_factory();
94        assert_eq!(actor.timestamp().unwrap().timestamp(), 1_600_000_000);
95    }
96
97    #[test]
98    fn test_offset() {
99        let actor = actor_factory();
100        assert_eq!(actor.offset(), -60);
101    }
102
103    #[test]
104    fn test_from_str() {
105        let actor = Actor::from_str("test <test@example.com>").unwrap();
106
107        assert_eq!(actor.name(), Some("test"));
108        assert_eq!(actor.email(), Some("test@example.com"));
109        assert_eq!(actor.timestamp().unwrap().timestamp(), 0);
110        assert_eq!(actor.offset(), 0);
111    }
112
113    #[test]
114    fn test_malformed_from_str() {
115        let result = Actor::from_str("some nonsense <>");
116
117        assert!(result.is_err())
118    }
119
120    #[test]
121    fn test_extended_from_str() {
122        let actor = Actor::from_str("test <test@example.com> nonsense").unwrap();
123
124        assert_eq!(actor.name(), Some("test"));
125        assert_eq!(actor.email(), Some("test@example.com"));
126    }
127
128    #[test]
129    fn test_surname() {
130        let actor = Actor::from_str("test surname <email>").unwrap();
131
132        assert_eq!(actor.name(), Some("test surname"));
133    }
134}