Skip to main content

storage_kit/state_stores/config/
lance.rs

1use std::{path::PathBuf, time::Duration};
2
3use serde::{Deserialize, Serialize};
4
5use crate::StateStoreError;
6
7/// Embedded Lance state storage. Memory belongs to one connected store;
8/// directory connections share persisted state through native atomic commits.
9///
10/// # Examples
11///
12/// ```
13/// use storage_kit::state::config::LanceStateStoreLocation;
14/// let location = LanceStateStoreLocation::Memory;
15/// assert!(matches!(location, LanceStateStoreLocation::Memory));
16/// ```
17#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
18pub enum LanceStateStoreLocation {
19    /// Process-local storage, shared by clones of the connected handle.
20    Memory,
21    /// A local directory, which may also contain Lance Database collections.
22    Directory(PathBuf),
23}
24
25/// Configuration for the embedded Lance StateStore.
26///
27/// The timeout bounds admission and observation. An admitted commit retains
28/// authority after its observer times out or is dropped. Native filesystem
29/// calls may delay the final owner's joining cleanup beyond that timeout.
30///
31/// # Examples
32///
33/// ```
34/// use storage_kit::state::config::{LanceStateStoreConfig, StateStoreConfig};
35/// let config = StateStoreConfig::Lance(LanceStateStoreConfig::memory());
36/// config.validate()?;
37/// # Ok::<(), storage_kit::StateStoreError>(())
38/// ```
39#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
40pub struct LanceStateStoreConfig {
41    location: LanceStateStoreLocation,
42    operation_timeout_ms: u64,
43}
44
45impl LanceStateStoreConfig {
46    /// Selects process-local memory with a 30-second observation timeout.
47    ///
48    /// # Examples
49    ///
50    /// ```
51    /// use storage_kit::state::config::LanceStateStoreConfig;
52    /// assert_eq!(LanceStateStoreConfig::memory().operation_timeout(),
53    ///     std::time::Duration::from_secs(30));
54    /// ```
55    #[must_use]
56    pub fn memory() -> Self {
57        Self {
58            location: LanceStateStoreLocation::Memory,
59            operation_timeout_ms: 30_000,
60        }
61    }
62
63    /// Selects a local directory. No filesystem I/O occurs until connection.
64    ///
65    /// # Examples
66    ///
67    /// ```
68    /// use storage_kit::state::config::LanceStateStoreConfig;
69    /// let config = LanceStateStoreConfig::directory("application-data");
70    /// config.validate()?;
71    /// # Ok::<(), storage_kit::StateStoreError>(())
72    /// ```
73    #[must_use]
74    pub fn directory(path: impl Into<PathBuf>) -> Self {
75        Self {
76            location: LanceStateStoreLocation::Directory(path.into()),
77            operation_timeout_ms: 30_000,
78        }
79    }
80
81    /// Sets the admission and observation timeout, without expiring transactions.
82    ///
83    /// # Errors
84    ///
85    /// Rejects sub-millisecond, zero, overflowing, or unrepresentable deadlines.
86    ///
87    /// # Examples
88    ///
89    /// ```
90    /// use storage_kit::state::config::LanceStateStoreConfig;
91    /// let config = LanceStateStoreConfig::memory()
92    ///     .with_operation_timeout(std::time::Duration::from_secs(2))?;
93    /// assert_eq!(config.operation_timeout(), std::time::Duration::from_secs(2));
94    /// # Ok::<(), storage_kit::StateStoreError>(())
95    /// ```
96    pub fn with_operation_timeout(mut self, timeout: Duration) -> Result<Self, StateStoreError> {
97        self.operation_timeout_ms = u64::try_from(timeout.as_millis()).map_err(|_| {
98            StateStoreError::invalid_config("Lance state operation timeout exceeds milliseconds")
99        })?;
100        self.validate()?;
101        Ok(self)
102    }
103
104    /// Borrows the selected storage location.
105    ///
106    /// # Examples
107    ///
108    /// ```
109    /// use storage_kit::state::config::{LanceStateStoreConfig, LanceStateStoreLocation};
110    /// assert_eq!(LanceStateStoreConfig::memory().location(), &LanceStateStoreLocation::Memory);
111    /// ```
112    #[must_use]
113    pub fn location(&self) -> &LanceStateStoreLocation {
114        &self.location
115    }
116
117    /// Returns the admission and observation timeout.
118    ///
119    /// # Examples
120    ///
121    /// ```
122    /// use storage_kit::state::config::LanceStateStoreConfig;
123    /// assert_eq!(LanceStateStoreConfig::memory().operation_timeout().as_secs(), 30);
124    /// ```
125    #[must_use]
126    pub fn operation_timeout(&self) -> Duration {
127        Duration::from_millis(self.operation_timeout_ms)
128    }
129
130    /// Checks local configuration without opening storage.
131    ///
132    /// # Errors
133    ///
134    /// Rejects empty, NUL-containing, non-UTF-8, or URI directory paths, and
135    /// zero or unrepresentable observation timeouts. External URIs are outside
136    /// this embedded backend's supported scope.
137    ///
138    /// # Examples
139    ///
140    /// ```
141    /// use storage_kit::state::config::LanceStateStoreConfig;
142    /// assert!(LanceStateStoreConfig::directory("").validate().is_err());
143    /// LanceStateStoreConfig::memory().validate()?;
144    /// # Ok::<(), storage_kit::StateStoreError>(())
145    /// ```
146    pub fn validate(&self) -> Result<(), StateStoreError> {
147        if self.operation_timeout_ms == 0
148            || std::time::Instant::now()
149                .checked_add(self.operation_timeout())
150                .is_none()
151        {
152            return Err(StateStoreError::invalid_config(
153                "Lance state operation timeout must be positive and fit the clock range",
154            ));
155        }
156        if let LanceStateStoreLocation::Directory(path) = &self.location {
157            let value = path.to_str().ok_or_else(|| {
158                StateStoreError::invalid_config("Lance state directory must be valid UTF-8")
159            })?;
160            if value.is_empty() || value.contains('\0') || value.contains("://") {
161                return Err(StateStoreError::invalid_config(
162                    "Lance state directory must be a nonempty local filesystem path without NUL",
163                ));
164            }
165        }
166        Ok(())
167    }
168}