use super::daemon_utils::daemon_base_url;
use anyhow::{anyhow, bail, Context, Result};
use clap::{Subcommand, ValueEnum};
use colored::Colorize;
use serde_json::{json, Value};
#[derive(Subcommand, Debug)]
pub enum ConfigAction {
Get {
key: Option<ConfigKey>,
},
Set {
key: ConfigKey,
value: String,
},
}
#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq)]
pub enum ConfigKey {
MemoryLimit,
IndexMemoryLimit,
}
impl ConfigKey {
fn json_field(self) -> &'static str {
match self {
ConfigKey::MemoryLimit => "memory_limit_mb",
ConfigKey::IndexMemoryLimit => "index_memory_limit_mb",
}
}
fn display_name(self) -> &'static str {
match self {
ConfigKey::MemoryLimit => "memory-limit",
ConfigKey::IndexMemoryLimit => "index-memory-limit",
}
}
}
fn parse_value(raw: &str) -> Result<Option<u64>> {
let trimmed = raw.trim();
let lower = trimmed.to_ascii_lowercase();
if matches!(
lower.as_str(),
"0" | "off" | "none" | "disable" | "disabled" | "unlimited"
) {
return Ok(None);
}
trimmed
.parse::<u64>()
.map(Some)
.map_err(|_| anyhow!("value must be a number in MB, or 0/off/none/disable/unlimited"))
}
fn fmt_mb(v: Option<&Value>) -> String {
match v {
None | Some(Value::Null) => "unlimited".to_string(),
Some(Value::Number(n)) => match n.as_u64() {
Some(mb) => format!("{mb} MB"),
None => n.to_string(),
},
Some(other) => other.to_string(),
}
}
pub async fn handle_config(action: ConfigAction) -> Result<()> {
match action {
ConfigAction::Get { key } => handle_config_get(key).await,
ConfigAction::Set { key, value } => handle_config_set(key, &value).await,
}
}
async fn handle_config_get(key: Option<ConfigKey>) -> Result<()> {
let base = daemon_base_url();
let client = trusty_common::server::daemon_http_client()?;
let url = format!("{base}/config");
let resp = client
.get(&url)
.send()
.await
.with_context(daemon_unreachable_hint)?;
if !resp.status().is_success() {
bail!("daemon returned HTTP {} from {}", resp.status(), url);
}
let body: Value = resp
.json()
.await
.context("daemon returned invalid JSON from /config")?;
if let Some(k) = key {
let field = k.json_field();
let v = body.get(field);
println!("{}: {}", k.display_name().bold(), fmt_mb(v));
} else {
for k in [ConfigKey::MemoryLimit, ConfigKey::IndexMemoryLimit] {
let v = body.get(k.json_field());
println!("{}: {}", k.display_name().bold(), fmt_mb(v));
}
}
Ok(())
}
async fn handle_config_set(key: ConfigKey, raw_value: &str) -> Result<()> {
let parsed = parse_value(raw_value)?;
let base = daemon_base_url();
let client = trusty_common::server::daemon_http_client()?;
let url = format!("{base}/config");
let body = match parsed {
Some(n) => json!({ key.json_field(): n }),
None => json!({ key.json_field(): Value::Null }),
};
let resp = client
.patch(&url)
.json(&body)
.send()
.await
.with_context(daemon_unreachable_hint)?;
if !resp.status().is_success() {
bail!("daemon returned HTTP {} from {}", resp.status(), url);
}
let new: Value = resp
.json()
.await
.context("daemon returned invalid JSON from /config")?;
let pretty_after = match parsed {
Some(n) => format!("{n} MB"),
None => "unlimited".to_string(),
};
println!(
"{} {} → {}",
"✓".green(),
key.display_name().bold(),
pretty_after
);
println!();
println!("{}", "Daemon configuration:".dimmed());
for k in [ConfigKey::MemoryLimit, ConfigKey::IndexMemoryLimit] {
let v = new.get(k.json_field());
println!(" {}: {}", k.display_name(), fmt_mb(v));
}
Ok(())
}
fn daemon_unreachable_hint() -> &'static str {
"daemon is not running (no port.lock found, or daemon unreachable). \
Start it with `trusty-search start`."
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_value_accepts_numbers_and_disable_tokens() {
assert_eq!(parse_value("16384").unwrap(), Some(16384));
assert_eq!(parse_value(" 4096 ").unwrap(), Some(4096));
for tok in [
"0",
"off",
"OFF",
"None",
"disable",
"disabled",
"unlimited",
] {
assert_eq!(parse_value(tok).unwrap(), None, "token: {tok}");
}
assert!(parse_value("not-a-number").is_err());
assert!(parse_value("").is_err());
assert!(parse_value("-5").is_err()); }
#[test]
fn config_key_json_field() {
assert_eq!(ConfigKey::MemoryLimit.json_field(), "memory_limit_mb");
assert_eq!(
ConfigKey::IndexMemoryLimit.json_field(),
"index_memory_limit_mb"
);
assert_eq!(ConfigKey::MemoryLimit.display_name(), "memory-limit");
assert_eq!(
ConfigKey::IndexMemoryLimit.display_name(),
"index-memory-limit"
);
}
#[test]
fn fmt_mb_renders_null_as_unlimited() {
assert_eq!(fmt_mb(None), "unlimited");
assert_eq!(fmt_mb(Some(&Value::Null)), "unlimited");
assert_eq!(fmt_mb(Some(&json!(4096))), "4096 MB");
}
}