storage_kit/databases/config/lance.rs
1use std::path::{Path, PathBuf};
2
3use serde::{Deserialize, Serialize};
4
5use crate::StorageError;
6
7/// LanceDB embedded configuration.
8///
9/// This configuration accepts local filesystem directories and `memory://`.
10/// Native remote routes and `file:` URLs are rejected at validation. Construction
11/// does not open the database or create a directory.
12///
13/// # Examples
14///
15/// ```
16/// use storage_kit::database::config::{DatabaseConfig, LanceConfig};
17/// let config = LanceConfig::embedded_path("memory://");
18/// assert_eq!(config.default_vector_column.as_deref(), Some("vector"));
19/// DatabaseConfig::lance(config).validate()?;
20/// # Ok::<(), storage_kit::StorageError>(())
21/// ```
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct LanceConfig {
24 /// Filesystem directory used for embedded tables, or `memory://`.
25 /// Native remote and `file:` URI routes are not accepted.
26 pub path: PathBuf,
27 /// Default vector column used when a query omits its vector field.
28 pub default_vector_column: Option<String>,
29}
30
31impl LanceConfig {
32 /// Builds an embedded LanceDB configuration rooted at `path`.
33 ///
34 /// The default vector column is `vector`. The path is checked by
35 /// [`DatabaseConfig::validate`](crate::database::config::DatabaseConfig::validate)
36 /// and [`crate::Database::connect`], not by this infallible constructor.
37 ///
38 /// # Examples
39 ///
40 /// ```
41 /// use storage_kit::database::config::{DatabaseConfig, LanceConfig};
42 /// let config = LanceConfig::embedded_path("data/documents");
43 /// assert_eq!(config.path, std::path::PathBuf::from("data/documents"));
44 /// DatabaseConfig::lance(config).validate()?;
45 /// # Ok::<(), storage_kit::StorageError>(())
46 /// ```
47 #[must_use]
48 pub fn embedded_path(path: impl Into<PathBuf>) -> Self {
49 Self {
50 path: path.into(),
51 default_vector_column: Some("vector".into()),
52 }
53 }
54
55 pub(crate) fn validate(&self) -> Result<(), StorageError> {
56 if self.path.as_os_str().is_empty() {
57 return Err(StorageError::InvalidConfig(
58 "LanceConfig.path must not be empty".into(),
59 ));
60 }
61 let path = self.uri_string();
62 let bytes = path.as_bytes();
63 let windows_drive = bytes.len() >= 3
64 && bytes[0].is_ascii_alphabetic()
65 && bytes[1] == b':'
66 && matches!(bytes[2], b'/' | b'\\');
67 if path != "memory://" && !windows_drive && url::Url::parse(&path).is_ok() {
68 return Err(StorageError::InvalidConfig(
69 "LanceConfig.path accepts filesystem paths or memory://, not native URI routes"
70 .into(),
71 ));
72 }
73 if self
74 .default_vector_column
75 .as_deref()
76 .is_some_and(|column| column.trim().is_empty())
77 {
78 return Err(StorageError::InvalidConfig(
79 "LanceConfig.default_vector_column must not be empty when configured".into(),
80 ));
81 }
82 Ok(())
83 }
84
85 pub(crate) fn uri_string(&self) -> String {
86 self.path.to_string_lossy().into_owned()
87 }
88
89 pub(crate) fn path(&self) -> &Path {
90 &self.path
91 }
92}