Skip to main content

iqdb_persist/
config.rs

1//! Public configuration types for the persistence layer.
2//!
3//! [`PersistConfig`] is what a caller hands to [`crate::PersistedIndex`].
4//! As of v0.3 it implements the snapshot subset (path, fsync policy) plus
5//! the **write-ahead log** (`wal_enabled`). The compression variants are
6//! present so the v0.4 wiring lands without an API break; until then they
7//! are rejected at construction with a clear
8//! [`crate::PersistError::Unsupported`] rather than panicking or silently
9//! no-oping.
10
11use std::path::PathBuf;
12use std::time::Duration;
13
14/// How aggressively the persistence layer fsyncs to durable storage.
15///
16/// Snapshot saves honor `Always` and `Never` (and treat `Periodic` as
17/// `Always`, since a snapshot is a single write). For WAL appends,
18/// `Periodic(interval)` `fsync`s no more than once per `interval`,
19/// trading a bounded window of un-`fsync`ed tail records for throughput.
20///
21/// # Examples
22///
23/// ```
24/// use iqdb_persist::FsyncPolicy;
25/// use std::time::Duration;
26///
27/// let _ = FsyncPolicy::Always;
28/// let _ = FsyncPolicy::Periodic(Duration::from_secs(1));
29/// let _ = FsyncPolicy::Never;
30/// ```
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33pub enum FsyncPolicy {
34    /// fsync every write.
35    Always,
36    /// fsync no more often than this interval (governs WAL appends).
37    Periodic(Duration),
38    /// Never fsync. Fastest, weakest durability — appropriate for tests
39    /// and tmpfs-backed paths only.
40    Never,
41}
42
43/// Compression applied to the snapshot payload on save (v0.4+).
44///
45/// `Zstd` and `Lz4` are gated behind the `zstd` / `lz4` cargo features.
46/// Selecting a scheme whose feature is not compiled in returns
47/// [`crate::PersistError::Unsupported`] at [`crate::PersistedIndex`]
48/// construction — never a panic or a silent fallback. Compression applies
49/// only to snapshots; the WAL is always stored uncompressed.
50///
51/// # Examples
52///
53/// ```
54/// use iqdb_persist::Compression;
55///
56/// let none = Compression::None;
57/// let zstd = Compression::Zstd { level: 3 };
58/// let lz4 = Compression::Lz4;
59/// let _ = (none, zstd, lz4);
60/// ```
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
63pub enum Compression {
64    /// No compression — the payload is stored verbatim. Always available.
65    None,
66    /// Zstandard compression at the given level (requires the `zstd`
67    /// feature). Best ratio; `level` must be in `1..=22`.
68    Zstd {
69        /// The Zstd compression level (1–22).
70        level: i32,
71    },
72    /// LZ4 compression (requires the `lz4` feature). Fastest; lower ratio.
73    Lz4,
74}
75
76/// Configuration for [`crate::PersistedIndex`].
77///
78/// Construct with [`PersistConfig::new`] (recommended) and override the
79/// fields you want, or start from [`PersistConfig::default`] for a
80/// no-path placeholder + sensible knobs.
81///
82/// # Examples
83///
84/// ```
85/// use iqdb_persist::{Compression, FsyncPolicy, PersistConfig};
86/// use std::path::PathBuf;
87///
88/// let cfg = PersistConfig::new("/tmp/my-snapshot.iqdb");
89/// assert_eq!(cfg.path, PathBuf::from("/tmp/my-snapshot.iqdb"));
90/// assert_eq!(cfg.fsync_policy, FsyncPolicy::Always);
91/// assert_eq!(cfg.compression, Compression::None);
92/// assert!(!cfg.wal_enabled);
93/// ```
94#[derive(Debug, Clone)]
95#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
96pub struct PersistConfig {
97    /// Path to the snapshot file on disk.
98    pub path: PathBuf,
99    /// Whether the write-ahead log is enabled (v0.3+). When `true`,
100    /// [`crate::PersistedIndex::insert`] / `delete` log every mutation
101    /// before applying it, [`crate::PersistedIndex::load`] replays the log
102    /// onto the snapshot, and [`crate::PersistedIndex::checkpoint`]
103    /// compacts the log into a fresh snapshot.
104    pub wal_enabled: bool,
105    /// How aggressively to fsync.
106    pub fsync_policy: FsyncPolicy,
107    /// Payload compression. v0.2 rejects non-`None` with
108    /// [`crate::PersistError::Unsupported`]; the API is in place for
109    /// v0.4.
110    pub compression: Compression,
111}
112
113impl PersistConfig {
114    /// Build a config with `path` and v0.2 defaults
115    /// (`wal_enabled = false`, `fsync_policy = Always`,
116    /// `compression = None`).
117    ///
118    /// # Examples
119    ///
120    /// ```
121    /// use iqdb_persist::PersistConfig;
122    ///
123    /// let cfg = PersistConfig::new("snapshot.iqdb");
124    /// assert_eq!(cfg.path.file_name().unwrap(), "snapshot.iqdb");
125    /// ```
126    #[must_use]
127    pub fn new(path: impl Into<PathBuf>) -> Self {
128        Self {
129            path: path.into(),
130            wal_enabled: false,
131            fsync_policy: FsyncPolicy::Always,
132            compression: Compression::None,
133        }
134    }
135}
136
137impl Default for PersistConfig {
138    /// Returns a config with an empty path and v0.2 defaults. The empty
139    /// path is a placeholder — callers MUST set
140    /// [`path`](PersistConfig::path) before passing the config to
141    /// [`crate::PersistedIndex::save`] or
142    /// [`crate::PersistedIndex::load`].
143    ///
144    /// # Examples
145    ///
146    /// ```
147    /// use iqdb_persist::{Compression, FsyncPolicy, PersistConfig};
148    ///
149    /// let cfg = PersistConfig {
150    ///     path: "snapshot.iqdb".into(),
151    ///     ..PersistConfig::default()
152    /// };
153    /// assert_eq!(cfg.fsync_policy, FsyncPolicy::Always);
154    /// assert_eq!(cfg.compression, Compression::None);
155    /// ```
156    fn default() -> Self {
157        Self {
158            path: PathBuf::new(),
159            wal_enabled: false,
160            fsync_policy: FsyncPolicy::Always,
161            compression: Compression::None,
162        }
163    }
164}