use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageBackendConfig {
pub id: String,
#[serde(flatten)]
pub spec: StorageBackendSpec,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all_fields = "camelCase")]
pub enum StorageBackendSpec {
#[serde(rename = "memory")]
Memory {
#[serde(default)]
enable_archive: bool,
},
#[serde(untagged)]
Plugin {
kind: String,
#[serde(flatten)]
config: serde_json::Value,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum StorageBackendRef {
Named(String),
Inline(StorageBackendSpec),
}
impl StorageBackendSpec {
pub fn validate(&self) -> Result<(), String> {
match self {
StorageBackendSpec::Memory { .. } => Ok(()),
StorageBackendSpec::Plugin { kind, .. } => {
if kind.trim().is_empty() {
return Err("Storage backend 'kind' must not be empty".to_string());
}
Ok(())
}
}
}
pub fn is_volatile(&self) -> bool {
matches!(self, StorageBackendSpec::Memory { .. })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_json_roundtrip_both_variants() {
let memory = StorageBackendSpec::Memory {
enable_archive: true,
};
let memory_value = serde_json::to_value(&memory).unwrap();
assert_eq!(memory_value["kind"], "memory");
match serde_json::from_value::<StorageBackendSpec>(memory_value).unwrap() {
StorageBackendSpec::Memory { enable_archive } => assert!(enable_archive),
_ => panic!("Expected Memory variant after JSON round-trip"),
}
let plugin = StorageBackendSpec::Plugin {
kind: "rocksdb".to_string(),
config: serde_json::json!({
"path": "/data/drasi",
"enableArchive": true,
}),
};
let plugin_value = serde_json::to_value(&plugin).unwrap();
assert_eq!(plugin_value["kind"], "rocksdb");
assert_eq!(plugin_value["path"], "/data/drasi");
match serde_json::from_value::<StorageBackendSpec>(plugin_value).unwrap() {
StorageBackendSpec::Plugin { kind, config } => {
assert_eq!(kind, "rocksdb");
assert_eq!(config["path"], "/data/drasi");
assert_eq!(config["enableArchive"], true);
}
_ => panic!("Expected Plugin variant after JSON round-trip"),
}
}
#[test]
fn test_memory_serde() {
let yaml = r#"
kind: memory
enableArchive: true
"#;
let spec: StorageBackendSpec = serde_yaml::from_str(yaml).unwrap();
match spec {
StorageBackendSpec::Memory { enable_archive } => {
assert!(enable_archive);
}
_ => panic!("Expected Memory variant"),
}
let serialized = serde_yaml::to_string(&spec).unwrap();
let deserialized: StorageBackendSpec = serde_yaml::from_str(&serialized).unwrap();
match deserialized {
StorageBackendSpec::Memory { enable_archive } => {
assert!(enable_archive);
}
_ => panic!("Expected Memory variant after round-trip"),
}
}
#[test]
fn test_plugin_rocksdb_serde() {
let yaml = r#"
kind: rocksdb
path: /data/drasi
enableArchive: true
directIo: false
"#;
let spec: StorageBackendSpec = serde_yaml::from_str(yaml).unwrap();
match &spec {
StorageBackendSpec::Plugin { kind, config } => {
assert_eq!(kind, "rocksdb");
assert_eq!(config["path"], "/data/drasi");
assert_eq!(config["enableArchive"], true);
assert_eq!(config["directIo"], false);
}
_ => panic!("Expected Plugin variant"),
}
let serialized = serde_yaml::to_string(&spec).unwrap();
let deserialized: StorageBackendSpec = serde_yaml::from_str(&serialized).unwrap();
match deserialized {
StorageBackendSpec::Plugin { kind, config } => {
assert_eq!(kind, "rocksdb");
assert_eq!(config["path"], "/data/drasi");
}
_ => panic!("Expected Plugin variant after round-trip"),
}
}
#[test]
fn test_plugin_redis_serde() {
let yaml = r#"
kind: redis
connectionString: "redis://localhost:6379"
cacheSize: 10000
"#;
let spec: StorageBackendSpec = serde_yaml::from_str(yaml).unwrap();
match spec {
StorageBackendSpec::Plugin { kind, config } => {
assert_eq!(kind, "redis");
assert_eq!(config["connectionString"], "redis://localhost:6379");
assert_eq!(config["cacheSize"], 10000);
}
_ => panic!("Expected Plugin variant"),
}
}
#[test]
fn test_storage_backend_config_serde() {
let yaml = r#"
id: rocks_persistent
kind: rocksdb
path: /data/drasi
enableArchive: true
"#;
let config: StorageBackendConfig = serde_yaml::from_str(yaml).unwrap();
assert_eq!(config.id, "rocks_persistent");
match config.spec {
StorageBackendSpec::Plugin { kind, config } => {
assert_eq!(kind, "rocksdb");
assert_eq!(config["path"], "/data/drasi");
assert_eq!(config["enableArchive"], true);
}
_ => panic!("Expected Plugin variant"),
}
}
#[test]
fn test_storage_backend_config_memory_serde() {
let yaml = r#"
id: mem
kind: memory
enableArchive: true
"#;
let config: StorageBackendConfig = serde_yaml::from_str(yaml).unwrap();
assert_eq!(config.id, "mem");
match config.spec {
StorageBackendSpec::Memory { enable_archive } => assert!(enable_archive),
_ => panic!("Expected Memory variant"),
}
}
#[test]
fn test_storage_backend_ref_named() {
let yaml = r#""rocks_persistent""#;
let ref_val: StorageBackendRef = serde_yaml::from_str(yaml).unwrap();
match ref_val {
StorageBackendRef::Named(name) => {
assert_eq!(name, "rocks_persistent");
}
_ => panic!("Expected Named variant"),
}
}
#[test]
fn test_storage_backend_ref_inline() {
let yaml = r#"
kind: memory
enableArchive: false
"#;
let ref_val: StorageBackendRef = serde_yaml::from_str(yaml).unwrap();
match ref_val {
StorageBackendRef::Inline(spec) => match spec {
StorageBackendSpec::Memory { enable_archive } => {
assert!(!enable_archive);
}
_ => panic!("Expected Memory variant"),
},
_ => panic!("Expected Inline variant"),
}
}
#[test]
fn test_validate_memory() {
let spec = StorageBackendSpec::Memory {
enable_archive: true,
};
assert!(spec.validate().is_ok());
}
#[test]
fn test_validate_plugin_ok() {
let spec = StorageBackendSpec::Plugin {
kind: "rocksdb".to_string(),
config: serde_json::json!({ "path": "/data/drasi" }),
};
assert!(spec.validate().is_ok());
}
#[test]
fn test_validate_plugin_empty_kind() {
let spec = StorageBackendSpec::Plugin {
kind: " ".to_string(),
config: serde_json::json!({}),
};
assert!(spec.validate().is_err());
let err = spec.validate().unwrap_err();
assert!(err.contains("must not be empty"));
}
#[test]
fn test_is_volatile() {
let memory_spec = StorageBackendSpec::Memory {
enable_archive: false,
};
assert!(memory_spec.is_volatile());
let plugin_spec = StorageBackendSpec::Plugin {
kind: "rocksdb".to_string(),
config: serde_json::json!({ "path": "/data/drasi" }),
};
assert!(!plugin_spec.is_volatile());
}
}