mod endpoint;
mod enums;
mod error;
mod pool;
mod server;
mod tokens;
pub use endpoint::{ConfListen, EndpointKind};
pub use enums::{ConsistencyLevel, DataStore, Distribution, HashType, SecureServerOption};
pub use error::ConfError;
pub use pool::{
ConfBucketType, ConfPool, ConfRiak, ConfRiakWasmModule, ConfTlsProfile, ObservabilityConfig,
Servers,
};
pub use server::{ConfDynSeed, ConfServer};
pub use tokens::{TokenComponent, TokenList};
use std::collections::BTreeMap;
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
static NOXU_SUPPORTED: AtomicBool = AtomicBool::new(false);
pub fn set_noxu_supported(on: bool) {
NOXU_SUPPORTED.store(on, Ordering::SeqCst);
}
#[must_use]
pub fn is_noxu_supported() -> bool {
NOXU_SUPPORTED.load(Ordering::SeqCst)
}
#[derive(Debug, Clone)]
pub struct Config {
pool_name: String,
pool: ConfPool,
}
impl Config {
pub fn parse_str(input: &str) -> Result<Self, ConfError> {
let raw: BTreeMap<String, ConfPool> =
serde_yaml::from_str(input).map_err(|e| ConfError::from_yaml(&e))?;
if raw.is_empty() {
return Err(ConfError::EmptyDocument);
}
if raw.len() != 1 {
return Err(ConfError::TooManyPools(raw.len()));
}
let (pool_name, pool) = raw
.into_iter()
.next()
.expect("invariant: raw.len() == 1, checked above");
if pool_name.is_empty() {
return Err(ConfError::EmptyPoolName);
}
Ok(Self { pool_name, pool })
}
pub fn parse_file(path: &Path) -> Result<Self, ConfError> {
let bytes = std::fs::read_to_string(path).map_err(|e| ConfError::Io {
path: path.to_path_buf(),
source: e,
})?;
Self::parse_str(&bytes)
}
pub fn pool_name(&self) -> &str {
&self.pool_name
}
pub fn pool(&self) -> &ConfPool {
&self.pool
}
pub fn pool_mut(&mut self) -> &mut ConfPool {
&mut self.pool
}
pub fn finalize(&mut self) {
self.pool.apply_defaults();
}
pub fn validate(&self) -> Result<(), ConfError> {
self.pool.validate(&self.pool_name)
}
pub fn test_conf(&self) -> Result<String, ConfError> {
let mut owned = self.clone();
owned.finalize();
owned.validate()?;
Ok(format!(
"configuration file with pool '{}' is valid",
owned.pool_name
))
}
}
#[cfg(test)]
mod tests {
use super::*;
const MINIMAL: &str = r"
dyn_o_mite:
listen: 127.0.0.1:8102
dyn_listen: 127.0.0.1:8101
tokens: '101134286'
servers:
- 127.0.0.1:22122:1
data_store: 0
";
#[test]
fn parse_minimal() {
let cfg = Config::parse_str(MINIMAL).unwrap();
assert_eq!(cfg.pool_name(), "dyn_o_mite");
assert_eq!(cfg.pool().listen.as_ref().unwrap().port(), 8102);
}
#[test]
fn finalize_sets_defaults() {
let mut cfg = Config::parse_str(MINIMAL).unwrap();
cfg.finalize();
assert_eq!(cfg.pool().rack.as_deref(), Some("localrack"));
assert_eq!(cfg.pool().datacenter.as_deref(), Some("localdc"));
assert_eq!(cfg.pool().timeout, Some(5000));
}
#[test]
fn validate_minimal() {
let mut cfg = Config::parse_str(MINIMAL).unwrap();
cfg.finalize();
cfg.validate().unwrap();
}
#[test]
fn empty_document_rejected() {
let err = Config::parse_str("").unwrap_err();
assert!(matches!(
err,
ConfError::Yaml { .. } | ConfError::EmptyDocument
));
}
#[test]
fn too_many_pools_rejected() {
let yaml = "a:\n listen: 1.2.3.4:80\nb:\n listen: 1.2.3.4:81\n";
let err = Config::parse_str(yaml).unwrap_err();
assert!(matches!(err, ConfError::TooManyPools(2)));
}
#[test]
fn unknown_key_rejected() {
let yaml = "p:\n listen: 127.0.0.1:1\n bogus_key: 42\n";
let err = Config::parse_str(yaml).unwrap_err();
match err {
ConfError::UnknownKey { name } => assert_eq!(name, "bogus_key"),
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn test_conf_reports_pool_name() {
let cfg = Config::parse_str(MINIMAL).unwrap();
let report = cfg.test_conf().unwrap();
assert!(report.contains("dyn_o_mite"));
}
}