greentic_distributor_client/
source.rs1use crate::{ComponentId, DistributorError, PackId, Version};
2
3pub trait DistributorSource: Send + Sync {
5 fn fetch_pack(&self, pack_id: &PackId, version: &Version) -> Result<Vec<u8>, DistributorError>;
6
7 fn fetch_component(
8 &self,
9 component_id: &ComponentId,
10 version: &Version,
11 ) -> Result<Vec<u8>, DistributorError>;
12}
13
14pub struct ChainedDistributorSource {
16 sources: Vec<Box<dyn DistributorSource>>,
17}
18
19impl ChainedDistributorSource {
20 pub fn new(sources: Vec<Box<dyn DistributorSource>>) -> Self {
21 Self { sources }
22 }
23}
24
25impl DistributorSource for ChainedDistributorSource {
26 fn fetch_pack(&self, pack_id: &PackId, version: &Version) -> Result<Vec<u8>, DistributorError> {
27 for source in &self.sources {
28 match source.fetch_pack(pack_id, version) {
29 Ok(bytes) => return Ok(bytes),
30 Err(DistributorError::NotFound) => continue,
31 Err(err) => return Err(err),
32 }
33 }
34 Err(DistributorError::NotFound)
35 }
36
37 fn fetch_component(
38 &self,
39 component_id: &ComponentId,
40 version: &Version,
41 ) -> Result<Vec<u8>, DistributorError> {
42 for source in &self.sources {
43 match source.fetch_component(component_id, version) {
44 Ok(bytes) => return Ok(bytes),
45 Err(DistributorError::NotFound) => continue,
46 Err(err) => return Err(err),
47 }
48 }
49 Err(DistributorError::NotFound)
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56 use std::collections::HashMap;
57
58 struct MemorySource {
59 packs: HashMap<(PackId, Version), Vec<u8>>,
60 components: HashMap<(ComponentId, Version), Vec<u8>>,
61 error: Option<String>,
62 }
63
64 impl MemorySource {
65 fn new() -> Self {
66 Self {
67 packs: HashMap::new(),
68 components: HashMap::new(),
69 error: None,
70 }
71 }
72
73 fn with_error(err: impl Into<String>) -> Self {
74 Self {
75 packs: HashMap::new(),
76 components: HashMap::new(),
77 error: Some(err.into()),
78 }
79 }
80 }
81
82 impl DistributorSource for MemorySource {
83 fn fetch_pack(
84 &self,
85 pack_id: &PackId,
86 version: &Version,
87 ) -> Result<Vec<u8>, DistributorError> {
88 if let Some(err) = &self.error {
89 return Err(DistributorError::Other(err.clone()));
90 }
91 self.packs
92 .get(&(pack_id.clone(), version.clone()))
93 .cloned()
94 .ok_or(DistributorError::NotFound)
95 }
96
97 fn fetch_component(
98 &self,
99 component_id: &ComponentId,
100 version: &Version,
101 ) -> Result<Vec<u8>, DistributorError> {
102 if let Some(err) = &self.error {
103 return Err(DistributorError::Other(err.clone()));
104 }
105 self.components
106 .get(&(component_id.clone(), version.clone()))
107 .cloned()
108 .ok_or(DistributorError::NotFound)
109 }
110 }
111
112 #[test]
113 fn chained_prefers_first_success() {
114 let version = Version::parse("1.0.0").unwrap();
115 let pack_id = PackId::try_from("pack.one").unwrap();
116 let mut primary = MemorySource::new();
117 primary
118 .packs
119 .insert((pack_id.clone(), version.clone()), b"pack".to_vec());
120 let chained =
121 ChainedDistributorSource::new(vec![Box::new(primary), Box::new(MemorySource::new())]);
122
123 let bytes = chained.fetch_pack(&pack_id, &version).unwrap();
124 assert_eq!(bytes, b"pack");
125 }
126
127 #[test]
128 fn chained_skips_not_found_continues() {
129 let version = Version::parse("1.0.0").unwrap();
130 let pack_id = PackId::try_from("pack.missing").unwrap();
131 let mut fallback = MemorySource::new();
132 fallback
133 .packs
134 .insert((pack_id.clone(), version.clone()), b"found".to_vec());
135 let chained =
136 ChainedDistributorSource::new(vec![Box::new(MemorySource::new()), Box::new(fallback)]);
137
138 let bytes = chained.fetch_pack(&pack_id, &version).unwrap();
139 assert_eq!(bytes, b"found");
140 }
141
142 #[test]
143 fn chained_propagates_other_errors() {
144 let version = Version::parse("1.0.0").unwrap();
145 let pack_id = PackId::try_from("pack.error").unwrap();
146 let chained =
147 ChainedDistributorSource::new(vec![Box::new(MemorySource::with_error("boom"))]);
148
149 let err = chained.fetch_pack(&pack_id, &version).unwrap_err();
150 assert!(matches!(err, DistributorError::Other(msg) if msg == "boom"));
151 }
152}