triblespace_core/blob/schemas/
simplearchive.rs1use crate::blob::Blob;
2use crate::blob::BlobSchema;
3use crate::blob::ToBlob;
4use crate::blob::TryFromBlob;
5use crate::id::ExclusiveId;
6use crate::id::Id;
7use crate::id_hex;
8use crate::macros::entity;
9use crate::metadata;
10use crate::metadata::ConstMetadata;
11use crate::repo::BlobStore;
12use crate::trible::Trible;
13use crate::trible::TribleSet;
14use crate::value::schemas::hash::Blake3;
15
16use anybytes::Bytes;
17use anybytes::View;
18
19pub struct SimpleArchive;
20
21impl BlobSchema for SimpleArchive {}
22
23impl ConstMetadata for SimpleArchive {
24 fn id() -> Id {
25 id_hex!("8F4A27C8581DADCBA1ADA8BA228069B6")
26 }
27
28 fn describe<B>(blobs: &mut B) -> Result<TribleSet, B::PutError>
29 where
30 B: BlobStore<Blake3>,
31 {
32 let id = Self::id();
33 let description = blobs.put(
34 "Canonical trible sequence stored as raw 64-byte entries. This is the simplest portable archive format and preserves the exact trible ordering expected by the canonicalization rules.\n\nUse SimpleArchive for export, import, streaming, hashing, or audit trails where you want a byte-for-byte stable representation. Prefer SuccinctArchiveBlob when you need compact indexed storage and fast offline queries, and keep a SimpleArchive around if you want a source of truth that can be re-indexed or validated.",
35 )?;
36 Ok(entity! {
37 ExclusiveId::force_ref(&id) @
38 metadata::name: blobs.put("simplearchive".to_string())?,
39 metadata::description: description,
40 metadata::tag: metadata::KIND_BLOB_SCHEMA,
41 })
42 }
43}
44
45impl ToBlob<SimpleArchive> for TribleSet {
46 fn to_blob(self) -> Blob<SimpleArchive> {
47 let mut tribles: Vec<[u8; 64]> = Vec::with_capacity(self.len());
48 tribles.extend(self.eav.iter_ordered());
49 let bytes: Bytes = tribles.into();
50 Blob::new(bytes)
51 }
52}
53
54impl ToBlob<SimpleArchive> for &TribleSet {
55 fn to_blob(self) -> Blob<SimpleArchive> {
56 let mut tribles: Vec<[u8; 64]> = Vec::with_capacity(self.len());
57 tribles.extend(self.eav.iter_ordered());
58 let bytes: Bytes = tribles.into();
59 Blob::new(bytes)
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum UnarchiveError {
65 BadArchive,
66 BadTrible,
67 BadCanonicalizationRedundancy,
68 BadCanonicalizationOrdering,
69}
70
71impl std::fmt::Display for UnarchiveError {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 match self {
74 UnarchiveError::BadArchive => write!(f, "The archive is malformed or invalid."),
75 UnarchiveError::BadTrible => write!(f, "A trible in the archive is malformed."),
76 UnarchiveError::BadCanonicalizationRedundancy => {
77 write!(f, "The archive contains redundant tribles.")
78 }
79 UnarchiveError::BadCanonicalizationOrdering => {
80 write!(f, "The tribles in the archive are not in canonical order.")
81 }
82 }
83 }
84}
85
86impl std::error::Error for UnarchiveError {}
87
88impl TryFromBlob<SimpleArchive> for TribleSet {
89 type Error = UnarchiveError;
90
91 fn try_from_blob(blob: Blob<SimpleArchive>) -> Result<Self, Self::Error> {
92 let mut tribles = TribleSet::new();
93
94 let mut prev_trible = None;
95 let Ok(packed_tribles): Result<View<[[u8; 64]]>, _> = blob.bytes.clone().view() else {
96 return Err(UnarchiveError::BadArchive);
97 };
98 for t in packed_tribles.iter() {
99 if let Some(trible) = Trible::as_transmute_force_raw(t) {
100 if let Some(prev) = prev_trible {
101 if prev == t {
102 return Err(UnarchiveError::BadCanonicalizationRedundancy);
103 }
104 if prev > t {
105 return Err(UnarchiveError::BadCanonicalizationOrdering);
106 }
107 }
108 prev_trible = Some(t);
109 tribles.insert(trible);
110 } else {
111 return Err(UnarchiveError::BadTrible);
112 }
113 }
114
115 Ok(tribles)
116 }
117}