1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;

use toml;

use super::util;

#[derive(Deserialize)]
pub struct Metadata {
    pub repos: HashMap<String, Repo>,
    pub proxy: Proxy,
}

#[derive(Deserialize)]
pub struct Repo {
    pub vcs: String,
    pub allow_sync: bool,
    pub bare: bool,
    pub use_proxy: bool,
    pub topics: Vec<String>,
}

#[derive(Deserialize)]
pub struct Proxy {
    pub scheme: String,
    pub host: String,
    pub port: u16,
}

/// Load metadata from file path.
pub fn load(path: &Path) -> Result<Metadata, String> {
    let path_display = path.display();
    let mut file = match File::open(&path) {
        Ok(file) => file,
        Err(err) => {
            return Err(format!(
                "Couldn't open {}: {}",
                path_display,
                err.to_string()
            ))
        }
    };

    let mut content = String::new();
    match file.read_to_string(&mut content) {
        Ok(_) => {}
        Err(err) => {
            return Err(format!(
                "Couldn't read {}: {}",
                path_display,
                err.to_string()
            ))
        }
    }

    loads(&content)
}

/// Load metadata from string slices.
pub fn loads(content: &str) -> Result<Metadata, String> {
    let md: Metadata = match toml::from_str(&content) {
        Ok(md) => md,
        Err(err) => {
            return Err(format!(
                "Couldn't parse as toml format because of: {}",
                err.to_string()
            ));
        }
    };

    // Validate repo url.
    let mut urls_errors: HashMap<String, String> = HashMap::new();
    for url in md.repos.keys() {
        match util::validate_repo_url(url) {
            Err(err) => {
                urls_errors.insert(url.to_string(), err.to_string());
            }
            Ok(_) => {}
        }
    }
    if !urls_errors.is_empty() {
        for (url, error) in urls_errors {
            println!("Url '{}' is unsupported because of: {}.", url, error);
        }
        Err("Unsupported repository urls found in metadata file.".to_string())
    } else {
        Ok(md)
    }
}

#[cfg(test)]
mod tests {
    extern crate tempfile;

    use std::io::Write;
    use std::path::Path;

    use super::{load, loads};

    static TEMP_CONTENT: &'static str = "
        [repos.'https://github.com/org/repo.git']
        vcs = 'git'
        allow_sync = true
        bare = false
        use_proxy = false
        topics = ['topic1', 'topic2']

        [proxy]
        scheme = 'socks5'
        host = '127.0.0.1'
        port = 1080
    ";

    #[test]
    fn md_loads() {
        let md = loads(&TEMP_CONTENT).unwrap();

        assert_eq!(md.proxy.scheme, "socks5");
        assert_eq!(md.proxy.host, "127.0.0.1");
        assert_eq!(md.proxy.port, 1080);
    }

    #[test]
    fn md_load() {
        let mut tmpfile = tempfile::NamedTempFile::new().unwrap();
        write!(tmpfile, "{}", &TEMP_CONTENT).unwrap();

        let path = tmpfile.path();

        let filepath = Path::new(path);
        let md = load(&filepath).unwrap();

        assert_eq!(md.proxy.scheme, "socks5");
        assert_eq!(md.proxy.host, "127.0.0.1");
        assert_eq!(md.proxy.port, 1080);
    }
}