eat-rocks 0.1.1

Restore a rocks database from object storage
Documentation
//! Restore a [rocks backup](https://github.com/facebook/rocksdb/wiki/How-to-backup-RocksDB)
//! from object storage
//!
//! ```rust,no_run
//! use eat_rocks::{public_bucket, restore};
//! # async fn example() -> Result<(), eat_rocks::Error> {
//! let store = public_bucket("https://constellation.t3.storage.dev")?;
//! restore(store, "", "/tmp/constellation-db", Default::default()).await?;
//! # Ok(())
//! # }
//! ```
//!
//! the `public_bucket` function (`easy` feature, enabled by default) works with
//! s3-compatible stores (like tigris) which are configured for public read access.
//!
//! you can use any [`ObjectStore`](object_store::ObjectStore) to restore from pretty much any backend (S3, GCS, Azure, local filesystem, ...)

pub mod meta;
pub mod restore;

pub use meta::{BackupFile, BackupMeta};
pub use restore::{DEFAULT_CONCURRENCY, RestoreOptions, fetch_meta, list_backup_ids, restore};

use std::io;
use std::path::PathBuf;
use std::string::FromUtf8Error;

use object_store::path::Path as StorePath;

/// Build an unsigned s3-compatible object store
///
/// `endpoint` should include the bucket as a subdomain for virtual-hosted style:
///
/// ```text
/// https://constellation.t3.storage.dev
/// ```
///
/// you can construct your own [`object_store::ObjectStore`] implementation directly for more control.
#[cfg(feature = "easy")]
pub fn public_bucket(
    endpoint: &str,
) -> Result<std::sync::Arc<dyn object_store::ObjectStore>, Error> {
    use object_store::aws::AmazonS3Builder;
    use std::sync::Arc;

    let store = AmazonS3Builder::new()
        .with_endpoint(endpoint)
        .with_bucket_name("_")
        .with_skip_signature(true)
        .with_allow_http(true)
        .with_virtual_hosted_style_request(true)
        .build()
        .map_err(|e| Error::ObjectStoreBuild {
            endpoint: endpoint.to_string(),
            source: e,
        })?;

    Ok(Arc::new(store))
}

/// Errors that can occur during backup discovery, metadata parsing, or restore.
#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[cfg(feature = "easy")]
    #[error("failed to build object store")]
    ObjectStoreBuild {
        endpoint: String,
        #[source]
        source: object_store::Error,
    },

    #[error("failed to list objects under {prefix:?}")]
    List {
        prefix: StorePath,
        #[source]
        source: object_store::Error,
    },

    #[error("failed to fetch {key:?}")]
    Fetch {
        key: StorePath,
        #[source]
        source: object_store::Error,
    },

    #[error("meta file for backup {backup_id} is not valid UTF-8")]
    MetaEncoding {
        backup_id: u64,
        #[source]
        source: FromUtf8Error,
    },

    #[error("failed to parse meta file for backup {backup_id}")]
    MetaParse {
        backup_id: u64,
        #[source]
        source: meta::ParseError,
    },

    #[error("no backups found")]
    NoBackups,

    #[error("backup contains {count} excluded file(s) (unsupported)")]
    ExcludedFiles { count: usize },

    #[error("backup path has unrecognized prefix: {0:?}")]
    UnrecognizedPathPrefix(String),

    #[error("shared_checksum filename missing underscore: {0:?}")]
    SharedChecksumNoUnderscore(String),

    #[error("shared_checksum filename missing extension: {0:?}")]
    SharedChecksumNoExtension(String),

    #[error("private/ path has too few components: {0:?}")]
    PrivatePathTooShort(String),

    #[error("size mismatch on {path}: expected {expected} bytes, got {actual}")]
    SizeMismatch {
        path: String,
        expected: u64,
        actual: u64,
    },

    #[error("crc32c mismatch on {path}: expected {expected:#010x}, got {actual:#010x}")]
    ChecksumMismatch {
        path: String,
        expected: u32,
        actual: u32,
    },

    #[error("{}: {source}", path.display())]
    Io {
        path: PathBuf,
        #[source]
        source: io::Error,
    },
}