1use crate::source::{LayerRef, LayerSource, SourceError};
37use std::cell::RefCell;
38
39pub struct Mirror {
41 pub label: String,
43 pub source: Box<dyn LayerSource>,
44}
45
46pub struct Mirrors {
52 mirrors: Vec<Mirror>,
53 served_by: RefCell<Option<String>>,
55 attempts: RefCell<Vec<(String, String)>>,
57}
58
59impl Mirrors {
60 pub fn new(mirrors: Vec<Mirror>) -> Self {
61 Mirrors {
62 mirrors,
63 served_by: RefCell::new(None),
64 attempts: RefCell::new(Vec::new()),
65 }
66 }
67
68 pub fn served_by(&self) -> Option<String> {
74 self.served_by.borrow().clone()
75 }
76
77 pub fn attempts(&self) -> Vec<(String, String)> {
79 self.attempts.borrow().clone()
80 }
81
82 pub fn labels(&self) -> Vec<&str> {
83 self.mirrors.iter().map(|m| m.label.as_str()).collect()
84 }
85
86 fn try_each<T>(
93 &self,
94 what: &str,
95 mut f: impl FnMut(&dyn LayerSource) -> Result<T, SourceError>,
96 ) -> Result<T, SourceError> {
97 self.attempts.borrow_mut().clear();
98 if self.mirrors.is_empty() {
99 return Err(SourceError::Transport(
100 "this realm declares no sources at all".into(),
101 ));
102 }
103 for m in &self.mirrors {
104 match f(m.source.as_ref()) {
105 Ok(v) => {
106 *self.served_by.borrow_mut() = Some(m.label.clone());
107 return Ok(v);
108 }
109 Err(e) => {
110 self.attempts
111 .borrow_mut()
112 .push((m.label.clone(), e.to_string()));
113 }
114 }
115 }
116 let tried = self
117 .attempts
118 .borrow()
119 .iter()
120 .map(|(l, e)| format!("\n {l}: {e}"))
121 .collect::<String>();
122 Err(SourceError::Transport(format!(
123 "no source could supply {what}. Tried {} source(s):{tried}",
124 self.mirrors.len()
125 )))
126 }
127}
128
129impl LayerSource for Mirrors {
130 fn fetch_manifest(&self, layer: &LayerRef) -> Result<Vec<u8>, SourceError> {
131 self.try_each("the layer manifest", |s| s.fetch_manifest(layer))
132 }
133
134 fn fetch_blob(&self, digest: &str) -> Result<Vec<u8>, SourceError> {
135 self.try_each(&format!("blob {digest}"), |s| s.fetch_blob(digest))
136 }
137
138 fn fetch_line_status(&self, layer: &LayerRef) -> Result<Option<Vec<u8>>, SourceError> {
139 self.try_each_optional("the line-status document", |s| s.fetch_line_status(layer))
144 }
145
146 fn fetch_line_index(&self, line: &str) -> Result<Option<Vec<u8>>, SourceError> {
147 self.try_each_optional("the line index", |s| s.fetch_line_index(line))
148 }
149
150 fn fetch_published_line_status(&self, line: &str) -> Result<Option<Vec<u8>>, SourceError> {
151 self.try_each_optional("the published line-status document", |s| {
155 s.fetch_published_line_status(line)
156 })
157 }
158
159 fn fetch_attestations(
160 &self,
161 layer: &LayerRef,
162 ) -> Result<Vec<crate::attestcarry::CarriedAttestation>, SourceError> {
163 self.try_each("the attestations", |s| s.fetch_attestations(layer))
164 }
165}
166
167impl Mirrors {
168 fn try_each_optional(
172 &self,
173 what: &str,
174 mut f: impl FnMut(&dyn LayerSource) -> Result<Option<Vec<u8>>, SourceError>,
175 ) -> Result<Option<Vec<u8>>, SourceError> {
176 self.attempts.borrow_mut().clear();
177 let mut any_answered = false;
178 for m in &self.mirrors {
179 match f(m.source.as_ref()) {
180 Ok(Some(v)) => {
181 *self.served_by.borrow_mut() = Some(m.label.clone());
182 return Ok(Some(v));
183 }
184 Ok(None) => {
185 any_answered = true;
186 self.attempts
187 .borrow_mut()
188 .push((m.label.clone(), "carries none".into()));
189 }
190 Err(e) => {
191 self.attempts
192 .borrow_mut()
193 .push((m.label.clone(), e.to_string()));
194 }
195 }
196 }
197 if any_answered {
198 return Ok(None);
201 }
202 let tried = self
203 .attempts
204 .borrow()
205 .iter()
206 .map(|(l, e)| format!("\n {l}: {e}"))
207 .collect::<String>();
208 Err(SourceError::Transport(format!(
209 "no source could be reached for {what}:{tried}"
210 )))
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217 use std::cell::Cell;
218 use std::rc::Rc;
219
220 struct Fake {
221 manifest: Option<Vec<u8>>,
222 err: Option<SourceError>,
223 status: Option<Option<Vec<u8>>>,
224 published: Option<Option<Vec<u8>>>,
230 calls: Rc<Cell<usize>>,
231 }
232
233 fn ok(bytes: &[u8]) -> Box<Fake> {
234 Box::new(Fake {
235 manifest: Some(bytes.to_vec()),
236 err: None,
237 status: Some(Some(bytes.to_vec())),
238 published: Some(Some(bytes.to_vec())),
239 calls: Rc::new(Cell::new(0)),
240 })
241 }
242 fn down(msg: &str) -> Box<Fake> {
243 Box::new(Fake {
244 manifest: None,
245 err: Some(SourceError::Transport(msg.into())),
246 status: None,
247 published: None,
248 calls: Rc::new(Cell::new(0)),
249 })
250 }
251 fn empty() -> Box<Fake> {
252 Box::new(Fake {
253 manifest: None,
254 err: Some(SourceError::NotFound("layer".into())),
255 status: Some(None),
256 published: Some(None),
257 calls: Rc::new(Cell::new(0)),
258 })
259 }
260
261 impl LayerSource for Fake {
262 fn fetch_manifest(&self, _l: &LayerRef) -> Result<Vec<u8>, SourceError> {
263 self.calls.set(self.calls.get() + 1);
264 match (&self.manifest, &self.err) {
265 (Some(m), _) => Ok(m.clone()),
266 (None, Some(e)) => Err(clone_err(e)),
267 _ => Err(SourceError::NotFound("x".into())),
268 }
269 }
270 fn fetch_blob(&self, _d: &str) -> Result<Vec<u8>, SourceError> {
271 self.fetch_manifest(&LayerRef::Digest("x".into()))
272 }
273 fn fetch_published_line_status(&self, _l: &str) -> Result<Option<Vec<u8>>, SourceError> {
274 self.calls.set(self.calls.get() + 1);
275 match (&self.published, &self.err) {
276 (Some(s), _) => Ok(s.clone()),
277 (None, Some(e)) => Err(clone_err(e)),
278 (None, None) => Ok(None),
279 }
280 }
281
282 fn fetch_line_status(&self, _l: &LayerRef) -> Result<Option<Vec<u8>>, SourceError> {
283 self.calls.set(self.calls.get() + 1);
284 match (&self.status, &self.err) {
285 (Some(s), _) => Ok(s.clone()),
286 (None, Some(e)) => Err(clone_err(e)),
287 _ => Ok(None),
288 }
289 }
290 fn fetch_line_index(&self, _line: &str) -> Result<Option<Vec<u8>>, SourceError> {
291 self.fetch_line_status(&LayerRef::Digest("x".into()))
292 }
293 fn fetch_attestations(
294 &self,
295 _l: &LayerRef,
296 ) -> Result<Vec<crate::attestcarry::CarriedAttestation>, SourceError> {
297 self.calls.set(self.calls.get() + 1);
298 match (&self.manifest, &self.err) {
299 (Some(_), _) => Ok(Vec::new()),
300 (None, Some(e)) => Err(clone_err(e)),
301 _ => Ok(Vec::new()),
302 }
303 }
304 }
305
306 fn clone_err(e: &SourceError) -> SourceError {
307 match e {
308 SourceError::NotFound(s) => SourceError::NotFound(s.clone()),
309 SourceError::Transport(s) => SourceError::Transport(s.clone()),
310 other => SourceError::Transport(other.to_string()),
311 }
312 }
313
314 fn mirrors(v: Vec<(&str, Box<Fake>)>) -> Mirrors {
315 Mirrors::new(
316 v.into_iter()
317 .map(|(l, s)| Mirror {
318 label: l.into(),
319 source: s as Box<dyn LayerSource>,
320 })
321 .collect(),
322 )
323 }
324
325 #[test]
327 fn the_first_source_that_answers_serves_the_layer() {
328 let m = mirrors(vec![("primary", ok(b"manifest")), ("mirror", ok(b"other"))]);
329 assert_eq!(
330 m.fetch_manifest(&LayerRef::Digest("d".into())).unwrap(),
331 b"manifest".to_vec()
332 );
333 assert_eq!(m.served_by().as_deref(), Some("primary"));
334 }
335
336 #[test]
339 fn an_unreachable_source_falls_through_to_the_next() {
340 let m = mirrors(vec![
341 ("primary", down("dial tcp: no such host")),
342 ("mirror", ok(b"manifest")),
343 ]);
344 assert_eq!(
345 m.fetch_manifest(&LayerRef::Digest("d".into())).unwrap(),
346 b"manifest".to_vec()
347 );
348 assert_eq!(m.served_by().as_deref(), Some("mirror"));
349 }
350
351 #[test]
355 fn a_source_that_lacks_the_layer_falls_through_too() {
356 let m = mirrors(vec![("primary", empty()), ("mirror", ok(b"manifest"))]);
357 assert!(m.fetch_manifest(&LayerRef::Digest("d".into())).is_ok());
358 assert_eq!(m.served_by().as_deref(), Some("mirror"));
359 }
360
361 #[test]
365 fn which_source_served_the_layer_is_reportable() {
366 let m = mirrors(vec![
367 ("oci://primary", down("503")),
368 ("oci://backup", ok(b"m")),
369 ]);
370 m.fetch_manifest(&LayerRef::Digest("d".into())).unwrap();
371 assert_eq!(m.served_by().as_deref(), Some("oci://backup"));
372 let attempts = m.attempts();
373 assert_eq!(attempts.len(), 1);
374 assert_eq!(attempts[0].0, "oci://primary");
375 assert!(attempts[0].1.contains("503"), "{attempts:?}");
376 }
377
378 #[test]
382 fn when_no_source_answers_the_error_names_all_of_them() {
383 let m = mirrors(vec![
384 ("oci://a", down("no such host")),
385 ("oci://b", down("503 Service Unavailable")),
386 ]);
387 let e = m
388 .fetch_manifest(&LayerRef::Digest("d".into()))
389 .expect_err("must fail");
390 let msg = e.to_string();
391 assert!(
392 msg.contains("oci://a") && msg.contains("no such host"),
393 "{msg}"
394 );
395 assert!(msg.contains("oci://b") && msg.contains("503"), "{msg}");
396 assert!(msg.contains("2 source(s)"), "{msg}");
397 assert!(m.served_by().is_none());
398 }
399
400 #[test]
404 fn a_realm_with_no_sources_says_so() {
405 let m = Mirrors::new(Vec::new());
406 let e = m
407 .fetch_manifest(&LayerRef::Digest("d".into()))
408 .expect_err("must fail");
409 assert!(e.to_string().contains("no sources at all"), "{e}");
410 }
411
412 #[test]
416 fn a_source_after_the_one_that_answered_is_not_consulted() {
417 let second = ok(b"second");
418 let counter = Rc::clone(&second.calls);
419 let m = mirrors(vec![("primary", ok(b"first")), ("mirror", second)]);
420 m.fetch_manifest(&LayerRef::Digest("d".into())).unwrap();
421 assert_eq!(counter.get(), 0, "the later source was consulted anyway");
422 }
423
424 #[test]
430 fn every_kind_of_fetch_falls_through_not_only_the_manifest() {
431 let m = mirrors(vec![("primary", down("503")), ("mirror", ok(b"bytes"))]);
432 assert_eq!(m.fetch_blob("sha256:x").unwrap(), b"bytes".to_vec());
433 assert_eq!(m.served_by().as_deref(), Some("mirror"));
434
435 let m = mirrors(vec![("primary", down("503")), ("mirror", ok(b"index"))]);
436 assert_eq!(
437 m.fetch_line_index("2026.09").unwrap(),
438 Some(b"index".to_vec())
439 );
440 assert_eq!(m.served_by().as_deref(), Some("mirror"));
441
442 let m = mirrors(vec![("primary", down("503")), ("mirror", ok(b"att"))]);
443 assert!(m.fetch_attestations(&LayerRef::Digest("d".into())).is_ok());
444 assert_eq!(m.served_by().as_deref(), Some("mirror"));
445 }
446
447 #[test]
451 fn every_kind_of_fetch_refuses_when_no_source_answers() {
452 let m = mirrors(vec![("a", down("no such host")), ("b", down("503"))]);
453 assert!(m.fetch_manifest(&LayerRef::Digest("d".into())).is_err());
454 assert!(m.fetch_blob("sha256:x").is_err());
455 assert!(m.fetch_line_index("2026.09").is_err());
456 assert!(m.fetch_attestations(&LayerRef::Digest("d".into())).is_err());
457 assert!(
458 m.served_by().is_none(),
459 "nothing served, yet a source is named"
460 );
461 }
462
463 #[test]
467 fn the_configured_sources_are_reportable_in_order() {
468 let m = mirrors(vec![("oci://a", ok(b"x")), ("oci://b", ok(b"y"))]);
469 assert_eq!(m.labels(), vec!["oci://a", "oci://b"]);
470 assert_eq!(Mirrors::new(Vec::new()).labels(), Vec::<&str>::new());
471 }
472
473 #[test]
478 fn a_document_no_source_carries_is_absent_not_unreachable() {
479 let m = mirrors(vec![("a", empty()), ("b", empty())]);
480 assert_eq!(
481 m.fetch_line_status(&LayerRef::Digest("d".into())).unwrap(),
482 None
483 );
484 }
485
486 #[test]
489 fn a_document_nobody_could_be_asked_about_is_not_reported_as_absent() {
490 let m = mirrors(vec![("a", down("no such host")), ("b", down("503"))]);
491 let e = m
492 .fetch_line_status(&LayerRef::Digest("d".into()))
493 .expect_err("must not report absence");
494 assert!(e.to_string().contains("could be reached"), "{e}");
495 }
496
497 #[test]
503 fn a_mirror_carrying_a_correction_is_reached_past_one_that_has_none() {
504 let m = mirrors(vec![("a", empty()), ("b", ok(b"correction"))]);
505 assert_eq!(
506 m.fetch_published_line_status("2026.09").unwrap(),
507 Some(b"correction".to_vec())
508 );
509 assert_eq!(m.served_by().as_deref(), Some("b"));
510 }
511
512 #[test]
517 fn a_correction_nobody_could_be_asked_about_is_not_reported_as_absent() {
518 let m = mirrors(vec![("a", down("no such host")), ("b", down("503"))]);
519 let e = m
520 .fetch_published_line_status("2026.09")
521 .expect_err("must not report absence");
522 assert!(e.to_string().contains("could be reached"), "{e}");
523 }
524
525 #[test]
529 fn a_line_with_no_correction_published_is_absent_not_an_error() {
530 let m = mirrors(vec![("a", empty()), ("b", empty())]);
531 assert_eq!(m.fetch_published_line_status("2026.09").unwrap(), None);
532 }
533
534 #[test]
538 fn a_source_carrying_the_document_is_reached_past_one_that_does_not() {
539 let m = mirrors(vec![("a", empty()), ("b", ok(b"status"))]);
540 assert_eq!(
541 m.fetch_line_status(&LayerRef::Digest("d".into())).unwrap(),
542 Some(b"status".to_vec())
543 );
544 assert_eq!(m.served_by().as_deref(), Some("b"));
545 }
546}