1use std::path::Path;
9
10use kmp_adapter_embedded::StorageEngine;
11use kmp_domain::PortError;
12
13pub const ENGINE_ENV: &str = "KMP_MCP_ENGINE";
15
16pub fn parse_engine(value: &str) -> Result<StorageEngine, PortError> {
19 match value.trim().to_ascii_lowercase().as_str() {
20 "sqlite" => Ok(StorageEngine::Sqlite),
21 other => Err(PortError::InvalidState(format!(
22 "unknown storage engine `{other}`; {ENGINE_ENV} only accepts `sqlite`"
23 ))),
24 }
25}
26
27pub fn resolve_engine_from_env() -> Result<Option<StorageEngine>, PortError> {
31 match std::env::var(ENGINE_ENV) {
32 Ok(value) if !value.trim().is_empty() => parse_engine(&value).map(Some),
33 _ => Ok(None),
34 }
35}
36
37pub fn resolve_engine_for_data_dir_from_env(
42 data_dir: &Path,
43) -> Result<Option<StorageEngine>, PortError> {
44 if let Some(engine) = resolve_engine_from_env()? {
45 return Ok(Some(engine));
46 }
47 Ok(default_engine_for_data_dir(data_dir))
48}
49
50pub fn default_engine_for_data_dir(data_dir: &Path) -> Option<StorageEngine> {
53 if data_dir.join("FORMAT_VERSION").exists() {
54 return None;
55 }
56 Some(StorageEngine::Sqlite)
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62
63 #[test]
64 fn sqlite_name_is_case_insensitive_and_trimmed() {
65 assert_eq!(
66 parse_engine(" SQLite ").expect("sqlite parses"),
67 StorageEngine::Sqlite
68 );
69 }
70
71 #[test]
72 fn unknown_engine_names_the_variable_and_the_choices() {
73 let error = parse_engine("postgres").expect_err("not an embedded engine");
74 let message = error.to_string();
75 assert!(message.contains("postgres"), "{message}");
76 assert!(message.contains(ENGINE_ENV), "{message}");
77 assert!(message.contains("only accepts `sqlite`"), "{message}");
78 }
79
80 #[test]
81 fn compiled_sqlite_is_only_the_implicit_choice_for_a_fresh_directory() {
82 let temp = tempfile::tempdir().expect("tempdir");
83 let fresh = temp.path().join("fresh");
84 assert_eq!(
85 default_engine_for_data_dir(&fresh),
86 Some(StorageEngine::Sqlite)
87 );
88
89 std::fs::create_dir_all(&fresh).expect("data dir");
90 std::fs::write(fresh.join("FORMAT_VERSION"), "1\n").expect("stamp");
91 assert_eq!(
92 default_engine_for_data_dir(&fresh),
93 None,
94 "an existing store must be opened from its stamp"
95 );
96 }
97}