use serde::{Deserialize, Serialize};
fn default_true() -> bool {
true
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TitleConfig {
#[serde(default = "default_true")]
pub enabled: bool,
pub provider: Option<String>,
pub model: Option<String>,
}
impl Default for TitleConfig {
fn default() -> Self {
Self {
enabled: true,
provider: None,
model: None,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ObservabilityConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub exporter: TelemetryExporterKind,
pub endpoint: Option<String>,
pub service_name: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TelemetryExporterKind {
#[default]
Otlp,
Stdout,
None,
}
impl TelemetryExporterKind {
pub fn is_none(self) -> bool {
matches!(self, Self::None)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_title_config_default() {
let cfg = TitleConfig::default();
assert!(cfg.enabled);
assert!(cfg.provider.is_none());
assert!(cfg.model.is_none());
}
#[test]
fn test_title_config_fields() {
let cfg = TitleConfig {
enabled: false,
provider: Some("anthropic".to_string()),
model: Some("claude-haiku-4-5-20251001".to_string()),
};
assert!(!cfg.enabled);
assert_eq!(cfg.provider.as_deref(), Some("anthropic"));
assert_eq!(cfg.model.as_deref(), Some("claude-haiku-4-5-20251001"));
}
#[test]
fn test_title_config_clone_and_debug() {
let cfg = TitleConfig::default();
let cloned = cfg.clone();
assert_eq!(cloned.enabled, cfg.enabled);
assert!(format!("{:?}", cfg).contains("TitleConfig"));
}
#[test]
fn test_title_config_serde_roundtrip() {
let cfg = TitleConfig {
enabled: true,
provider: Some("openai".to_string()),
model: Some("gpt-4o-mini".to_string()),
};
let json = serde_json::to_string(&cfg).unwrap();
let back: TitleConfig = serde_json::from_str(&json).unwrap();
assert_eq!(back.enabled, cfg.enabled);
assert_eq!(back.provider, cfg.provider);
assert_eq!(back.model, cfg.model);
}
#[test]
fn test_title_config_deserialize_defaults_enabled_true() {
let toml_str = r#"
provider = "anthropic"
model = "claude-haiku-4-5-20251001"
"#;
let cfg: TitleConfig = toml::from_str(toml_str).unwrap();
assert!(cfg.enabled);
assert_eq!(cfg.provider.as_deref(), Some("anthropic"));
}
#[test]
fn test_title_config_deserialize_explicit_disabled() {
let toml_str = r#"
enabled = false
"#;
let cfg: TitleConfig = toml::from_str(toml_str).unwrap();
assert!(!cfg.enabled);
assert!(cfg.provider.is_none());
assert!(cfg.model.is_none());
}
#[test]
fn observability_config_defaults_to_disabled_otlp() {
let cfg = ObservabilityConfig::default();
assert!(!cfg.enabled);
assert_eq!(cfg.exporter, TelemetryExporterKind::Otlp);
assert!(cfg.endpoint.is_none());
assert!(cfg.service_name.is_none());
let parsed: ObservabilityConfig = toml::from_str("").unwrap();
assert_eq!(parsed, cfg);
}
#[test]
fn observability_config_full_roundtrip() {
let toml_str = r#"
enabled = true
exporter = "stdout"
endpoint = "http://collector:4318"
service_name = "leviath-prod"
"#;
let cfg: ObservabilityConfig = toml::from_str(toml_str).unwrap();
assert!(cfg.enabled);
assert_eq!(cfg.exporter, TelemetryExporterKind::Stdout);
assert_eq!(cfg.endpoint.as_deref(), Some("http://collector:4318"));
assert_eq!(cfg.service_name.as_deref(), Some("leviath-prod"));
let serialized = toml::to_string(&cfg).unwrap();
let back: ObservabilityConfig = toml::from_str(&serialized).unwrap();
assert_eq!(back, cfg);
assert!(format!("{cfg:?}").contains("ObservabilityConfig"));
}
#[test]
fn telemetry_exporter_kind_round_trips_through_config_syntax() {
for (kind, text) in [
(TelemetryExporterKind::Otlp, "otlp"),
(TelemetryExporterKind::Stdout, "stdout"),
(TelemetryExporterKind::None, "none"),
] {
let toml_str = format!("exporter = \"{text}\"");
let cfg: ObservabilityConfig = toml::from_str(&toml_str).unwrap();
assert_eq!(cfg.exporter, kind);
let serialized = toml::to_string(&cfg).unwrap();
assert!(serialized.contains(text), "{serialized} missing {text}");
}
}
#[test]
fn telemetry_exporter_kind_is_none_helper() {
assert!(TelemetryExporterKind::None.is_none());
assert!(!TelemetryExporterKind::Otlp.is_none());
assert!(!TelemetryExporterKind::Stdout.is_none());
}
#[test]
fn telemetry_exporter_kind_rejects_unknown_value() {
let err = toml::from_str::<ObservabilityConfig>("exporter = \"grpc\"");
assert!(err.is_err());
}
}