liveboat/
utils.rs

1use chrono::DateTime;
2use log::info;
3use rand::{distributions::Alphanumeric, Rng};
4use std::cmp::Ordering;
5use std::fs;
6use std::fs::File;
7use std::io;
8use std::io::copy as ioCopy;
9use std::path::Path;
10
11#[cfg(not(test))]
12use chrono::Local;
13
14#[cfg(test)]
15use chrono::Utc;
16
17use anyhow::{anyhow, Result};
18use env_logger::Env;
19
20/// Representation of Versioning used by liveboat,
21/// conforming to <major>.<minor>.<patch> format.
22#[derive(Debug)]
23pub struct Version {
24    pub major: u64,
25    pub minor: u64,
26    pub patch: u64,
27}
28
29impl Version {
30    /// Load version instance from string.
31    pub fn from_str(s: String) -> Result<Version> {
32        let parts = s.split(".").collect::<Vec<&str>>();
33        if parts.len() != 3 {
34            return Err(anyhow!(
35                "Invalid version detected, proper format is <major>.<minor>.<patch>: {}",
36                s
37            ));
38        }
39        let mut v = Version {
40            major: 0,
41            minor: 0,
42            patch: 0,
43        };
44        v.major = parts[0].to_string().parse::<u64>()?;
45        v.minor = parts[1].to_string().parse::<u64>()?;
46        v.patch = parts[2].to_string().parse::<u64>()?;
47        Ok(v)
48    }
49    /// Compare 2 versions returning ordering result in response.
50    pub fn cmp(&self, other: &Version) -> Ordering {
51        match self.major.cmp(&other.major) {
52            Ordering::Greater => Ordering::Greater,
53            Ordering::Less => Ordering::Less,
54            Ordering::Equal => match self.minor.cmp(&other.minor) {
55                Ordering::Greater => Ordering::Greater,
56                Ordering::Less => Ordering::Less,
57                Ordering::Equal => match self.patch.cmp(&other.patch) {
58                    Ordering::Greater => Ordering::Greater,
59                    Ordering::Less => Ordering::Less,
60                    Ordering::Equal => Ordering::Equal,
61                },
62            },
63        }
64    }
65}
66
67#[cfg(not(test))]
68pub fn now() -> DateTime<Local> {
69    Local::now()
70}
71
72#[cfg(test)]
73pub fn now() -> DateTime<Utc> {
74    DateTime::from_timestamp(1733974974, 0).unwrap()
75}
76
77/// Initialize logger for the app.
78pub fn init_logger(debug: bool) {
79    let llevel = match debug {
80        true => "info",
81        false => "error",
82    };
83    env_logger::Builder::from_env(Env::default().default_filter_or(llevel))
84        .init();
85    info!("Logger initialized")
86}
87
88/// Generate random string with given length.
89pub fn generate_random_string(len: usize) -> String {
90    return rand::thread_rng()
91        .sample_iter(&Alphanumeric)
92        .take(len)
93        .map(char::from)
94        .collect();
95}
96
97/// Fetch file from url saving it under path specified
98pub fn download_file(url: &str, f_name: &Path) -> Result<()> {
99    let mut response = reqwest::blocking::get(url)?;
100    let mut file = File::create(f_name)?;
101    ioCopy(&mut response, &mut file)?;
102
103    Ok(())
104}
105
106/// Cleanup temp dir
107pub fn tidy_up(tmp_dir: &Path) {
108    _ = fs::remove_dir_all(tmp_dir);
109}
110
111/// Helper func for copying all the contents of directory
112/// to another.
113pub fn copy_all(
114    src: impl AsRef<Path>,
115    dst: impl AsRef<Path>,
116) -> io::Result<()> {
117    fs::create_dir_all(&dst)?;
118    for entry in fs::read_dir(src)? {
119        let entry = entry?;
120        let ty = entry.file_type()?;
121        if ty.is_dir() {
122            copy_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
123        } else {
124            fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
125        }
126    }
127    Ok(())
128}
129
130#[cfg(test)]
131mod tests {
132
133    use super::*;
134
135    #[test]
136    fn test_loading_version_from_string() {
137        let mut result = Version::from_str("1.2.3".to_string());
138        assert!(result.is_ok());
139        let v = result.unwrap();
140        assert_eq!(v.major, 1);
141        assert_eq!(v.minor, 2);
142        assert_eq!(v.patch, 3);
143        result = Version::from_str("12.3".to_string());
144        assert!(result.is_err());
145        result = Version::from_str("1.2.3.4".to_string());
146        assert!(result.is_err());
147        result = Version::from_str("gibberish".to_string());
148        assert!(result.is_err());
149        result = Version::from_str("1.2.3-alpha".to_string());
150        assert!(result.is_err());
151    }
152
153    #[test]
154    fn test_comparing_versions() {
155        let mut v1 = Version::from_str("1.2.3".to_string()).unwrap();
156        let mut v2 = Version::from_str("1.2.3".to_string()).unwrap();
157        assert_eq!(v1.cmp(&v2), Ordering::Equal);
158        v1 = Version::from_str("1.2.3".to_string()).unwrap();
159        v2 = Version::from_str("2.2.3".to_string()).unwrap();
160        assert_eq!(v1.cmp(&v2), Ordering::Less);
161        v1 = Version::from_str("1.2.4".to_string()).unwrap();
162        v2 = Version::from_str("1.2.3".to_string()).unwrap();
163        assert_eq!(v1.cmp(&v2), Ordering::Greater);
164        v1 = Version::from_str("1.2.3".to_string()).unwrap();
165        v2 = Version::from_str("1.4.3".to_string()).unwrap();
166        assert_eq!(v1.cmp(&v2), Ordering::Less);
167    }
168}