Skip to main content

objstore_r/
lib.rs

1// Author: support@lituus.io
2// Copyright: lituus-io, all rights reserved
3//
4//! # objstore-r
5//!
6//! Object stores with **generation preconditions**, for data that is published
7//! as immutable snapshots plus append-only segments (graph-r, link-r).
8//!
9//! Every object carries a *generation* (a `u64`, never zero) that changes on
10//! each write. Writes take a [`Cond`]:
11//!
12//! - [`Cond::CreateOnly`]: succeed only if the key does not exist. Immutable
13//!   objects (snapshots, log segments) are written this way, so a retry after a
14//!   lost response is harmless and a second writer can never clobber the first.
15//! - [`Cond::IfGeneration`]: succeed only if the object is still at that
16//!   generation. A small `HEAD` object updated this way gives single-writer
17//!   compare-and-swap across machines, with no lock service.
18//!
19//! ## Backends
20//!
21//! | Backend | Feature | Use |
22//! |---|---|---|
23//! | [`LocalDir`] | always | a directory; atomic rename, fsync, `File::lock` for conditions |
24//! | [`Mem`] | always | tests; deterministic, with scripted fault injection |
25//! | `Gcs` | `gcs` | Google Cloud Storage (JSON API), ADC tokens, retries, ranged reads |
26//!
27//! ## Design tenets
28//!
29//! - **Named futures, no boxing.** [`ObjectStore`] methods return associated
30//!   future types (GATs), so callers compose them without `Box<dyn Future>`
31//!   and without `Send`/`'static` bounds imposed by this crate. Futures are
32//!   `Send` whenever the backend is, so multi-threaded runtimes work too.
33//! - **Enums over `dyn`.** Choose a backend at compile time, or wrap several in
34//!   your own enum; nothing here needs a trait object.
35//! - **Zero-copy bodies.** Objects are [`bytes::Bytes`]; a body is read or
36//!   written once and shared by reference count from there on.
37//! - **No unsafe code.**
38//!
39//! ## Example
40//!
41//! ```
42//! use objstore_r::{Cond, Mem, ObjectStore};
43//! # futures::executor::block_on(async {
44//! let store = Mem::default();
45//! let g1 = store.put("snap/0001", "v1".into(), Cond::CreateOnly).await?;
46//! assert!(store.put("snap/0001", "again".into(), Cond::CreateOnly).await.is_err());
47//! let head = store.put("HEAD", "0001".into(), Cond::None).await?;
48//! store.put("HEAD", "0002".into(), Cond::IfGeneration(head)).await?;       // CAS
49//! assert_eq!(store.get("snap/0001").await?.unwrap().bytes, "v1");
50//! # let _ = g1;
51//! # Ok::<(), objstore_r::Error>(()) }).unwrap();
52//! ```
53
54#![cfg_attr(feature = "http", feature(impl_trait_in_assoc_type))]
55#![forbid(unsafe_code)]
56#![deny(missing_docs)]
57#![deny(missing_debug_implementations)]
58#![deny(rust_2018_idioms)]
59#![deny(unreachable_pub)]
60#![warn(clippy::all, clippy::pedantic, clippy::cargo)]
61#![allow(
62    clippy::module_name_repetitions,
63    clippy::missing_errors_doc,
64    clippy::missing_panics_doc,
65    clippy::multiple_crate_versions,
66    clippy::cast_possible_truncation
67)]
68
69pub mod error;
70pub mod fenced;
71pub mod join;
72pub mod key;
73pub mod local;
74pub mod mem;
75#[cfg(feature = "mirror")]
76pub mod mirror;
77pub mod net;
78pub mod sse;
79pub mod transport;
80
81#[cfg(feature = "gcs")]
82pub mod gcs;
83#[cfg(feature = "http")]
84pub mod http;
85#[cfg(feature = "http")]
86pub mod token;
87
88#[cfg(feature = "testing")]
89pub mod testing;
90
91use std::future::Future;
92use std::ops::Range;
93
94pub use bytes::Bytes;
95pub use error::{Error, Result};
96pub use key::Key;
97pub use local::LocalDir;
98pub use mem::{Fault, Mem};
99
100#[cfg(feature = "gcs")]
101pub use gcs::{Gcs, GcsConfig};
102
103/// An object's content and generation.
104#[derive(Debug, Clone, PartialEq, Eq)]
105pub struct Object {
106    /// The whole body (shared, zero-copy).
107    pub bytes: Bytes,
108    /// The generation it was read at (never zero).
109    pub generation: u64,
110}
111
112/// A listed object.
113#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
114pub struct Meta {
115    /// The full key.
116    pub key: String,
117    /// Body size in bytes.
118    pub size: u64,
119    /// Current generation (never zero).
120    pub generation: u64,
121}
122
123/// A write precondition.
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
125pub enum Cond {
126    /// Write unconditionally (replace whatever is there).
127    #[default]
128    None,
129    /// Write only if no object exists under the key ([`Error::Conflict`] otherwise).
130    CreateOnly,
131    /// Write only if the object is at exactly this generation
132    /// ([`Error::PreconditionFailed`] otherwise, including when it does not exist).
133    IfGeneration(u64),
134}
135
136/// An object store with generation preconditions.
137///
138/// Keys are validated by [`Key::parse`]: `/`-separated segments of
139/// `[A-Za-z0-9._-]`, no empty segment, none starting with `.`, at most 1024 bytes.
140///
141/// Implementations must be linearizable per key: a successful conditional
142/// write is observed by every later read.
143pub trait ObjectStore {
144    /// Future of [`get`](ObjectStore::get).
145    type Get<'a>: Future<Output = Result<Option<Object>>> + 'a
146    where
147        Self: 'a;
148    /// Future of [`get_range`](ObjectStore::get_range).
149    type GetRange<'a>: Future<Output = Result<Bytes>> + 'a
150    where
151        Self: 'a;
152    /// Future of [`head`](ObjectStore::head).
153    type Head<'a>: Future<Output = Result<Option<Meta>>> + 'a
154    where
155        Self: 'a;
156    /// Future of [`put`](ObjectStore::put).
157    type Put<'a>: Future<Output = Result<u64>> + 'a
158    where
159        Self: 'a;
160    /// Future of [`list`](ObjectStore::list).
161    type List<'a>: Future<Output = Result<Vec<Meta>>> + 'a
162    where
163        Self: 'a;
164    /// Future of [`delete`](ObjectStore::delete).
165    type Delete<'a>: Future<Output = Result<()>> + 'a
166    where
167        Self: 'a;
168
169    /// The whole object, or `None` if it does not exist.
170    fn get<'a>(&'a self, key: &'a str) -> Self::Get<'a>;
171
172    /// Bytes `range` of the object. A range running past the end is clipped;
173    /// a range starting at or past the end is an empty body. A missing object
174    /// is [`Error::NotFound`].
175    fn get_range<'a>(&'a self, key: &'a str, range: Range<u64>) -> Self::GetRange<'a>;
176
177    /// Size and generation, without the body.
178    fn head<'a>(&'a self, key: &'a str) -> Self::Head<'a>;
179
180    /// Write `body` under `key` if `cond` holds; returns the new generation.
181    fn put<'a>(&'a self, key: &'a str, body: Bytes, cond: Cond) -> Self::Put<'a>;
182
183    /// Every object whose key starts with `prefix`, sorted by key. An empty
184    /// prefix lists everything; a non-empty prefix is validated like a key
185    /// (a trailing `/` is allowed).
186    fn list<'a>(&'a self, prefix: &'a str) -> Self::List<'a>;
187
188    /// Remove the object. Deleting a missing key succeeds.
189    fn delete<'a>(&'a self, key: &'a str) -> Self::Delete<'a>;
190}
191
192/// Clip `range` to an object of `len` bytes, as `get_range` specifies.
193pub(crate) fn clip(range: &Range<u64>, len: u64) -> Range<usize> {
194    let start = range.start.min(len);
195    let end = range.end.clamp(start, len);
196    start as usize..end as usize
197}