1use std::collections::{HashMap, HashSet};
2
3use anyhow::anyhow;
4use serde::{Deserialize, Serialize};
5
6use crate::entities::ImmutableFileNumber;
7use crate::StdResult;
8
9#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
11pub struct FileUri(pub String);
12
13impl From<FileUri> for String {
14 fn from(file_uri: FileUri) -> Self {
15 file_uri.0
16 }
17}
18
19impl From<&FileUri> for String {
20 fn from(file_uri: &FileUri) -> Self {
21 file_uri.0.clone()
22 }
23}
24
25pub type TemplateVariable = String;
27
28pub type TemplateValue = String;
30
31#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
33pub struct TemplateUri(pub String);
34
35#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
37pub enum MultiFilesUri {
38 Template(TemplateUri),
40}
41
42impl MultiFilesUri {
43 pub fn extract_template_from_uris(
45 file_uris: Vec<String>,
46 extractor: impl Fn(&str) -> StdResult<Option<String>>,
47 ) -> StdResult<Option<TemplateUri>> {
48 let mut templates = HashSet::new();
49 for file_uri in file_uris {
50 let template_uri = extractor(&file_uri)?;
51 template_uri.map(|template| templates.insert(template));
52 }
53
54 if templates.len() > 1 {
55 return Err(anyhow!("Multiple templates found in the file URIs"));
56 }
57
58 Ok(templates.into_iter().next().map(TemplateUri))
59 }
60
61 pub fn expand_to_file_uris(
63 &self,
64 variables: Vec<HashMap<TemplateVariable, TemplateValue>>,
65 ) -> StdResult<Vec<FileUri>> {
66 Ok(variables
67 .into_iter()
68 .map(|variable| self.expand_to_file_uri(variable))
69 .collect())
70 }
71
72 pub fn expand_to_file_uri(
74 &self,
75 variable: HashMap<TemplateVariable, TemplateValue>,
76 ) -> FileUri {
77 match self {
78 MultiFilesUri::Template(template) => {
79 let mut file_uri = template.0.clone();
80 for (key, value) in variable {
81 file_uri = file_uri.replace(&format!("{{{}}}", key), &value);
82 }
83
84 FileUri(file_uri)
85 }
86 }
87 }
88
89 pub fn expand_for_immutable_file_number(
93 &self,
94 immutable_file_number: ImmutableFileNumber,
95 ) -> FileUri {
96 self.expand_to_file_uri(HashMap::from([(
97 "immutable_file_number".to_string(),
98 format!("{:05}", immutable_file_number),
99 )]))
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn returns_template() {
109 let file_uris = vec![
110 "http://whatever/00001.tar.gz".to_string(),
111 "http://whatever/00002.tar.gz".to_string(),
112 ];
113 fn extractor_returning_same_uri(_file_uri: &str) -> StdResult<Option<String>> {
114 Ok(Some(
115 "http://whatever/{immutable_file_number}.tar.gz".to_string(),
116 ))
117 }
118
119 let template =
120 MultiFilesUri::extract_template_from_uris(file_uris, extractor_returning_same_uri)
121 .unwrap();
122
123 assert_eq!(
124 template,
125 Some(TemplateUri(
126 "http://whatever/{immutable_file_number}.tar.gz".to_string()
127 ))
128 );
129 }
130
131 #[test]
132 fn returns_error_with_multiple_templates() {
133 let file_uris = vec![
134 "http://whatever/00001.tar.gz".to_string(),
135 "http://00002.tar.gz/whatever".to_string(),
136 ];
137 fn extractor_returning_different_uri(file_uri: &str) -> StdResult<Option<String>> {
138 Ok(Some(file_uri.to_string()))
139 }
140
141 MultiFilesUri::extract_template_from_uris(file_uris, extractor_returning_different_uri)
142 .expect_err(
143 "Should return an error when multiple templates are found in the file URIs",
144 );
145 }
146
147 #[test]
148 fn expand_multi_file_template_to_one_file_uri() {
149 let template = MultiFilesUri::Template(TemplateUri(
150 "http://whatever/{var1}-{var2}.tar.gz".to_string(),
151 ));
152
153 assert_eq!(
154 template.expand_to_file_uri(HashMap::from([
155 ("var1".to_string(), "00001".to_string()),
156 ("var2".to_string(), "abc".to_string()),
157 ]),),
158 FileUri("http://whatever/00001-abc.tar.gz".to_string()),
159 );
160
161 assert_eq!(
162 template.expand_to_file_uri(HashMap::from([
163 ("var1".to_string(), "00001".to_string()),
164 ("var2".to_string(), "def".to_string()),
165 ]),),
166 FileUri("http://whatever/00001-def.tar.gz".to_string()),
167 );
168
169 assert_eq!(
170 template.expand_to_file_uri(HashMap::from([
171 ("var1".to_string(), "00002".to_string()),
172 ("var2".to_string(), "def".to_string()),
173 ]),),
174 FileUri("http://whatever/00002-def.tar.gz".to_string()),
175 );
176 }
177
178 #[test]
179 fn expand_multi_file_template_to_immutable_file_number() {
180 let template = MultiFilesUri::Template(TemplateUri(
181 "http://whatever/{immutable_file_number}.tar.gz".to_string(),
182 ));
183
184 assert_eq!(
185 template.expand_for_immutable_file_number(6),
186 FileUri("http://whatever/00006.tar.gz".to_string()),
187 );
188
189 assert_eq!(
190 template.expand_for_immutable_file_number(15329),
191 FileUri("http://whatever/15329.tar.gz".to_string()),
192 );
193
194 assert_eq!(
195 template.expand_for_immutable_file_number(199999),
196 FileUri("http://whatever/199999.tar.gz".to_string()),
197 );
198 }
199
200 #[test]
201 fn expand_multi_file_template_to_multiple_file_uris() {
202 let template = MultiFilesUri::Template(TemplateUri(
203 "http://whatever/{var1}-{var2}.tar.gz".to_string(),
204 ));
205 let variables = vec![
206 HashMap::from([
207 ("var1".to_string(), "00001".to_string()),
208 ("var2".to_string(), "abc".to_string()),
209 ]),
210 HashMap::from([
211 ("var1".to_string(), "00001".to_string()),
212 ("var2".to_string(), "def".to_string()),
213 ]),
214 HashMap::from([
215 ("var1".to_string(), "00002".to_string()),
216 ("var2".to_string(), "def".to_string()),
217 ]),
218 ];
219
220 let expanded_file_uris = template.expand_to_file_uris(variables).unwrap();
221 assert_eq!(
222 vec![
223 FileUri("http://whatever/00001-abc.tar.gz".to_string()),
224 FileUri("http://whatever/00001-def.tar.gz".to_string()),
225 FileUri("http://whatever/00002-def.tar.gz".to_string()),
226 ],
227 expanded_file_uris,
228 );
229 }
230}