use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use crate::{Password, StorageError};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SurrealModeConfig {
Mem,
RocksDb {
path: PathBuf,
},
SurrealKv {
path: PathBuf,
},
RemoteWs {
endpoint: String,
},
RemoteHttp {
endpoint: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SurrealConfig {
pub max_retries: u32,
pub mode: SurrealModeConfig,
pub namespace: Option<String>,
pub database: Option<String>,
pub username: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub password: Option<Password>,
}
impl SurrealConfig {
pub(crate) fn validate(&self) -> Result<(), StorageError> {
match &self.mode {
SurrealModeConfig::Mem => {}
SurrealModeConfig::RocksDb { path } => validate_non_empty_path("mode.path", path)?,
SurrealModeConfig::SurrealKv { path } => validate_non_empty_path("mode.path", path)?,
SurrealModeConfig::RemoteWs { endpoint } => {
validate_endpoint("mode.endpoint", endpoint, &["ws", "wss"])?
}
SurrealModeConfig::RemoteHttp { endpoint } => {
validate_endpoint("mode.endpoint", endpoint, &["http", "https"])?
}
}
validate_optional_non_empty("namespace", self.namespace.as_deref())?;
validate_optional_non_empty("database", self.database.as_deref())?;
validate_optional_non_empty("username", self.username.as_deref())?;
Ok(())
}
}
impl Default for SurrealConfig {
fn default() -> Self {
Self {
mode: SurrealModeConfig::Mem,
max_retries: 2,
namespace: Some("test".into()),
database: Some("test".into()),
username: None,
password: None,
}
}
}
fn validate_non_empty_path(field: &str, path: &Path) -> Result<(), StorageError> {
if path.as_os_str().is_empty() {
return Err(StorageError::InvalidConfig(format!(
"{field} must not be empty"
)));
}
Ok(())
}
fn validate_optional_non_empty(field: &str, value: Option<&str>) -> Result<(), StorageError> {
if let Some(value) = value
&& value.trim().is_empty()
{
return Err(StorageError::InvalidConfig(format!(
"{field} must not be empty when configured"
)));
}
Ok(())
}
fn validate_endpoint(field: &str, endpoint: &str, schemes: &[&str]) -> Result<(), StorageError> {
validate_optional_non_empty(field, Some(endpoint))?;
let parsed = url::Url::parse(endpoint).map_err(|err| {
StorageError::InvalidConfig(format!("{field} must be a valid URL: {err}"))
})?;
if !schemes.iter().any(|scheme| parsed.scheme() == *scheme) {
return Err(StorageError::InvalidConfig(format!(
"{field} must use one of the following schemes: {}",
schemes.join(", ")
)));
}
Ok(())
}