automerge/rust/automerge/src/storage/load.rs
Alex Good 0f90fe4d02 Add a method for loading a document without verifying heads
This is primarily useful when debugging documents which have been
corrupted somehow so you would like to see the ops even if you can't
trust them. Note that this is _not_ currently useful for performance
reasons as the hash graph is still constructed, just not verified.
2022-12-19 16:30:14 +00:00

120 lines
4.3 KiB
Rust

use tracing::instrument;
use crate::{
change::Change,
storage::{self, parse},
};
mod change_collector;
mod reconstruct_document;
pub(crate) use reconstruct_document::{
reconstruct_document, DocObserver, LoadedObject, Reconstructed, VerificationMode,
};
#[derive(Debug, thiserror::Error)]
#[allow(unreachable_pub)]
pub enum Error {
#[error("unable to parse chunk: {0}")]
Parse(Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("invalid change columns: {0}")]
InvalidChangeColumns(Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("invalid ops columns: {0}")]
InvalidOpsColumns(Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("a chunk contained leftover data")]
LeftoverData,
#[error("error inflating document chunk ops: {0}")]
InflateDocument(Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("bad checksum")]
BadChecksum,
}
pub(crate) enum LoadedChanges<'a> {
/// All the data was succesfully loaded into a list of changes
Complete(Vec<Change>),
/// We only managed to load _some_ changes.
Partial {
/// The succesfully loaded changes
loaded: Vec<Change>,
/// The data which we were unable to parse
#[allow(dead_code)]
remaining: parse::Input<'a>,
/// The error encountered whilst trying to parse `remaining`
error: Error,
},
}
/// Attempt to Load all the chunks in `data`.
///
/// # Partial Loads
///
/// Automerge documents are encoded as one or more concatenated chunks. Each chunk containing one
/// or more changes. This means it is possible to partially load corrupted data if the first `n`
/// chunks are valid. This function returns a `LoadedChanges` which you can examine to determine if
/// this is the case.
#[instrument(skip(data))]
pub(crate) fn load_changes<'a>(mut data: parse::Input<'a>) -> LoadedChanges<'a> {
let mut changes = Vec::new();
while !data.is_empty() {
let remaining = match load_next_change(data, &mut changes) {
Ok(d) => d,
Err(e) => {
return LoadedChanges::Partial {
loaded: changes,
remaining: data,
error: e,
};
}
};
data = remaining.reset();
}
LoadedChanges::Complete(changes)
}
fn load_next_change<'a>(
data: parse::Input<'a>,
changes: &mut Vec<Change>,
) -> Result<parse::Input<'a>, Error> {
let (remaining, chunk) = storage::Chunk::parse(data).map_err(|e| Error::Parse(Box::new(e)))?;
if !chunk.checksum_valid() {
return Err(Error::BadChecksum);
}
match chunk {
storage::Chunk::Document(d) => {
tracing::trace!("loading document chunk");
let Reconstructed {
changes: new_changes,
..
} = reconstruct_document(&d, VerificationMode::DontCheck, NullObserver)
.map_err(|e| Error::InflateDocument(Box::new(e)))?;
changes.extend(new_changes);
}
storage::Chunk::Change(change) => {
tracing::trace!("loading change chunk");
let change = Change::new_from_unverified(change.into_owned(), None)
.map_err(|e| Error::InvalidChangeColumns(Box::new(e)))?;
#[cfg(debug_assertions)]
{
let loaded_ops = change.iter_ops().collect::<Vec<_>>();
tracing::trace!(actor=?change.actor_id(), num_ops=change.len(), ops=?loaded_ops, "loaded change");
}
#[cfg(not(debug_assertions))]
tracing::trace!(actor=?change.actor_id(), num_ops=change.len(), "loaded change");
changes.push(change);
}
storage::Chunk::CompressedChange(change, compressed) => {
tracing::trace!("loading compressed change chunk");
let change =
Change::new_from_unverified(change.into_owned(), Some(compressed.into_owned()))
.map_err(|e| Error::InvalidChangeColumns(Box::new(e)))?;
changes.push(change);
}
};
Ok(remaining)
}
struct NullObserver;
impl DocObserver for NullObserver {
type Output = ();
fn finish(self, _metadata: crate::op_tree::OpSetMetadata) -> Self::Output {}
fn object_loaded(&mut self, _object: LoadedObject) {}
}