use crate::cluster::{PeerEntry, ScopeEntry};
use crate::schema::{Config, LogOutput};
const SERVICE_SECTIONS: [&str; 6] =
["cluster", "replication", "lua", "metrics", "audit", "feed"];
impl Config {
pub fn to_toml_string(&self) -> String {
let mut out = String::new();
self.write_toml_storage_sections(&mut out);
self.write_toml_tuning_sections(&mut out);
self.write_toml_service_sections(&mut out);
out
}
fn write_toml_storage_sections(&self, out: &mut String) {
use std::fmt::Write;
let [a, b, c, d] = self.server.bind;
let _ = writeln!(out, "[server]");
let _ = writeln!(out, "bind = \"{a}.{b}.{c}.{d}\"");
let _ = writeln!(out, "port = {}", self.server.port);
let _ = writeln!(out, "threads = {}", self.server.threads);
if let Some(n) = self.server.accept_shards {
let _ = writeln!(out, "accept_shards = {n}");
}
let _ = writeln!(out, "max_clients = {}", self.server.max_clients);
let _ = writeln!(
out,
"data_dir = \"{}\"",
escape_toml_basic_string(&self.server.data_dir.display().to_string()),
);
let _ = writeln!(out);
let _ = writeln!(out, "[persistence]");
let _ = writeln!(out, "aof = {}", self.persistence.aof);
let _ = writeln!(
out,
"appendfsync = \"{}\"",
self.persistence.appendfsync.as_str(),
);
let _ = writeln!(
out,
"auto_aof_rewrite_percentage = {}",
self.persistence.auto_aof_rewrite_percentage,
);
let _ = writeln!(
out,
"auto_aof_rewrite_min_size = {}",
self.persistence.auto_aof_rewrite_min_size,
);
let _ = writeln!(out);
let _ = writeln!(out, "[memory]");
let _ = writeln!(out, "maxmemory = {}", self.memory.maxmemory);
let _ = writeln!(
out,
"maxmemory_policy = \"{}\"",
self.memory.maxmemory_policy.as_str(),
);
}
fn write_toml_tuning_sections(&self, out: &mut String) {
use std::fmt::Write;
let _ = writeln!(out);
let _ = writeln!(out, "[expiry]");
let _ = writeln!(out, "hz = {}", self.expiry.hz);
let _ = writeln!(out, "sample = {}", self.expiry.sample);
let _ = writeln!(out);
let _ = writeln!(out, "[log]");
let _ = writeln!(out, "level = \"{}\"", self.log.level.as_str());
let _ = writeln!(
out,
"output = \"{}\"",
escape_toml_basic_string(&self.log.output.as_str()),
);
let _ = writeln!(out);
let _ = writeln!(out, "[notification]");
let _ = writeln!(
out,
"notify_keyspace_events = \"{}\"",
escape_toml_basic_string(&self.notification.notify_keyspace_events),
);
let _ = writeln!(out);
let _ = writeln!(out, "[advanced]");
let _ = writeln!(out, "spin_limit = {}", self.advanced.spin_limit);
let _ = writeln!(out, "park_timeout_ms = {}", self.advanced.park_timeout_ms);
let _ = writeln!(out, "tick_check_every = {}", self.advanced.tick_check_every);
let _ = writeln!(out, "ring_capacity = {}", self.advanced.ring_capacity);
let _ = writeln!(out);
let _ = writeln!(out, "[slowlog]");
let _ = writeln!(
out,
"slower_than_micros = {}",
self.slowlog.slower_than_micros,
);
let _ = writeln!(out, "max_len = {}", self.slowlog.max_len);
}
fn write_toml_service_sections(&self, out: &mut String) {
use std::fmt::Write;
let pairs = canonical_pairs(self);
for section in SERVICE_SECTIONS {
let _ = writeln!(out);
let _ = writeln!(out, "[{section}]");
for p in pairs.iter().filter(|p| p.section == section) {
let _ = writeln!(out, "{} = {}", p.key, p.value);
}
}
}
}
fn escape_toml_basic_string(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
match c {
'\\' => out.push_str("\\\\"),
'"' => out.push_str("\\\""),
other => out.push(other),
}
}
out
}
pub(crate) struct CanonicalPair {
pub(crate) section: &'static str,
pub(crate) key: &'static str,
pub(crate) value: String,
}
pub(crate) fn canonical_pairs(cfg: &Config) -> Vec<CanonicalPair> {
let mut v = Vec::with_capacity(40);
push_server(&mut v, cfg);
push_persistence(&mut v, cfg);
push_memory(&mut v, cfg);
push_expiry(&mut v, cfg);
push_log(&mut v, cfg);
push_notification(&mut v, cfg);
push_advanced(&mut v, cfg);
push_slowlog(&mut v, cfg);
push_cluster(&mut v, cfg);
push_replication(&mut v, cfg);
push_lua(&mut v, cfg);
push_metrics(&mut v, cfg);
push_audit(&mut v, cfg);
push_feed(&mut v, cfg);
v
}
#[allow(clippy::many_single_char_names)]
fn push_server(v: &mut Vec<CanonicalPair>, cfg: &Config) {
let [a, b, c, d] = cfg.server.bind;
push(v, "server", "bind", format!("\"{a}.{b}.{c}.{d}\""));
push(v, "server", "port", cfg.server.port.to_string());
push(v, "server", "threads", cfg.server.threads.to_string());
if let Some(n) = cfg.server.accept_shards {
push(v, "server", "accept_shards", n.to_string());
}
push(v, "server", "max_clients", cfg.server.max_clients.to_string());
push(
v,
"server",
"data_dir",
toml_string(&cfg.server.data_dir.display().to_string()),
);
}
fn push_persistence(v: &mut Vec<CanonicalPair>, cfg: &Config) {
let p = &cfg.persistence;
push(v, "persistence", "aof", p.aof.to_string());
push(v, "persistence", "appendfsync", toml_string(p.appendfsync.as_str()));
push(
v,
"persistence",
"auto_aof_rewrite_percentage",
p.auto_aof_rewrite_percentage.to_string(),
);
push(
v,
"persistence",
"auto_aof_rewrite_min_size",
p.auto_aof_rewrite_min_size.to_string(),
);
}
fn push_memory(v: &mut Vec<CanonicalPair>, cfg: &Config) {
push(v, "memory", "maxmemory", cfg.memory.maxmemory.to_string());
push(
v,
"memory",
"maxmemory_policy",
toml_string(cfg.memory.maxmemory_policy.as_str()),
);
}
fn push_expiry(v: &mut Vec<CanonicalPair>, cfg: &Config) {
push(v, "expiry", "hz", cfg.expiry.hz.to_string());
push(v, "expiry", "sample", cfg.expiry.sample.to_string());
}
fn push_log(v: &mut Vec<CanonicalPair>, cfg: &Config) {
push(v, "log", "level", toml_string(cfg.log.level.as_str()));
push(v, "log", "output", toml_string(&log_output_str(&cfg.log.output)));
}
fn push_notification(v: &mut Vec<CanonicalPair>, cfg: &Config) {
push(
v,
"notification",
"notify_keyspace_events",
toml_string(&cfg.notification.notify_keyspace_events),
);
}
fn push_advanced(v: &mut Vec<CanonicalPair>, cfg: &Config) {
let a = &cfg.advanced;
push(v, "advanced", "spin_limit", a.spin_limit.to_string());
push(v, "advanced", "park_timeout_ms", a.park_timeout_ms.to_string());
push(v, "advanced", "tick_check_every", a.tick_check_every.to_string());
push(v, "advanced", "ring_capacity", a.ring_capacity.to_string());
}
fn push_slowlog(v: &mut Vec<CanonicalPair>, cfg: &Config) {
push(
v,
"slowlog",
"slower_than_micros",
cfg.slowlog.slower_than_micros.to_string(),
);
push(v, "slowlog", "max_len", cfg.slowlog.max_len.to_string());
}
fn push_cluster(v: &mut Vec<CanonicalPair>, cfg: &Config) {
let cl = &cfg.cluster;
push(v, "cluster", "enabled", cl.enabled.to_string());
push(v, "cluster", "port_base", cl.port_base.to_string());
push(v, "cluster", "node_id", toml_string(&cl.node_id));
push(v, "cluster", "elect_port_base", cl.elect_port_base.to_string());
let peers: Vec<String> = cl.peers.iter().map(PeerEntry::to_token).collect();
push(v, "cluster", "peers", toml_string(&peers.join(",")));
let scopes: Vec<String> = cl.scopes.iter().map(ScopeEntry::to_token).collect();
push(v, "cluster", "scopes", toml_string(&scopes.join(",")));
}
fn push_replication(v: &mut Vec<CanonicalPair>, cfg: &Config) {
let r = &cfg.replication;
push(v, "replication", "role", toml_string(r.role.as_str()));
if let Some(up) = &r.upstream {
push(v, "replication", "upstream", toml_string(up));
}
push(v, "replication", "listen_port_base", r.listen_port_base.to_string());
push(
v,
"replication",
"replication_buffer_size",
r.replication_buffer_size.to_string(),
);
push(v, "replication", "reconnect_window_ms", r.reconnect_window_ms.to_string());
push(
v,
"replication",
"min_replicas_to_write",
r.min_replicas_to_write.to_string(),
);
push(
v,
"replication",
"min_replicas_max_lag_ms",
r.min_replicas_max_lag_ms.to_string(),
);
push(
v,
"replication",
"replica_max_staleness_ms",
r.replica_max_staleness_ms.to_string(),
);
push(v, "replication", "replica_read_only", r.replica_read_only.to_string());
push(v, "replication", "single_source", r.single_source.to_string());
}
fn push_lua(v: &mut Vec<CanonicalPair>, cfg: &Config) {
push(v, "lua", "time_limit_ms", cfg.lua.time_limit_ms.to_string());
push(v, "lua", "allow_dialects", toml_string(&cfg.lua.allow_dialects.join(",")));
}
fn push_metrics(v: &mut Vec<CanonicalPair>, cfg: &Config) {
push(v, "metrics", "listen_port", cfg.metrics.listen_port.to_string());
}
fn push_audit(v: &mut Vec<CanonicalPair>, cfg: &Config) {
push(
v,
"audit",
"log_path",
toml_string(&cfg.audit.log_path.display().to_string()),
);
}
fn push_feed(v: &mut Vec<CanonicalPair>, cfg: &Config) {
push(v, "feed", "enabled", cfg.feed.enabled.to_string());
push(v, "feed", "feed_buffer_size", cfg.feed.feed_buffer_size.to_string());
}
fn push(v: &mut Vec<CanonicalPair>, section: &'static str, key: &'static str, value: String) {
v.push(CanonicalPair { section, key, value });
}
fn log_output_str(o: &LogOutput) -> String {
o.as_str().into_owned()
}
fn toml_string(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
out.push_str(&escape_toml_basic_string(s));
out.push('"');
out
}
#[cfg(test)]
#[path = "emit_tests.rs"]
mod tests;