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