Skip to main content

locus_core_rs/storage/surrealdb/
runtime.rs

1#[cfg(all(feature = "surreal-runtime", not(target_arch = "wasm32")))]
2use std::env;
3#[cfg(all(feature = "surreal-runtime", not(target_arch = "wasm32")))]
4use std::path::{Path, PathBuf};
5
6use anyhow::{Result, anyhow};
7
8#[derive(Debug, Clone)]
9pub struct SurrealDbEndpointsSettings {
10    pub embedded: Option<String>,
11    pub remote: Option<String>,
12}
13
14impl Default for SurrealDbEndpointsSettings {
15    fn default() -> Self {
16        Self {
17            embedded: Some("surrealkv://data/locus".to_string()),
18            remote: None,
19        }
20    }
21}
22
23#[derive(Debug, Clone)]
24pub struct SurrealDbSettings {
25    pub endpoints: SurrealDbEndpointsSettings,
26    pub namespace: String,
27    pub database: String,
28    pub user: Option<String>,
29    pub password: Option<String>,
30}
31
32impl Default for SurrealDbSettings {
33    fn default() -> Self {
34        Self {
35            endpoints: SurrealDbEndpointsSettings::default(),
36            namespace: "entasis".to_string(),
37            database: "locus".to_string(),
38            user: Some("root".to_string()),
39            password: Some("root".to_string()),
40        }
41    }
42}
43
44impl SurrealDbSettings {
45    pub fn endpoint(&self, use_remote: bool) -> Result<String> {
46        if use_remote {
47            if let Some(remote) = self
48                .endpoints
49                .remote
50                .as_ref()
51                .filter(|value| !value.trim().is_empty())
52            {
53                return Ok(remote.clone());
54            }
55        }
56
57        if let Some(embedded) = self
58            .endpoints
59            .embedded
60            .as_ref()
61            .filter(|value| !value.trim().is_empty())
62        {
63            return Ok(embedded.clone());
64        }
65
66        let mode = if use_remote { "remote" } else { "embedded" };
67        Err(anyhow!(
68            "No SurrealDB endpoint configured for mode {mode}. Set SurrealDb Endpoints."
69        ))
70    }
71}
72
73#[derive(Debug, Clone)]
74pub struct SurrealDbRuntimeOptions {
75    pub root_dir: String,
76    pub use_remote: bool,
77    pub endpoint: String,
78    pub namespace: String,
79    pub database: String,
80}
81
82impl SurrealDbRuntimeOptions {
83    /// Resolve runtime options from CLI args and filesystem-backed embedded paths.
84    ///
85    /// Requires the `surreal-runtime` feature and is unavailable on `wasm32` targets.
86    #[cfg(all(feature = "surreal-runtime", not(target_arch = "wasm32")))]
87    pub fn from_args(
88        args: &[String],
89        settings: &SurrealDbSettings,
90        root_directory_name: Option<&str>,
91    ) -> Result<Self> {
92        let use_remote = args.iter().any(|arg| arg.eq_ignore_ascii_case("--remote"));
93        let root_name = root_directory_name
94            .unwrap_or(".locus")
95            .trim()
96            .to_string();
97
98        let home = env::var("HOME").map_err(|_| anyhow!("HOME is not set"))?;
99        let root_dir = PathBuf::from(home).join(if root_name.is_empty() {
100            ".locus"
101        } else {
102            root_name.as_str()
103        });
104
105        let endpoint = settings.endpoint(use_remote)?;
106        let normalized_endpoint = normalize_embedded_endpoint(&endpoint, &root_dir, use_remote)?;
107
108        Ok(Self {
109            root_dir: root_dir.to_string_lossy().to_string(),
110            use_remote,
111            endpoint: normalized_endpoint,
112            namespace: settings.namespace.clone(),
113            database: settings.database.clone(),
114        })
115    }
116}
117
118#[cfg(all(feature = "surreal-runtime", not(target_arch = "wasm32")))]
119fn normalize_embedded_endpoint(
120    endpoint: &str,
121    root_dir: &Path,
122    use_remote: bool,
123) -> Result<String> {
124    if use_remote {
125        return Ok(endpoint.to_string());
126    }
127
128    const SCHEME: &str = "surrealkv://";
129    if !endpoint.starts_with(SCHEME) {
130        return Ok(endpoint.to_string());
131    }
132
133    let endpoint_path = endpoint.trim_start_matches(SCHEME);
134    let absolute_path = if Path::new(endpoint_path).is_absolute() {
135        PathBuf::from(endpoint_path)
136    } else {
137        root_dir.join(endpoint_path)
138    };
139
140    if let Some(parent) = absolute_path.parent() {
141        std::fs::create_dir_all(parent)?;
142    }
143
144    Ok(format!("{SCHEME}{}", absolute_path.to_string_lossy()))
145}