//! vcs-core: the vcs data model with no I/O. //! //! Content is addressed by SHA-256. A project is a set of objects (blobs, //! chunk lists for big files, trees) plus a numbered line of versions (main) //! and changes that land on it. Hosts (the CLI, the self-hosted server and the //! hosted server in Wasm) own storage and the network; everything here works //! on bytes and on a [`Source`] that hands objects back by ID. pub mod bundle; pub mod chunk; pub mod diff; pub mod filter; pub mod id; pub mod merge; pub mod object; pub mod secrets; pub mod tree; pub mod version; pub use id::Id; pub use object::{Kind, Object}; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("missing object {0}")] Missing(Id), #[error("{0}")] Corrupt(String), #[error("limit exceeded: {0}")] Limit(String), } pub type Result = std::result::Result; pub(crate) fn corrupt(message: impl Into) -> Error { Error::Corrupt(message.into()) } /// Where objects come from. Hosts implement it over their storage. pub trait Source { fn get(&self, id: &Id) -> Result>; fn must(&self, id: &Id) -> Result { self.get(id)?.ok_or(Error::Missing(*id)) } } /// An in-memory source, for tests and for objects a merge creates. #[derive(Default, Clone)] pub struct MemSource { pub objects: std::collections::HashMap, } impl MemSource { pub fn put(&mut self, object: Object) -> Id { let id = object.id(); self.objects.insert(id, object); id } } impl Source for MemSource { fn get(&self, id: &Id) -> Result> { Ok(self.objects.get(id).cloned()) } } /// Reads from `first`, then `second` (new objects over a store). pub struct Layered<'a> { pub first: &'a dyn Source, pub second: &'a dyn Source, } impl Source for Layered<'_> { fn get(&self, id: &Id) -> Result> { match self.first.get(id)? { Some(o) => Ok(Some(o)), None => self.second.get(id), } } }