# snapdir for Rust The [`snapdir-api`](https://crates.io/crates/snapdir-api) crate is snapdir's canonical core surface: the stable, semver-versioned async facade over `snapdir-core` that the CLI and every other binding consume. The manifests and snapshot IDs it produces are bit-identical to `snapdir id`, `snapdir push`, and every other binding. Every public function returns `Result` — no `anyhow` leaks into the surface. ## Install ```sh cargo add snapdir-api cargo add tokio --features full # async runtime for push/pull/fetch/diff/sync ``` **Native crate — built from source by cargo.** Unlike the C/C++, Zig, and Go bindings, Rust does not go through the `snapdir-ffi` C ABI: `snapdir-api` is a plain crate that `cargo` compiles like any dependency. No `cbindgen`, no C toolchain, no vendored native blob. The sync operations (`id`, `manifest`, `id_from_manifest`, `stage`) need no runtime; the distribution operations are `async` and require a Tokio (or compatible) runtime. ## Usage Paths are `&Path`, the store URI is an `argument` parsed into a typed `StoreUri`, and snapshot IDs are the typed `SnapshotId` newtype (`.to_hex()` renders the 64-character lowercase hex). Use `file://$PWD/store` for a no-setup local store, or `gs://bucket/prefix` for Google Cloud Storage. ```rust use snapdir_api::{ diff, fetch, id, manifest, pull, push, CheckoutOptions, DiffOptions, ManifestOptions, PushSource, StoreUri, TransferOptions, }; use std::path::Path; #[tokio::main] async fn main() -> Result<(), Box> { let store = StoreUri::parse(&format!( "file://{}/store", std::env::current_dir()?.display() ))?; // Snapshot ID — pure, deterministic, no store touched. let snapshot_id = id(Path::new("./my-dir"), &ManifestOptions::default())?; println!("{}", snapshot_id.to_hex()); // 64-char lowercase hex // Full manifest: per-entry path, checksum, and size (u64). let m = manifest(Path::new("./my-dir"), &ManifestOptions::default())?; for entry in &m.entries { println!("{} {}", entry.path.display(), entry.size); } // Push to the store; returns the same 64-hex ID. let sid = push( PushSource::Path(Path::new("./my-dir")), &store, &TransferOptions::default(), ) .await?; // Fetch into the local cache, or pull + materialize into a directory. fetch(&sid, &store, &TransferOptions::default()).await?; pull(&sid, &store, Path::new("./restored"), &CheckoutOptions::default()).await?; // Diff two stores: `from` and `to` are vecs of store URIs. A pinned // snapshot reference uses the store@id convention — the last '@' splits // the store URI from the 64-hex snapshot ID. let next = StoreUri::parse(&format!("{store}-next"))?; let entries = diff(&DiffOptions { from: vec![store.clone()], to: vec![next], ..DiffOptions::default() }) .await?; for e in &entries { // status glyph is A / D / M / = — byte-identical to the CLI. println!("{}\t{}", e.status, e.path.display()); } Ok(()) } ``` For Google Cloud, swap the store URI for a `gs://` URL: ```rust let store = StoreUri::parse("gs://my-bucket/snapshots")?; let sid = push(PushSource::Path(Path::new("./my-dir")), &store, &TransferOptions::default()).await?; ``` ## Errors Every failure is a typed `SnapdirError`. It implements `std::error::Error`, and `.code()` returns one of the eight stable `SCREAMING_SNAKE_CASE` codes shared across all bindings and the CLI, so you can match on the variant or the code: ```rust use snapdir_api::{push, PushSource, SnapdirError, StoreUri, TransferOptions}; use std::path::Path; let store = StoreUri::parse("file://$PWD/store")?; match push(PushSource::Path(Path::new("./missing")), &store, &TransferOptions::default()).await { Ok(id) => println!("{}", id.to_hex()), Err(err) => { match err { SnapdirError::Io(_) => {} // IO_ERROR — filesystem read/write failure SnapdirError::HashMismatch { .. } => {} // HASH_MISMATCH — content did not match its checksum SnapdirError::StoreError(_) => {} // STORE_ERROR — object-store transport/backend failure SnapdirError::InFlux { .. } => {} // IN_FLUX — a file changed while being read SnapdirError::CatalogError { .. } => {} // CATALOG_ERROR — manifest/catalog could not be parsed SnapdirError::InvalidId { .. } => {} // INVALID_ID — snapshot ID is not 64-hex SnapdirError::InvalidStore { .. } => {} // INVALID_STORE — store URI is malformed or unsupported SnapdirError::Conflict { .. } => {} // CONFLICT — a concurrent write conflicted } eprintln!("{}: {err}", err.code()); } } ``` ## Platform support Every binding is CI-verified against its live public registry (crates.io) before release. | Platform | Supported | | --- | --- | | Linux glibc (x64 + arm64) | yes | | Linux musl / Alpine (x64 + arm64) | yes | | macOS | yes | | Windows | no | The Rust binding builds and links on both glibc and musl. The only binding-wide exception to musl support is **Java**, which is glibc-only today (musl planned for 1.11.1); the Rust binding has no such caveat. Windows is unsupported — snapdir is Unix-only.