1use std::{path::PathBuf, sync::Arc};
5
6use crate::object::{
7 Action, ActionId, Blob, ContentHash, State, StateAttachment, StateAttachmentId, StateId, Tree,
8};
9
10pub mod actor_presence;
11pub mod agent_task;
12pub mod codec;
13pub mod fs;
14pub mod liveness;
15#[cfg(any(test, feature = "memory-backend"))]
16pub mod memory;
17pub use heddle_pack::store::pack;
18pub mod shallow;
19mod snapshot_commit;
20pub mod source;
21pub mod store_compliance;
22pub mod writer_lease;
23
24pub use actor_presence::{
25 ActorChainNode, ActorPresence, ActorPresenceStatus, ActorPresenceStore, AgentUsageSummary,
26 ContextQueryEntry, generate_actor_session_id,
27};
28pub use agent_task::{
29 AGENT_TASK_SCHEMA_VERSION, AgentTaskRecord, AgentTaskStatus, AgentTaskStore,
30 generate_agent_task_id, validate_task_id,
31};
32pub use fs::{
33 DEFAULT_PACK_INSTALL_INTENT_TTL_SECS, FsStore, PackInstallIntent, PackInstallMetricsSnapshot,
34 PackInstallPhase, PackInstallRecoverReport, install_pack_bytes_journaled,
35 pack_install_metrics_reset, pack_install_metrics_snapshot, recover_pack_install_intents,
36 recover_pack_install_intents_with_ttl,
37};
38pub use heddle_format::compression::{CompressionConfig, CompressionError, compress, decompress};
39pub use liveness::{
40 AGENT_LEASE_DURATION, Liveness, current_boot_id, process_alive, reservation_liveness_at,
41};
42#[cfg(any(test, feature = "memory-backend"))]
43pub use memory::InMemoryStore;
44pub use pack::{PackBuilder, PackObjectId, PackReader, PackStats, StreamingPackBuilder, SyncData};
45pub use shallow::ShallowInfo;
46#[doc(hidden)]
47pub use snapshot_commit::{
48 SNAPSHOT_COMMIT_ARTIFACT_SCHEMA, SnapshotCommitArtifact, SnapshotCommitDescriptor,
49 SnapshotPackManager,
50};
51#[cfg(feature = "async-source")]
52pub use source::AsyncObjectSource;
53pub use source::ObjectSource;
54pub use writer_lease::{
55 WriterLease, WriterLeaseAuthOutcome, WriterLeaseDraft, WriterLeaseGrant,
56 WriterLeaseReserveOutcome, WriterLeaseStatus, WriterLeaseStore, generate_writer_lease_id,
57 generate_writer_lease_token,
58};
59
60pub trait ExternalObjectSource: Send + Sync {
64 fn get_blob(&self, hash: &ContentHash) -> Result<Option<Blob>>;
65 fn get_tree(&self, hash: &ContentHash) -> Result<Option<Tree>>;
66 fn get_state(&self, id: &StateId) -> Result<Option<State>>;
67 fn list_states(&self) -> Result<Vec<StateId>>;
68}
69
70pub use crate::error::{HeddleError as StoreError, HeddleError, Result};
71
72#[derive(Clone)]
84pub enum AnyStore {
85 Fs(FsStore),
86}
87
88macro_rules! any_store_dispatch {
94 ($self:ident, $method:ident ( $($arg:expr),* )) => {
95 match $self {
96 AnyStore::Fs(inner) => inner.$method($($arg),*),
97 }
98 };
99}
100
101impl ObjectStore for AnyStore {
102 fn get_blob(&self, hash: &ContentHash) -> Result<Option<Blob>> {
103 match self {
104 AnyStore::Fs(inner) => ObjectStore::get_blob(inner, hash),
105 }
106 }
107 fn put_blob(&self, blob: &Blob) -> Result<ContentHash> {
108 any_store_dispatch!(self, put_blob(blob))
109 }
110 fn get_blob_bytes(&self, hash: &ContentHash) -> Result<Option<bytes::Bytes>> {
111 match self {
112 AnyStore::Fs(inner) => ObjectStore::get_blob_bytes(inner, hash),
113 }
114 }
115 fn blob_size(&self, hash: &ContentHash) -> Result<Option<u64>> {
116 any_store_dispatch!(self, blob_size(hash))
117 }
118 fn loose_blob_path(&self, hash: &ContentHash) -> Option<PathBuf> {
119 any_store_dispatch!(self, loose_blob_path(hash))
120 }
121 fn promote_to_loose_uncompressed(&self, hash: &ContentHash) -> Result<bool> {
122 any_store_dispatch!(self, promote_to_loose_uncompressed(hash))
123 }
124 fn clear_recent_caches(&self) {
125 any_store_dispatch!(self, clear_recent_caches())
126 }
127 fn put_blob_with_hash(&self, blob: &Blob, hash: ContentHash) -> Result<ContentHash> {
128 any_store_dispatch!(self, put_blob_with_hash(blob, hash))
129 }
130 fn has_blob(&self, hash: &ContentHash) -> Result<bool> {
131 any_store_dispatch!(self, has_blob(hash))
132 }
133 fn has_blob_locally(&self, hash: &ContentHash) -> Result<bool> {
134 any_store_dispatch!(self, has_blob_locally(hash))
135 }
136 fn get_tree(&self, hash: &ContentHash) -> Result<Option<Tree>> {
137 match self {
138 AnyStore::Fs(inner) => ObjectStore::get_tree(inner, hash),
139 }
140 }
141 fn get_tree_serialized(&self, hash: &ContentHash) -> Result<Option<Vec<u8>>> {
142 match self {
143 AnyStore::Fs(inner) => ObjectStore::get_tree_serialized(inner, hash),
144 }
145 }
146 fn put_tree(&self, tree: &Tree) -> Result<ContentHash> {
147 any_store_dispatch!(self, put_tree(tree))
148 }
149 fn has_tree(&self, hash: &ContentHash) -> Result<bool> {
150 any_store_dispatch!(self, has_tree(hash))
151 }
152 fn has_tree_locally(&self, hash: &ContentHash) -> Result<bool> {
153 any_store_dispatch!(self, has_tree_locally(hash))
154 }
155 fn get_state(&self, id: &StateId) -> Result<Option<State>> {
156 match self {
157 AnyStore::Fs(inner) => ObjectStore::get_state(inner, id),
158 }
159 }
160 fn put_state(&self, state: &State) -> Result<()> {
161 any_store_dispatch!(self, put_state(state))
162 }
163 fn has_state(&self, id: &StateId) -> Result<bool> {
164 any_store_dispatch!(self, has_state(id))
165 }
166 fn list_states(&self) -> Result<Vec<StateId>> {
167 any_store_dispatch!(self, list_states())
168 }
169 fn get_state_attachment(
170 &self,
171 state: &StateId,
172 id: &StateAttachmentId,
173 ) -> Result<Option<StateAttachment>> {
174 any_store_dispatch!(self, get_state_attachment(state, id))
175 }
176 fn put_state_attachment(&self, attachment: &StateAttachment) -> Result<StateAttachmentId> {
177 any_store_dispatch!(self, put_state_attachment(attachment))
178 }
179 fn list_state_attachments(&self, state: &StateId) -> Result<Vec<StateAttachment>> {
180 any_store_dispatch!(self, list_state_attachments(state))
181 }
182 fn get_action(&self, id: &ActionId) -> Result<Option<Action>> {
183 any_store_dispatch!(self, get_action(id))
184 }
185 fn put_action(&self, action: &mut Action) -> Result<ActionId> {
186 any_store_dispatch!(self, put_action(action))
187 }
188 fn list_actions(&self) -> Result<Vec<ActionId>> {
189 any_store_dispatch!(self, list_actions())
190 }
191 fn list_blobs(&self) -> Result<Vec<ContentHash>> {
192 any_store_dispatch!(self, list_blobs())
193 }
194 fn list_trees(&self) -> Result<Vec<ContentHash>> {
195 any_store_dispatch!(self, list_trees())
196 }
197 fn put_blob_bytes_with_hash(&self, data: &[u8], hash: ContentHash) -> Result<ContentHash> {
198 any_store_dispatch!(self, put_blob_bytes_with_hash(data, hash))
199 }
200 fn put_tree_serialized(&self, data: &[u8], hash: ContentHash) -> Result<ContentHash> {
201 match self {
202 AnyStore::Fs(inner) => ObjectStore::put_tree_serialized(inner, data, hash),
203 }
204 }
205 fn put_state_serialized(&self, data: &[u8], id: StateId) -> Result<()> {
206 any_store_dispatch!(self, put_state_serialized(data, id))
207 }
208 fn put_action_serialized(&self, data: &[u8], id: ActionId) -> Result<()> {
209 any_store_dispatch!(self, put_action_serialized(data, id))
210 }
211 fn get_pack_object(
212 &self,
213 id: &pack::PackObjectId,
214 ) -> Result<Option<(pack::ObjectType, Vec<u8>)>> {
215 any_store_dispatch!(self, get_pack_object(id))
216 }
217 fn put_blobs_packed(&self, blobs: Vec<(ContentHash, Vec<u8>)>) -> Result<()> {
218 any_store_dispatch!(self, put_blobs_packed(blobs))
219 }
220 fn put_snapshot_objects_packed(
221 &self,
222 blobs: Vec<(ContentHash, Vec<u8>)>,
223 tree: &Tree,
224 state: &State,
225 ) -> Result<()> {
226 any_store_dispatch!(self, put_snapshot_objects_packed(blobs, tree, state))
227 }
228 fn put_snapshot_objects_and_attachments_packed(
229 &self,
230 blobs: Vec<(ContentHash, Vec<u8>)>,
231 tree: &Tree,
232 state: &State,
233 attachments: Vec<StateAttachment>,
234 ) -> Result<()> {
235 any_store_dispatch!(
236 self,
237 put_snapshot_objects_and_attachments_packed(blobs, tree, state, attachments)
238 )
239 }
240
241 fn install_pack(&self, pack_data: &[u8], index_data: &[u8]) -> Result<Vec<pack::PackObjectId>> {
242 any_store_dispatch!(self, install_pack(pack_data, index_data))
243 }
244 fn install_pack_streaming(
245 &self,
246 pack_path: &std::path::Path,
247 index_path: &std::path::Path,
248 ) -> Result<Vec<pack::PackObjectId>> {
249 any_store_dispatch!(self, install_pack_streaming(pack_path, index_path))
250 }
251 fn pack_objects(&self, aggressive: bool) -> Result<(u64, u64)> {
252 any_store_dispatch!(self, pack_objects(aggressive))
253 }
254 fn prune_loose_objects(&self) -> Result<(u64, u64)> {
255 any_store_dispatch!(self, prune_loose_objects())
256 }
257 fn begin_snapshot_write_batch(&self) -> Result<()> {
258 any_store_dispatch!(self, begin_snapshot_write_batch())
259 }
260 fn flush_snapshot_write_batch(&self) -> Result<()> {
261 any_store_dispatch!(self, flush_snapshot_write_batch())
262 }
263 fn abort_snapshot_write_batch(&self) {
264 any_store_dispatch!(self, abort_snapshot_write_batch())
265 }
266 fn has_redactions_for_blob(&self, blob: &ContentHash) -> Result<bool> {
267 any_store_dispatch!(self, has_redactions_for_blob(blob))
268 }
269 fn get_redactions_bytes_for_blob(&self, blob: &ContentHash) -> Result<Option<Vec<u8>>> {
270 any_store_dispatch!(self, get_redactions_bytes_for_blob(blob))
271 }
272 fn put_redactions_bytes_for_blob(&self, blob: &ContentHash, bytes: &[u8]) -> Result<()> {
273 any_store_dispatch!(self, put_redactions_bytes_for_blob(blob, bytes))
274 }
275 fn list_blobs_with_redactions(&self) -> Result<Vec<ContentHash>> {
276 any_store_dispatch!(self, list_blobs_with_redactions())
277 }
278 fn has_state_visibility_for_state(&self, state: &StateId) -> Result<bool> {
279 any_store_dispatch!(self, has_state_visibility_for_state(state))
280 }
281 fn get_state_visibility_bytes_for_state(&self, state: &StateId) -> Result<Option<Vec<u8>>> {
282 any_store_dispatch!(self, get_state_visibility_bytes_for_state(state))
283 }
284 fn put_state_visibility_bytes_for_state(&self, state: &StateId, bytes: &[u8]) -> Result<()> {
285 any_store_dispatch!(self, put_state_visibility_bytes_for_state(state, bytes))
286 }
287 fn list_states_with_visibility(&self) -> Result<Vec<StateId>> {
288 any_store_dispatch!(self, list_states_with_visibility())
289 }
290}
291
292impl AnyStore {
293 pub fn set_external_source(&mut self, source: Arc<dyn ExternalObjectSource>) {
295 match self {
296 Self::Fs(store) => store.set_external_source(source),
297 }
298 }
299
300 #[doc(hidden)]
304 pub fn snapshot_commit_descriptors(&self) -> Result<Vec<SnapshotCommitDescriptor>> {
305 match self {
306 Self::Fs(store) => store.snapshot_commit_descriptors_impl(),
307 }
308 }
309
310 #[doc(hidden)]
314 pub fn snapshot_commit_descriptor_for_state(
315 &self,
316 state: &StateId,
317 ) -> Result<Option<SnapshotCommitDescriptor>> {
318 match self {
319 Self::Fs(store) => store.snapshot_commit_descriptor_for_state_impl(state),
320 }
321 }
322
323 #[doc(hidden)]
326 pub fn put_committed_snapshot_objects_packed(
327 &self,
328 blobs: Vec<(ContentHash, Vec<u8>)>,
329 tree: &Tree,
330 state: &State,
331 attachments: Vec<StateAttachment>,
332 artifact: SnapshotCommitArtifact,
333 ) -> Result<SnapshotCommitDescriptor> {
334 match self {
335 Self::Fs(store) => store.put_committed_snapshot_objects_packed_impl(
336 blobs,
337 tree,
338 state,
339 attachments,
340 artifact,
341 ),
342 }
343 }
344}
345
346pub trait ObjectStore: Send + Sync {
348 fn get_blob(&self, hash: &ContentHash) -> Result<Option<Blob>>;
349 fn put_blob(&self, blob: &Blob) -> Result<ContentHash>;
350
351 fn get_blob_bytes(&self, hash: &ContentHash) -> Result<Option<bytes::Bytes>> {
362 Ok(self
363 .get_blob(hash)?
364 .map(|blob| bytes::Bytes::from(blob.into_content())))
365 }
366
367 fn blob_size(&self, hash: &ContentHash) -> Result<Option<u64>> {
381 Ok(self.get_blob(hash)?.map(|blob| blob.content().len() as u64))
382 }
383
384 fn loose_blob_path(&self, _hash: &ContentHash) -> Option<PathBuf> {
396 None
397 }
398
399 fn promote_to_loose_uncompressed(&self, _hash: &ContentHash) -> Result<bool> {
436 Ok(false)
437 }
438
439 fn clear_recent_caches(&self) {}
448
449 fn put_blob_with_hash(&self, blob: &Blob, hash: ContentHash) -> Result<ContentHash> {
450 if blob.hash() != hash {
451 return Err(HeddleError::InvalidObject("blob hash mismatch".to_string()));
452 }
453 self.put_blob(blob)
454 }
455
456 fn has_blob(&self, hash: &ContentHash) -> Result<bool>;
457 fn has_blob_locally(&self, hash: &ContentHash) -> Result<bool> {
461 self.has_blob(hash)
462 }
463 fn get_tree(&self, hash: &ContentHash) -> Result<Option<Tree>>;
464 fn put_tree(&self, tree: &Tree) -> Result<ContentHash>;
465 fn has_tree(&self, hash: &ContentHash) -> Result<bool>;
466 fn has_tree_locally(&self, hash: &ContentHash) -> Result<bool> {
469 self.has_tree(hash)
470 }
471 fn get_state(&self, id: &StateId) -> Result<Option<State>>;
472 fn put_state(&self, state: &State) -> Result<()>;
473 fn has_state(&self, id: &StateId) -> Result<bool>;
474 fn list_states(&self) -> Result<Vec<StateId>>;
475 fn get_state_attachment(
476 &self,
477 _state: &StateId,
478 _id: &StateAttachmentId,
479 ) -> Result<Option<StateAttachment>> {
480 Ok(None)
481 }
482 fn put_state_attachment(&self, _attachment: &StateAttachment) -> Result<StateAttachmentId> {
483 Err(HeddleError::InvalidObject(
484 "object store does not support state attachments".to_string(),
485 ))
486 }
487 fn list_state_attachments(&self, _state: &StateId) -> Result<Vec<StateAttachment>> {
488 Ok(Vec::new())
489 }
490 fn get_action(&self, id: &ActionId) -> Result<Option<Action>>;
491 fn put_action(&self, action: &mut Action) -> Result<ActionId>;
492 fn list_actions(&self) -> Result<Vec<ActionId>>;
493 fn list_blobs(&self) -> Result<Vec<ContentHash>>;
494 fn list_trees(&self) -> Result<Vec<ContentHash>>;
495
496 fn put_blob_bytes_with_hash(&self, data: &[u8], hash: ContentHash) -> Result<ContentHash> {
497 self.put_blob_with_hash(&Blob::from_slice(data), hash)
498 }
499
500 fn get_tree_serialized(&self, hash: &ContentHash) -> Result<Option<Vec<u8>>> {
508 Ok(self
509 .get_tree(hash)?
510 .map(|tree| rmp_serde::to_vec(&tree))
511 .transpose()?)
512 }
513
514 fn put_tree_serialized(&self, data: &[u8], hash: ContentHash) -> Result<ContentHash> {
515 let tree: Tree = rmp_serde::from_slice(data)?;
516 tree.validate()?;
517 if tree.hash() != hash {
518 return Err(HeddleError::Corruption {
519 expected: hash,
520 found: tree.hash(),
521 });
522 }
523 self.put_tree(&tree)
524 }
525
526 fn put_state_serialized(&self, data: &[u8], id: StateId) -> Result<()> {
527 let state: State = rmp_serde::from_slice(data)?;
528 let found = state.id();
529 if found != id {
530 return Err(HeddleError::InvalidObject(format!(
531 "state id mismatch: expected {id}, computed {found}"
532 )));
533 }
534 self.put_state(&state)
535 }
536
537 fn put_action_serialized(&self, data: &[u8], id: ActionId) -> Result<()> {
538 let mut action: Action = rmp_serde::from_slice(data)?;
539 let found_id = action.compute_id();
540 if found_id != id {
541 return Err(HeddleError::InvalidObject(format!(
542 "action id mismatch: expected {}, found {}",
543 id, found_id
544 )));
545 }
546 let stored_id = self.put_action(&mut action)?;
547 if stored_id != id {
548 return Err(HeddleError::InvalidObject(format!(
549 "action id mismatch after write: expected {}, found {}",
550 id, stored_id
551 )));
552 }
553 Ok(())
554 }
555
556 fn get_pack_object(
557 &self,
558 id: &pack::PackObjectId,
559 ) -> Result<Option<(pack::ObjectType, Vec<u8>)>> {
560 match id {
561 pack::PackObjectId::Hash(hash) => {
562 if let Some(blob) = self.get_blob(hash)? {
563 return Ok(Some((pack::ObjectType::Blob, blob.content().to_vec())));
564 }
565 if let Some(tree) = self.get_tree(hash)? {
566 return Ok(Some((
567 pack::ObjectType::Tree,
568 rmp_serde::to_vec_named(&tree)?,
569 )));
570 }
571 if let Some(action) = self.get_action(&ActionId::from_hash(*hash))? {
572 return Ok(Some((
573 pack::ObjectType::Action,
574 rmp_serde::to_vec_named(&action)?,
575 )));
576 }
577 Ok(None)
578 }
579 pack::PackObjectId::StateId(change_id) => {
580 if let Some(state) = self.get_state(change_id)? {
581 Ok(Some((
582 pack::ObjectType::State,
583 rmp_serde::to_vec_named(&state)?,
584 )))
585 } else {
586 Ok(None)
587 }
588 }
589 }
590 }
591
592 fn put_blobs_packed(&self, blobs: Vec<(ContentHash, Vec<u8>)>) -> Result<()> {
602 for (hash, data) in blobs {
603 if !self.has_blob(&hash)? {
604 self.put_blob_bytes_with_hash(&data, hash)?;
605 }
606 }
607 Ok(())
608 }
609
610 fn put_snapshot_objects_packed(
615 &self,
616 blobs: Vec<(ContentHash, Vec<u8>)>,
617 tree: &Tree,
618 state: &State,
619 ) -> Result<()> {
620 self.put_blobs_packed(blobs)?;
621 self.put_tree(tree)?;
622 self.put_state(state)
623 }
624
625 fn put_snapshot_objects_and_attachments_packed(
629 &self,
630 blobs: Vec<(ContentHash, Vec<u8>)>,
631 tree: &Tree,
632 state: &State,
633 attachments: Vec<StateAttachment>,
634 ) -> Result<()> {
635 self.put_snapshot_objects_packed(blobs, tree, state)?;
636 for attachment in attachments {
637 self.put_state_attachment(&attachment)?;
638 }
639 Ok(())
640 }
641 fn install_pack(&self, pack_data: &[u8], index_data: &[u8]) -> Result<Vec<pack::PackObjectId>> {
642 let reader = pack::PackReader::from_slice(pack_data, index_data)?;
643 let ids = reader.list_ids();
644 for id in &ids {
645 let Some((obj_type, data)) = reader.get_object(id)? else {
646 continue;
647 };
648 match (id, obj_type) {
649 (pack::PackObjectId::Hash(hash), pack::ObjectType::Blob) => {
650 self.put_blob_bytes_with_hash(&data, *hash)?;
651 }
652 (pack::PackObjectId::Hash(hash), pack::ObjectType::Tree) => {
653 self.put_tree_serialized(&data, *hash)?;
654 }
655 (pack::PackObjectId::Hash(hash), pack::ObjectType::Action) => {
656 self.put_action_serialized(&data, ActionId::from_hash(*hash))?;
657 }
658 (pack::PackObjectId::StateId(change_id), pack::ObjectType::State) => {
659 self.put_state_serialized(&data, *change_id)?;
660 }
661 _ => {
662 return Err(HeddleError::InvalidObject(format!(
663 "unsupported native pack object: {:?} {:?}",
664 id, obj_type
665 )));
666 }
667 }
668 }
669 Ok(ids)
670 }
671
672 fn install_pack_streaming(
689 &self,
690 pack_path: &std::path::Path,
691 index_path: &std::path::Path,
692 ) -> Result<Vec<pack::PackObjectId>> {
693 let pack_data = std::fs::read(pack_path).map_err(StoreError::from)?;
694 let index_data = std::fs::read(index_path).map_err(StoreError::from)?;
695 let ids = self.install_pack(&pack_data, &index_data)?;
696 let _ = std::fs::remove_file(pack_path);
700 let _ = std::fs::remove_file(index_path);
701 Ok(ids)
702 }
703
704 fn pack_objects(&self, aggressive: bool) -> Result<(u64, u64)> {
705 let _ = aggressive;
706 Ok((0, 0))
707 }
708
709 fn prune_loose_objects(&self) -> Result<(u64, u64)> {
710 Ok((0, 0))
711 }
712
713 fn begin_snapshot_write_batch(&self) -> Result<()> {
714 Ok(())
715 }
716
717 fn flush_snapshot_write_batch(&self) -> Result<()> {
718 Ok(())
719 }
720
721 fn abort_snapshot_write_batch(&self) {}
722
723 fn has_redactions_for_blob(&self, _blob: &ContentHash) -> Result<bool> {
735 Ok(false)
736 }
737
738 fn get_redactions_bytes_for_blob(&self, _blob: &ContentHash) -> Result<Option<Vec<u8>>> {
746 Ok(None)
747 }
748
749 fn put_redactions_bytes_for_blob(&self, _blob: &ContentHash, _bytes: &[u8]) -> Result<()> {
758 Err(HeddleError::InvalidObject(
759 "this object store does not support persisting redactions".to_string(),
760 ))
761 }
762
763 fn list_blobs_with_redactions(&self) -> Result<Vec<ContentHash>> {
770 Ok(Vec::new())
771 }
772
773 fn has_state_visibility_for_state(&self, _state: &StateId) -> Result<bool> {
783 Ok(false)
784 }
785
786 fn get_state_visibility_bytes_for_state(&self, _state: &StateId) -> Result<Option<Vec<u8>>> {
792 Ok(None)
793 }
794
795 fn put_state_visibility_bytes_for_state(&self, _state: &StateId, _bytes: &[u8]) -> Result<()> {
800 Err(HeddleError::InvalidObject(
801 "this object store does not support persisting state visibility".to_string(),
802 ))
803 }
804
805 fn list_states_with_visibility(&self) -> Result<Vec<StateId>> {
809 Ok(Vec::new())
810 }
811}
812
813#[cfg(test)]
814mod any_store_tests {
815 use tempfile::TempDir;
816
817 use super::*;
818 use crate::object::{Attribution, Operation, Principal};
819
820 fn fs_any_store() -> (TempDir, AnyStore) {
821 let temp = TempDir::new().unwrap();
822 let store = FsStore::new(temp.path().join(".heddle"));
823 store.init().unwrap();
824 (temp, AnyStore::Fs(store))
825 }
826
827 #[test]
833 fn fs_variant_dispatches_every_object_store_method() {
834 let (_temp, store) = fs_any_store();
835
836 let blob = Blob::from("any-store dispatch blob");
838 let blob_hash = store.put_blob(&blob).unwrap();
839 assert_eq!(
840 ObjectStore::get_blob(&store, &blob_hash)
841 .unwrap()
842 .unwrap()
843 .content(),
844 blob.content()
845 );
846 assert!(store.has_blob(&blob_hash).unwrap());
847 assert_eq!(
848 ObjectStore::get_blob_bytes(&store, &blob_hash)
849 .unwrap()
850 .unwrap()
851 .as_ref(),
852 blob.content()
853 );
854 assert_eq!(
855 store.blob_size(&blob_hash).unwrap().unwrap(),
856 blob.content().len() as u64
857 );
858 assert!(store.loose_blob_path(&blob_hash).is_some());
859 store.promote_to_loose_uncompressed(&blob_hash).unwrap();
860 assert!(store.list_blobs().unwrap().contains(&blob_hash));
861
862 let bytes_blob = Blob::from("put-with-hash blob");
863 let bytes_hash = bytes_blob.hash();
864 assert_eq!(
865 store.put_blob_with_hash(&bytes_blob, bytes_hash).unwrap(),
866 bytes_hash
867 );
868 let raw_blob = Blob::from("raw bytes blob");
869 let raw_hash = raw_blob.hash();
870 assert_eq!(
871 store
872 .put_blob_bytes_with_hash(raw_blob.content(), raw_hash)
873 .unwrap(),
874 raw_hash
875 );
876
877 let tree = Tree::new();
879 let tree_hash = store.put_tree(&tree).unwrap();
880 assert!(ObjectStore::get_tree(&store, &tree_hash).unwrap().is_some());
881 assert!(store.has_tree(&tree_hash).unwrap());
882 assert!(store.list_trees().unwrap().contains(&tree_hash));
883 let tree2 = Tree::new();
884 let tree2_bytes = rmp_serde::to_vec_named(&tree2).unwrap();
885 assert_eq!(
886 store
887 .put_tree_serialized(&tree2_bytes, tree2.hash())
888 .unwrap(),
889 tree2.hash()
890 );
891
892 let attribution =
894 Attribution::human(Principal::new("AnyStore Test", "anystore@example.com"));
895 let state = State::new(tree_hash, vec![], attribution.clone());
896 let state_id = state.id();
897 store.put_state(&state).unwrap();
898 assert!(ObjectStore::get_state(&store, &state_id).unwrap().is_some());
899 assert!(store.has_state(&state_id).unwrap());
900 assert!(store.list_states().unwrap().contains(&state_id));
901 let state2 = State::new(tree2.hash(), vec![], attribution.clone());
902 let state2_bytes = rmp_serde::to_vec_named(&state2).unwrap();
903 store
904 .put_state_serialized(&state2_bytes, state2.id())
905 .unwrap();
906
907 let mut action = Action::new(
909 None,
910 StateId::from_bytes([3; 32]),
911 Operation::Snapshot,
912 "any-store action",
913 attribution,
914 );
915 let action_id = store.put_action(&mut action).unwrap();
916 assert!(store.get_action(&action_id).unwrap().is_some());
917 assert!(store.list_actions().unwrap().contains(&action_id));
918 let action_bytes = rmp_serde::to_vec_named(&action).unwrap();
919 store
920 .put_action_serialized(&action_bytes, action_id)
921 .unwrap();
922
923 let packed = Blob::from("packed-via-any-store");
925 let packed_hash = packed.hash();
926 store
927 .put_blobs_packed(vec![(packed_hash, packed.into_content())])
928 .unwrap();
929 assert!(
930 store
931 .get_pack_object(&pack::PackObjectId::Hash(packed_hash))
932 .unwrap()
933 .is_some()
934 );
935 store.pack_objects(false).unwrap();
936 store.prune_loose_objects().unwrap();
937 let _ = store.install_pack(&[], &[]);
941 let _ = store.install_pack_streaming(
942 std::path::Path::new("/nonexistent/pack"),
943 std::path::Path::new("/nonexistent/idx"),
944 );
945
946 store.begin_snapshot_write_batch().unwrap();
948 store.flush_snapshot_write_batch().unwrap();
949 store.begin_snapshot_write_batch().unwrap();
950 store.abort_snapshot_write_batch();
951
952 let redaction = b"any-store redaction bytes";
954 store
955 .put_redactions_bytes_for_blob(&blob_hash, redaction)
956 .unwrap();
957 assert!(store.has_redactions_for_blob(&blob_hash).unwrap());
958 assert_eq!(
959 store
960 .get_redactions_bytes_for_blob(&blob_hash)
961 .unwrap()
962 .as_deref(),
963 Some(redaction.as_slice())
964 );
965 assert!(
966 store
967 .list_blobs_with_redactions()
968 .unwrap()
969 .contains(&blob_hash)
970 );
971
972 let state_visibility = b"any-store state visibility bytes";
974 store
975 .put_state_visibility_bytes_for_state(&state_id, state_visibility)
976 .unwrap();
977 assert!(store.has_state_visibility_for_state(&state_id).unwrap());
978 assert_eq!(
979 store
980 .get_state_visibility_bytes_for_state(&state_id)
981 .unwrap()
982 .as_deref(),
983 Some(state_visibility.as_slice())
984 );
985 assert!(
986 store
987 .list_states_with_visibility()
988 .unwrap()
989 .contains(&state_id)
990 );
991
992 store.clear_recent_caches();
994 }
995}