use crate::config_keys::ConfigKeys;
use crate::custom_error::KSMRError;
use crate::enums::KvStoreType;
use base64::{
engine::general_purpose::{STANDARD, STANDARD_NO_PAD},
Engine as _,
};
use serde_json::{self};
use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::io::{BufReader, Read, Write};
use std::path::Path;
use std::{env, fs};
pub trait KeyValueStorage {
fn read_storage(&self) -> Result<HashMap<ConfigKeys, String>, KSMRError>;
fn save_storage(
&mut self,
updated_config: HashMap<ConfigKeys, String>,
) -> Result<bool, KSMRError>;
fn get(&self, key: ConfigKeys) -> Result<Option<String>, KSMRError>;
fn set(
&mut self,
key: ConfigKeys,
value: String,
) -> Result<HashMap<ConfigKeys, String>, KSMRError>;
fn delete(&mut self, key: ConfigKeys) -> Result<HashMap<ConfigKeys, String>, KSMRError>;
fn delete_all(&mut self) -> Result<HashMap<ConfigKeys, String>, KSMRError>;
fn contains(&self, key: ConfigKeys) -> Result<bool, KSMRError>;
fn create_config_file_if_missing(&self) -> Result<(), KSMRError>;
fn is_empty(&self) -> Result<bool, KSMRError>;
}
#[derive(Clone)]
pub struct FileKeyValueStorage {
config_file_location: String,
}
impl FileKeyValueStorage {
const DEFAULT_CONFIG_FILE_LOCATION: &str = "client-config.json";
pub fn new(config_file_location: Option<String>) -> Result<Self, KSMRError> {
let location = config_file_location
.or_else(|| env::var("KSM_CONFIG_FILE").ok())
.unwrap_or_else(|| Self::DEFAULT_CONFIG_FILE_LOCATION.to_string());
Ok(FileKeyValueStorage {
config_file_location: location,
})
}
pub fn new_config_storage(file_name: String) -> Result<KvStoreType, KSMRError> {
let file_storage = FileKeyValueStorage::new(Some(file_name.to_string()))?;
Ok(KvStoreType::File(file_storage))
}
}
impl KeyValueStorage for FileKeyValueStorage {
fn read_storage(&self) -> Result<HashMap<ConfigKeys, String>, KSMRError> {
self.create_config_file_if_missing().map_err(|err| {
KSMRError::StorageError(format!("Failed to ensure config file exists: {}", err))
})?;
let file = File::open(&self.config_file_location).map_err(|err| {
KSMRError::StorageError(format!(
"Unable to open config file {}: {}",
self.config_file_location, err
))
})?;
let mut reader = BufReader::new(file);
let mut contents = String::new();
reader
.read_to_string(&mut contents)
.map_err(|err| KSMRError::StorageError(format!("Failed to read file: {}", err)))?;
let config_result: Result<HashMap<ConfigKeys, String>, KSMRError> =
serde_json::from_str(&contents)
.map_err(|err| KSMRError::StorageError(format!("Failed to parse JSON: {}", err)));
match config_result {
Ok(config) => Ok(config),
Err(err) => {
eprintln!("Failed to parse JSON: {}", err);
Err(KSMRError::StorageError(format!(
"Failed to parse JSON: {}",
err
)))
}
}
}
fn save_storage(
&mut self,
updated_config: HashMap<ConfigKeys, String>,
) -> Result<bool, KSMRError> {
self.create_config_file_if_missing().map_err(|err| {
KSMRError::StorageError(format!("Failed to ensure config file exists: {}", err))
})?;
let mut file = OpenOptions::new()
.write(true)
.truncate(true) .open(&self.config_file_location)
.map_err(|err| {
KSMRError::StorageError(format!("Failed to open config file for writing: {}", err))
})?;
let json_data = serde_json::to_string_pretty(&updated_config).map_err(|err| {
KSMRError::StorageError(format!("Failed to serialize config to JSON: {}", err))
})?;
file.write_all(json_data.as_bytes()).map_err(|err| {
KSMRError::StorageError(format!("Failed to write JSON to config file: {}", err))
})?;
Ok(true)
}
fn get(&self, key: ConfigKeys) -> Result<Option<String>, KSMRError> {
let config: HashMap<ConfigKeys, String> = self
.read_storage()
.map_err(|err| KSMRError::StorageError(format!("Failed to Read storage: {}", err)))?;
Ok(config.get(&key).cloned())
}
fn set(
&mut self,
key: ConfigKeys,
value: String,
) -> Result<HashMap<ConfigKeys, String>, KSMRError> {
if ConfigKeys::get_enum(key.value()).is_none() {
return Err(KSMRError::StorageError(format!("Invalid key: {:?}", key)));
}
let mut config = self
.read_storage()
.map_err(|err| KSMRError::StorageError(format!("Failed to read storage: {}", err)))?;
config.insert(key, value);
self.save_storage(config.clone()).map_err(|err| {
KSMRError::StorageError(format!("Failed to save updated config: {}", err))
})?;
Ok(config) }
fn delete(&mut self, key: ConfigKeys) -> Result<HashMap<ConfigKeys, String>, KSMRError> {
let mut config = self
.read_storage()
.map_err(|err| KSMRError::StorageError(format!("Failed to read storage: {}", err)))?;
if config.remove(&key).is_some() {
log::debug!("Removed key {}", key);
} else {
log::debug!("No key {} was found in config", key);
}
self.save_storage(config.clone()).map_err(|err| {
KSMRError::StorageError(format!("Failed to save updated config: {}", err))
})?;
Ok(config) }
fn delete_all(&mut self) -> Result<HashMap<ConfigKeys, String>, KSMRError> {
let mut config = self
.read_storage()
.map_err(|e| KSMRError::StorageError(format!("Failed to read storage: {}", e)))?;
config.clear();
self.save_storage(config.clone()).map_err(|e| {
KSMRError::StorageError(format!("Failed to save cleared config: {}", e))
})?;
Ok(config) }
fn contains(&self, key: ConfigKeys) -> Result<bool, KSMRError> {
let config = self
.read_storage()
.map_err(|e| KSMRError::StorageError(format!("Failed to read storage: {}", e)))?;
Ok(config.contains_key(&key))
}
fn create_config_file_if_missing(&self) -> Result<(), KSMRError> {
if let Some(parent) = Path::new(&self.config_file_location).parent() {
fs::create_dir_all(parent)
.map_err(|e| KSMRError::DirectoryCreationError(parent.display().to_string(), e))?;
}
let config_path = Path::new(&self.config_file_location);
if !config_path.exists() {
#[cfg(unix)]
{
use std::fs::OpenOptions;
use std::os::unix::fs::OpenOptionsExt;
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.mode(0o600)
.open(config_path)
.map_err(|e| {
KSMRError::FileCreationError(config_path.display().to_string(), e)
})?;
let empty_json_string = b"{}";
file.write_all(empty_json_string)
.map_err(|e| KSMRError::FileWriteError(config_path.display().to_string(), e))?;
}
#[cfg(not(unix))]
{
let mut file = File::create(config_path).map_err(|e| {
KSMRError::FileCreationError(config_path.display().to_string(), e)
})?;
let empty_json_string = b"{}";
file.write_all(empty_json_string)
.map_err(|e| KSMRError::FileWriteError(config_path.display().to_string(), e))?;
}
}
Ok(())
}
fn is_empty(&self) -> Result<bool, KSMRError> {
let config = self
.read_storage()
.map_err(|e| KSMRError::StorageError(format!("Failed to read storage: {}", e)))?;
Ok(config.is_empty())
}
}
#[derive(Clone)]
pub struct InMemoryKeyValueStorage {
config: HashMap<ConfigKeys, String>,
}
impl InMemoryKeyValueStorage {
pub fn new(config: Option<String>) -> Result<Self, KSMRError> {
let mut config_map: HashMap<ConfigKeys, String> = HashMap::new();
if let Some(cfg) = config {
if Self::is_base64(&cfg) {
let decoded_bytes = STANDARD
.decode(&cfg)
.or_else(|_| STANDARD_NO_PAD.decode(&cfg))
.map_err(|e| {
KSMRError::DecodeError(format!("Failed to decode Base64 string: {}", e))
})?;
let decoded_string = String::from_utf8(decoded_bytes).map_err(|e| {
KSMRError::StringConversionError(format!(
"Failed to convert decoded bytes to string: {}",
e
))
})?;
config_map = Self::json_to_dict(&decoded_string)?;
} else {
config_map = Self::json_to_dict(&cfg)?;
}
}
Ok(InMemoryKeyValueStorage { config: config_map })
}
pub fn new_config_storage(config: Option<String>) -> Result<KvStoreType, KSMRError> {
let in_memory = InMemoryKeyValueStorage::new(config)?;
Ok(KvStoreType::InMemory(in_memory))
}
fn is_base64(s: &str) -> bool {
STANDARD.decode(s).is_ok() || STANDARD_NO_PAD.decode(s).is_ok()
}
pub fn json_to_dict(json_str: &str) -> Result<HashMap<ConfigKeys, String>, KSMRError> {
let json_str = if json_str.is_empty() { "{}" } else { json_str };
let value: serde_json::Value = serde_json::from_str(json_str)
.map_err(|e| KSMRError::SerializationError(format!("Failed to parse JSON: {}", e)))?;
let mut result = HashMap::new();
if let serde_json::Value::Object(obj) = value {
for (k, v) in obj {
if let serde_json::Value::String(s) = v {
if let Some(key) = ConfigKeys::get_enum(&k) {
result.insert(key, s);
} else {
return Err(KSMRError::SerializationError(format!(
"Invalid key in JSON: {}",
k
)));
}
} else {
return Err(KSMRError::SerializationError(format!(
"Expected string value for key: {}",
k
)));
}
}
} else {
return Err(KSMRError::SerializationError(
"Expected JSON object".to_string(),
));
}
Ok(result) }
}
impl KeyValueStorage for InMemoryKeyValueStorage {
fn read_storage(&self) -> Result<HashMap<ConfigKeys, String>, KSMRError> {
Ok(self.config.clone()) }
fn save_storage(
&mut self,
_updated_config: HashMap<ConfigKeys, String>,
) -> Result<bool, KSMRError> {
self.config = _updated_config;
Ok(true)
}
fn get(&self, key: ConfigKeys) -> Result<Option<String>, KSMRError> {
Ok(self.config.get(&key).cloned()) }
fn set(
&mut self,
key: ConfigKeys,
value: String,
) -> Result<HashMap<ConfigKeys, String>, KSMRError> {
self.config.insert(key, value.clone()); Ok(self.config.clone()) }
fn delete(&mut self, key: ConfigKeys) -> Result<HashMap<ConfigKeys, String>, KSMRError> {
self.config.remove(&key); Ok(self.config.clone()) }
fn delete_all(&mut self) -> Result<HashMap<ConfigKeys, String>, KSMRError> {
self.config.clear(); Ok(self.config.clone()) }
fn contains(&self, key: ConfigKeys) -> Result<bool, KSMRError> {
Ok(self.config.contains_key(&key)) }
fn create_config_file_if_missing(&self) -> Result<(), KSMRError> {
Ok(())
}
fn is_empty(&self) -> Result<bool, KSMRError> {
Ok(self.config.is_empty()) }
}