service_bindings/
binding.rs1use std::cell::RefCell;
18use std::collections::hash_map::Entry;
19use std::collections::HashMap;
20use std::fmt::Debug;
21use std::fs;
22use std::path::PathBuf;
23use std::str;
24
25use crate::secret;
26
27pub const PROVIDER: &str = "provider";
29
30pub const TYPE: &str = "type";
32
33#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct InvalidBindingError {
36 message: String,
37}
38
39impl InvalidBindingError {
40 pub fn new(message: impl Into<String>) -> InvalidBindingError {
41 InvalidBindingError { message: message.into() }
42 }
43}
44
45pub trait Binding {
48 fn get_as_bytes(&self, key: &str) -> Option<Vec<u8>>;
54
55 fn get_name(&self) -> String;
59
60 fn get(&self, key: &str) -> Option<String> {
66 return match self.get_as_bytes(key) {
67 None => None,
68 Some(b) => Some(str::from_utf8(&b)
69 .map(|s| s.trim().to_string())
70 .unwrap()),
71 };
72 }
73
74 fn get_provider(&self) -> Option<String> {
78 return self.get(PROVIDER);
79 }
80
81 fn get_type(&self) -> Result<String, InvalidBindingError> {
85 return match self.get(TYPE) {
86 None => Err(InvalidBindingError::new("binding does not contain a type")),
87 Some(t) => Ok(t),
88 };
89 }
90}
91
92pub struct CacheBinding<'a> {
94 delegate: Box<dyn Binding + 'a>,
95 cache: RefCell<HashMap<String, Vec<u8>>>,
96}
97
98impl<'a> CacheBinding<'a> {
99 pub fn new(delegate: impl Binding + 'a) -> CacheBinding<'a> {
103 return CacheBinding {
104 delegate: Box::new(delegate),
105 cache: RefCell::new(HashMap::new()),
106 };
107 }
108}
109
110impl Binding for CacheBinding<'_> {
111 fn get_as_bytes(&self, key: &str) -> Option<Vec<u8>> {
112 return match self.cache.borrow_mut().entry(key.to_string()) {
113 Entry::Occupied(o) => Some(o.get().to_vec()),
114 Entry::Vacant(v) => {
115 return match self.delegate.get_as_bytes(key) {
116 None => None,
117 Some(w) => Some(v.insert(w).to_vec()),
118 };
119 }
120 };
121 }
122
123 fn get_name(&self) -> String {
124 return self.delegate.get_name();
125 }
126}
127
128pub struct ConfigTreeBinding {
131 root: PathBuf,
132}
133
134impl ConfigTreeBinding {
135 pub fn new<P: Into<PathBuf>>(root: P) -> ConfigTreeBinding {
139 return ConfigTreeBinding {
140 root: root.into()
141 };
142 }
143}
144
145impl Binding for ConfigTreeBinding {
146 fn get_as_bytes(&self, key: &str) -> Option<Vec<u8>> {
147 if !secret::is_valid_secret_key(key) {
148 return None;
149 }
150
151 let p = self.root.join(PathBuf::from(key));
152
153 if !p.exists() || !p.is_file() {
154 return None;
155 }
156
157 return fs::read(p).ok();
158 }
159
160 fn get_name(&self) -> String {
161 return self.root.file_stem()
162 .and_then(|s| s.to_str())
163 .map(|s| s.to_string())
164 .unwrap();
165 }
166}
167
168pub struct HashMapBinding {
170 name: String,
171 content: HashMap<String, Vec<u8>>,
172}
173
174impl HashMapBinding {
175 pub fn new(name: impl Into<String>, content: HashMap<String, Vec<u8>>) -> HashMapBinding {
180 return HashMapBinding {
181 name: name.into(),
182 content,
183 };
184 }
185}
186
187impl Binding for HashMapBinding {
188 fn get_as_bytes(&self, key: &str) -> Option<Vec<u8>> {
189 if !secret::is_valid_secret_key(key) {
190 return None;
191 }
192
193 if !self.content.contains_key(key) {
194 return None;
195 }
196
197 return self.content.get(key)
198 .map(|v| v.to_vec());
199 }
200
201 fn get_name(&self) -> String {
202 return self.name.to_string();
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use std::cell::RefCell;
209 use std::collections::HashMap;
210 use std::rc::Rc;
211
212 use crate::binding::{Binding, CacheBinding, ConfigTreeBinding, HashMapBinding, InvalidBindingError};
213
214 #[test]
215 fn get_missing() {
216 let b = HashMapBinding::new("test-name", map! {});
217 assert_eq!(None, b.get("test-missing-key"))
218 }
219
220 #[test]
221 fn get_valid() {
222 let b = HashMapBinding::new("test-name", map! {
223 "test-secret-key" => "test-secret-value\n",
224 });
225
226 assert_eq!(Some("test-secret-value".to_string()), b.get("test-secret-key"))
227 }
228
229 #[test]
230 fn get_provider_missing() {
231 let b = HashMapBinding::new("test-name", HashMap::new());
232 assert_eq!(None, b.get_provider())
233 }
234
235 #[test]
236 fn get_provider_valid() {
237 let b = HashMapBinding::new("test-name", map! {
238 "provider" => "test-provider-1",
239 });
240
241 assert_eq!(Some("test-provider-1".to_string()), b.get_provider())
242 }
243
244 #[test]
245 fn get_type_invalid() {
246 let b = HashMapBinding::new("test-name", HashMap::new());
247 assert_eq!(Err(InvalidBindingError::new("binding does not contain a type")), b.get_type())
248 }
249
250 #[test]
251 fn get_type_valid() {
252 let b = HashMapBinding::new("test-name", map! {
253 "type" => "test-type-1",
254 });
255
256 assert_eq!(Ok("test-type-1".to_string()), b.get_type())
257 }
258
259 #[test]
260 fn cache_binding_missing() {
261 let s = StubBinding::new();
262 let c = Rc::clone(&s.get_as_bytes_count);
263
264 let b = CacheBinding::new(s);
265
266 assert_eq!(None, b.get_as_bytes("test-unknown-key"));
267 assert_eq!(None, b.get_as_bytes("test-unknown-key"));
268 assert_eq!(2, c.take());
269 }
270
271 #[test]
272 fn cache_binding_valid() {
273 let s = StubBinding::new();
274 let c = Rc::clone(&s.get_as_bytes_count);
275
276 let b = CacheBinding::new(s);
277
278 assert_eq!(Some(Vec::new()), b.get_as_bytes("test-secret-key"));
279 assert_eq!(Some(Vec::new()), b.get_as_bytes("test-secret-key"));
280 assert_eq!(1, c.take());
281 }
282
283 #[test]
284 fn cache_binding_get_name() {
285 let s = StubBinding::new();
286 let c = Rc::clone(&s.get_name_count);
287
288 let b = CacheBinding::new(s);
289
290 assert_eq!(String::from("test-name"), b.get_name());
291 assert_eq!(String::from("test-name"), b.get_name());
292 assert_eq!(2, c.take());
293 }
294
295 #[test]
296 fn config_tree_binding_missing() {
297 let b = ConfigTreeBinding::new("testdata/test-k8s");
298 assert_eq!(None, b.get_as_bytes("test-missing-key"))
299 }
300
301 #[test]
302 fn config_tree_binding_directory() {
303 let b = ConfigTreeBinding::new("testdata/test-k8s");
304 assert_eq!(None, b.get_as_bytes(".hidden-data"))
305 }
306
307 #[test]
308 fn config_tree_binding_invalid() {
309 let b = ConfigTreeBinding::new("testdata/test-k8s");
310 assert_eq!(None, b.get_as_bytes("test^invalid^key"))
311 }
312
313 #[test]
314 fn config_tree_binding_valid() {
315 let b = ConfigTreeBinding::new("testdata/test-k8s");
316 assert_eq!(Some("test-secret-value\n".as_bytes().to_vec()), b.get_as_bytes("test-secret-key"))
317 }
318
319 #[test]
320 fn config_tree_binding_get_name() {
321 let b = ConfigTreeBinding::new("testdata/test-k8s");
322 assert_eq!(String::from("test-k8s"), b.get_name())
323 }
324
325 #[test]
326 fn hash_map_binding_missing() {
327 let b = HashMapBinding::new("test-name", HashMap::new());
328 assert_eq!(None, b.get_as_bytes("test-missing-key"))
329 }
330
331 #[test]
332 fn hash_map_binding_invalid() {
333 let b = HashMapBinding::new("test-name", HashMap::new());
334 assert_eq!(None, b.get_as_bytes("test^invalid^key"))
335 }
336
337 #[test]
338 fn hash_map_binding_valid() {
339 let b = HashMapBinding::new("test-name", map! {
340 "test-secret-key" => "test-secret-value\n",
341 });
342
343 assert_eq!(Some("test-secret-value\n".as_bytes().to_vec()), b.get_as_bytes("test-secret-key"))
344 }
345
346 #[test]
347 fn hash_map_binding_get_name() {
348 let b = HashMapBinding::new("test-name", HashMap::new());
349 assert_eq!("test-name", b.get_name())
350 }
351
352 struct StubBinding {
353 get_as_bytes_count: Rc<RefCell<i32>>,
354 get_name_count: Rc<RefCell<i32>>,
355 }
356
357 impl StubBinding {
358 fn new() -> StubBinding {
359 return StubBinding {
360 get_as_bytes_count: Rc::new(RefCell::new(0)),
361 get_name_count: Rc::new(RefCell::new(0)),
362 };
363 }
364 }
365
366 impl Binding for StubBinding {
367 fn get_as_bytes(&self, key: &str) -> Option<Vec<u8>> {
368 (*self.get_as_bytes_count).replace_with(|f| *f + 1);
369
370 if "test-secret-key".eq(key) {
371 return Some(Vec::new());
372 }
373
374 return None;
375 }
376
377 fn get_name(&self) -> String {
378 (*self.get_name_count).replace_with(|f| *f + 1);
379 return String::from("test-name");
380 }
381 }
382}