mithril_common/entities/
file_uri.rs

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
140
141
142
143
144
145
146
147
148
149
use std::collections::{HashMap, HashSet};

use anyhow::anyhow;
use serde::{Deserialize, Serialize};

use crate::StdResult;

/// FileUri represents a file URI used to identify the file's location
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct FileUri(pub String);

impl From<FileUri> for String {
    fn from(file_uri: FileUri) -> Self {
        file_uri.0
    }
}

/// TemplateVariable represents a variable in a template
pub type TemplateVariable = String;

/// TemplateValue represents a value in a template
pub type TemplateValue = String;

/// [TemplateUri] represents an URI pattern used to build a file's location
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
pub struct TemplateUri(pub String);

/// [MultiFilesUri] represents a unique location uri for multiple files
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
pub enum MultiFilesUri {
    /// URI template representing several URI
    Template(TemplateUri),
}

impl MultiFilesUri {
    /// Extract a template from a list of URIs
    pub fn extract_template_from_uris(
        file_uris: Vec<String>,
        extractor: impl Fn(&str) -> StdResult<Option<String>>,
    ) -> StdResult<Option<TemplateUri>> {
        let mut templates = HashSet::new();
        for file_uri in file_uris {
            let template_uri = extractor(&file_uri)?;
            template_uri.map(|template| templates.insert(template));
        }

        if templates.len() > 1 {
            return Err(anyhow!("Multiple templates found in the file URIs"));
        }

        Ok(templates.into_iter().next().map(TemplateUri))
    }

    /// Expand the template to a list of file URIs
    pub fn expand_to_file_uris(
        &self,
        variables: Vec<HashMap<TemplateVariable, TemplateValue>>,
    ) -> StdResult<Vec<FileUri>> {
        match self {
            MultiFilesUri::Template(template) => Ok(variables
                .into_iter()
                .map(|variable| {
                    let mut file_uri = template.0.clone();
                    for (key, value) in variable {
                        file_uri = file_uri.replace(&format!("{{{}}}", key), &value);
                    }

                    FileUri(file_uri)
                })
                .collect()),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn returns_template() {
        let file_uris = vec![
            "http://whatever/00001.tar.gz".to_string(),
            "http://whatever/00002.tar.gz".to_string(),
        ];
        fn extractor_returning_same_uri(_file_uri: &str) -> StdResult<Option<String>> {
            Ok(Some(
                "http://whatever/{immutable_file_number}.tar.gz".to_string(),
            ))
        }

        let template =
            MultiFilesUri::extract_template_from_uris(file_uris, extractor_returning_same_uri)
                .unwrap();

        assert_eq!(
            template,
            Some(TemplateUri(
                "http://whatever/{immutable_file_number}.tar.gz".to_string()
            ))
        );
    }

    #[test]
    fn returns_error_with_multiple_templates() {
        let file_uris = vec![
            "http://whatever/00001.tar.gz".to_string(),
            "http://00002.tar.gz/whatever".to_string(),
        ];
        fn extractor_returning_different_uri(file_uri: &str) -> StdResult<Option<String>> {
            Ok(Some(file_uri.to_string()))
        }

        MultiFilesUri::extract_template_from_uris(file_uris, extractor_returning_different_uri)
            .expect_err(
                "Should return an error when multiple templates are found in the file URIs",
            );
    }

    #[test]
    fn expand_multi_file_template_to_multiple_file_uris() {
        let template = MultiFilesUri::Template(TemplateUri(
            "http://whatever/{var1}-{var2}.tar.gz".to_string(),
        ));
        let variables = vec![
            HashMap::from([
                ("var1".to_string(), "00001".to_string()),
                ("var2".to_string(), "abc".to_string()),
            ]),
            HashMap::from([
                ("var1".to_string(), "00001".to_string()),
                ("var2".to_string(), "def".to_string()),
            ]),
            HashMap::from([
                ("var1".to_string(), "00002".to_string()),
                ("var2".to_string(), "def".to_string()),
            ]),
        ];

        let expanded_file_uris = template.expand_to_file_uris(variables).unwrap();
        assert_eq!(
            vec![
                FileUri("http://whatever/00001-abc.tar.gz".to_string()),
                FileUri("http://whatever/00001-def.tar.gz".to_string()),
                FileUri("http://whatever/00002-def.tar.gz".to_string()),
            ],
            expanded_file_uris,
        );
    }
}