Skip to main content

locus_core_rs/storage/surrealdb/
runtime.rs

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