wire/
object_availability.rs1use objects::store::ObjectStore;
3
4use crate::{ObjectId, ObjectInfo, ObjectType, Result};
5
6#[derive(Debug, Clone, Default, PartialEq, Eq)]
7pub struct ObjectAvailabilityPlan {
8 pub have_objects: Vec<ObjectId>,
9 pub want_objects: Vec<ObjectId>,
10 pub partial_fetch_allowed: bool,
11}
12
13pub fn has_object(store: &impl ObjectStore, info: &ObjectInfo) -> Result<bool> {
14 match (&info.id, info.obj_type) {
15 (ObjectId::Hash(hash), ObjectType::Blob) => Ok(store.has_blob(hash)?),
16 (ObjectId::Hash(hash), ObjectType::Tree) => Ok(store.has_tree(hash)?),
17 (ObjectId::StateId(state_id), ObjectType::State) => Ok(store.has_state(state_id)?),
18 (ObjectId::StateAttachment { state, id, kind: _ }, ObjectType::StateAttachment) => {
19 Ok(store.get_state_attachment(state, id)?.is_some())
20 }
21 (ObjectId::Hash(_), ObjectType::Redaction) => Ok(false),
28 (ObjectId::Hash(_), ObjectType::Purge) => Ok(false),
31 (ObjectId::StateId(_), ObjectType::StateVisibility) => Ok(false),
35 (ObjectId::Hash(_), ObjectType::KeyBinding) => Ok(false),
38 _ => Ok(false),
39 }
40}
41
42pub fn plan_object_availability(
43 store: &impl ObjectStore,
44 objects: &[ObjectInfo],
45) -> Result<ObjectAvailabilityPlan> {
46 let mut plan = ObjectAvailabilityPlan::default();
47
48 for info in objects {
49 if has_object(store, info)? {
50 plan.have_objects.push(info.id.clone());
51 } else {
52 plan.want_objects.push(info.id.clone());
53 }
54 }
55
56 Ok(plan)
57}
58
59impl ObjectAvailabilityPlan {
60 pub fn with_partial_fetch_allowed(mut self, allowed: bool) -> Self {
61 self.partial_fetch_allowed = allowed;
62 self
63 }
64
65 pub fn is_complete(&self) -> bool {
66 self.want_objects.is_empty()
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use objects::{
73 object::{Blob, ContentHash, StateId, Tree},
74 store::{ObjectStore, Result as StoreResult},
75 };
76
77 use super::*;
78
79 #[derive(Default)]
80 struct DummyStore {
81 blob: Option<ContentHash>,
82 state: Option<StateId>,
83 }
84
85 impl ObjectStore for DummyStore {
86 fn get_blob(&self, _hash: &ContentHash) -> StoreResult<Option<Blob>> {
87 Ok(None)
88 }
89
90 fn put_blob(&self, _blob: &Blob) -> StoreResult<ContentHash> {
91 unreachable!("not used in test")
92 }
93
94 fn has_blob(&self, hash: &ContentHash) -> StoreResult<bool> {
95 Ok(self.blob == Some(*hash))
96 }
97
98 fn get_tree(&self, _hash: &ContentHash) -> StoreResult<Option<Tree>> {
99 Ok(None)
100 }
101
102 fn put_tree(&self, _tree: &Tree) -> StoreResult<ContentHash> {
103 unreachable!("not used in test")
104 }
105
106 fn has_tree(&self, _hash: &ContentHash) -> StoreResult<bool> {
107 Ok(false)
108 }
109
110 fn get_state(&self, _id: &StateId) -> StoreResult<Option<objects::object::State>> {
111 Ok(None)
112 }
113
114 fn put_state(&self, _state: &objects::object::State) -> StoreResult<()> {
115 unreachable!("not used in test")
116 }
117
118 fn has_state(&self, id: &StateId) -> StoreResult<bool> {
119 Ok(self.state == Some(*id))
120 }
121
122 fn list_states(&self) -> StoreResult<Vec<StateId>> {
123 Ok(vec![])
124 }
125
126 fn get_action(
127 &self,
128 _id: &objects::object::ActionId,
129 ) -> StoreResult<Option<objects::object::Action>> {
130 Ok(None)
131 }
132
133 fn put_action(
134 &self,
135 _action: &mut objects::object::Action,
136 ) -> StoreResult<objects::object::ActionId> {
137 unreachable!("not used in test")
138 }
139
140 fn list_actions(&self) -> StoreResult<Vec<objects::object::ActionId>> {
141 Ok(vec![])
142 }
143
144 fn list_blobs(&self) -> StoreResult<Vec<ContentHash>> {
145 Ok(vec![])
146 }
147
148 fn list_trees(&self) -> StoreResult<Vec<ContentHash>> {
149 Ok(vec![])
150 }
151 }
152
153 #[test]
154 fn test_plan_tracks_available_and_wanted_objects() {
155 let blob = Blob::new(b"hello".to_vec());
156 let blob_hash = blob.hash();
157 let store = DummyStore {
158 blob: Some(blob_hash),
159 state: None,
160 };
161 let missing_hash = ContentHash::from_bytes([7; 32]);
162 let objects = vec![
163 ObjectInfo {
164 id: ObjectId::Hash(blob_hash),
165 obj_type: ObjectType::Blob,
166 size: blob.size() as u64,
167 delta_base: None,
168 },
169 ObjectInfo {
170 id: ObjectId::Hash(missing_hash),
171 obj_type: ObjectType::Tree,
172 size: 0,
173 delta_base: None,
174 },
175 ];
176
177 let plan = plan_object_availability(&store, &objects).unwrap();
178
179 assert_eq!(plan.have_objects.len(), 1);
180 assert_eq!(plan.want_objects.len(), 1);
181 assert!(!plan.is_complete());
182 }
183
184 #[test]
185 fn missing_state_objects_are_requested() {
186 let store = DummyStore::default();
187 let state = StateId::from_bytes([9; 32]);
188 let objects = vec![ObjectInfo {
189 id: ObjectId::StateId(state),
190 obj_type: ObjectType::State,
191 size: 0,
192 delta_base: None,
193 }];
194
195 let plan = plan_object_availability(&store, &objects).unwrap();
196
197 assert!(plan.have_objects.is_empty());
198 assert_eq!(plan.want_objects, vec![ObjectId::StateId(state)]);
199 }
200
201 #[test]
202 fn immutable_state_objects_are_not_requested_when_present() {
203 let state = StateId::from_bytes([9; 32]);
204 let store = DummyStore {
205 state: Some(state),
206 ..DummyStore::default()
207 };
208 let objects = vec![ObjectInfo {
209 id: ObjectId::StateId(state),
210 obj_type: ObjectType::State,
211 size: 0,
212 delta_base: None,
213 }];
214
215 let plan = plan_object_availability(&store, &objects).unwrap();
216
217 assert_eq!(plan.have_objects, vec![ObjectId::StateId(state)]);
218 assert!(plan.want_objects.is_empty());
219 }
220
221 #[test]
222 fn test_partial_fetch_flag_helpers() {
223 let plan = ObjectAvailabilityPlan::default().with_partial_fetch_allowed(true);
224
225 assert!(plan.partial_fetch_allowed);
226 assert!(plan.is_complete());
227 }
228}