use std::path::PathBuf;
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum OutputFormat {
#[default]
Pretty,
Json,
JsonCompact,
}
impl std::str::FromStr for OutputFormat {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"pretty" => Ok(Self::Pretty),
"json" => Ok(Self::Json),
"json-compact" => Ok(Self::JsonCompact),
_ => Err(format!(
"Invalid output format: '{}'. Use 'pretty', 'json', or 'json-compact'",
s
)),
}
}
}
#[derive(Debug, Clone)]
pub struct Config {
pub endpoint: String,
pub timeout: Duration,
pub format: OutputFormat,
pub no_color: bool,
pub no_header: bool,
pub no_pager: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
endpoint: "http://localhost:3000".to_string(),
timeout: Duration::from_secs(30),
format: OutputFormat::Pretty,
no_color: false,
no_header: false,
no_pager: false,
}
}
}
impl Config {
#[allow(clippy::too_many_arguments)]
pub fn new(
endpoint: String,
timeout: Duration,
format: OutputFormat,
no_color: bool,
no_header: bool,
no_pager: bool,
) -> Self {
Self {
endpoint,
timeout,
format,
no_color,
no_header,
no_pager,
}
}
pub fn endpoint_from_env() -> String {
std::env::var("OTELITE_ENDPOINT").unwrap_or_else(|_| "http://localhost:3000".to_string())
}
pub fn config_dir() -> PathBuf {
let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
PathBuf::from(home).join(".config").join("otelite")
}
pub fn config_file() -> PathBuf {
Self::config_dir().join("config.toml")
}
pub fn is_first_run() -> bool {
!Self::config_file().exists()
}
pub fn create_default_config() -> std::io::Result<()> {
let config_dir = Self::config_dir();
std::fs::create_dir_all(&config_dir)?;
let config_file = Self::config_file();
let default_config = r#"# Otelite Configuration
# This file was automatically generated on first run
[server]
# Dashboard bind address
addr = "127.0.0.1:3000"
# Storage database path
storage_path = "otelite.db"
[otlp]
# OTLP gRPC receiver address
grpc_addr = "0.0.0.0:4317"
# OTLP HTTP receiver address
http_addr = "0.0.0.0:4318"
[cli]
# Default output format (pretty or json)
format = "pretty"
# Request timeout in seconds
timeout = 30
"#;
std::fs::write(config_file, default_config)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_output_format_from_str() {
assert_eq!(
"pretty".parse::<OutputFormat>().unwrap(),
OutputFormat::Pretty
);
assert_eq!("json".parse::<OutputFormat>().unwrap(), OutputFormat::Json);
assert_eq!(
"json-compact".parse::<OutputFormat>().unwrap(),
OutputFormat::JsonCompact
);
assert_eq!(
"PRETTY".parse::<OutputFormat>().unwrap(),
OutputFormat::Pretty
);
assert_eq!("JSON".parse::<OutputFormat>().unwrap(), OutputFormat::Json);
assert_eq!(
"JSON-COMPACT".parse::<OutputFormat>().unwrap(),
OutputFormat::JsonCompact
);
assert!("invalid".parse::<OutputFormat>().is_err());
}
#[test]
fn test_config_default() {
let config = Config::default();
assert_eq!(config.endpoint, "http://localhost:3000");
assert_eq!(config.timeout, Duration::from_secs(30));
assert_eq!(config.format, OutputFormat::Pretty);
assert!(!config.no_color);
assert!(!config.no_header);
}
#[test]
fn test_config_new() {
let config = Config::new(
"http://example.com:9090".to_string(),
Duration::from_secs(60),
OutputFormat::Json,
true,
true,
true,
);
assert_eq!(config.endpoint, "http://example.com:9090");
assert_eq!(config.timeout, Duration::from_secs(60));
assert_eq!(config.format, OutputFormat::Json);
assert!(config.no_color);
assert!(config.no_header);
assert!(config.no_pager);
}
}