waypoint/core/
util.rs

1use crate::core::types::FARCASTER_EPOCH;
2
3#[derive(Debug)]
4pub struct HubError {
5    pub code: String,
6    pub message: String,
7}
8
9impl std::fmt::Display for HubError {
10    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
11        write!(f, "{}: {}", self.code, self.message)
12    }
13}
14
15impl std::error::Error for HubError {}
16
17pub fn to_farcaster_time(time_ms: u64) -> Result<u32, HubError> {
18    if time_ms < FARCASTER_EPOCH {
19        return Err(HubError {
20            code: "bad_request.invalid_param".to_string(),
21            message: format!("time_ms is before the farcaster epoch: {}", time_ms),
22        });
23    }
24    let seconds_since_epoch = ((time_ms - FARCASTER_EPOCH) / 1000) as u32;
25    Ok(seconds_since_epoch)
26}
27
28pub fn from_farcaster_time(time: u32) -> u64 {
29    (time as u64) * 1000 + FARCASTER_EPOCH
30}
31
32pub fn get_farcaster_time() -> Result<u32, HubError> {
33    let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map_err(|e| {
34        HubError {
35            code: "internal_error".to_string(),
36            message: format!("failed to get time: {}", e),
37        }
38    })?;
39    to_farcaster_time(now.as_millis() as u64)
40}
41
42pub fn get_time_diff(timestamp: u64) -> String {
43    let now =
44        std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis()
45            as u64;
46
47    let diff_ms = if timestamp > now { timestamp - now } else { now - timestamp };
48
49    let diff_secs = diff_ms / 1000;
50    let is_future = timestamp > now;
51
52    let (value, unit) = if diff_secs < 60 {
53        (diff_secs, "s")
54    } else if diff_secs < 3600 {
55        (diff_secs / 60, "m")
56    } else if diff_secs < 86400 {
57        (diff_secs / 3600, "h")
58    } else {
59        (diff_secs / 86400, "d")
60    };
61
62    format!("({}{} {})", value, unit, if is_future { "in future" } else { "ago" })
63}
64
65pub fn calculate_message_hash(data_bytes: &[u8]) -> Vec<u8> {
66    blake3::hash(data_bytes).as_bytes()[0..20].to_vec()
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn test_time_diff() {
75        let now =
76            std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis()
77                as u64;
78
79        // For absolute time differences, use exact assertions
80        assert_eq!(get_time_diff(now), "(0s ago)");
81        assert_eq!(get_time_diff(now - 30_000), "(30s ago)");
82        assert_eq!(get_time_diff(now - 90_000), "(1m ago)");
83        assert_eq!(get_time_diff(now - 3_600_000), "(1h ago)");
84        assert_eq!(get_time_diff(now - 86_400_000), "(1d ago)");
85
86        // For future time, just check that it contains the expected text since timing can vary
87        let future_text = get_time_diff(now + 30_000);
88        assert!(future_text.contains("in future"), "Expected 'in future' in text: {}", future_text);
89    }
90
91    #[test]
92    fn test_get_farcaster_time() {
93        let time = get_farcaster_time().unwrap();
94        let now =
95            std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis()
96                as u32;
97        assert!(time <= now);
98    }
99
100    #[test]
101    fn test_to_farcaster_time() {
102        // It is an error to pass a time before the farcaster epoch
103        let time = to_farcaster_time(0);
104        assert!(time.is_err());
105
106        let time = to_farcaster_time(FARCASTER_EPOCH - 1);
107        assert!(time.is_err());
108
109        let time = to_farcaster_time(FARCASTER_EPOCH).unwrap();
110        assert_eq!(time, 0);
111
112        let time = to_farcaster_time(FARCASTER_EPOCH + 1000).unwrap();
113        assert_eq!(time, 1);
114    }
115
116    #[test]
117    fn test_from_farcaster_time() {
118        assert_eq!(from_farcaster_time(0), FARCASTER_EPOCH);
119        assert_eq!(from_farcaster_time(1), FARCASTER_EPOCH + 1000);
120        assert_eq!(from_farcaster_time(1000), FARCASTER_EPOCH + 1_000_000);
121    }
122
123    #[test]
124    fn test_time_conversion_roundtrip() {
125        let current_time =
126            std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis()
127                as u64;
128
129        let farcaster_time = to_farcaster_time(current_time).unwrap();
130        let reconverted_time = from_farcaster_time(farcaster_time);
131
132        // We might lose some precision due to seconds conversion
133        assert!((current_time as i64 - reconverted_time as i64).abs() < 1000);
134    }
135
136    #[test]
137    fn test_message_hash() {
138        let data = b"test message";
139        let hash = calculate_message_hash(data);
140        assert_eq!(hash.len(), 20);
141
142        // Test deterministic behavior
143        let hash2 = calculate_message_hash(data);
144        assert_eq!(hash, hash2);
145    }
146}