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}