use std::path::Path;
use std::str::FromStr;
use serde::Deserialize;
use crate::error::DapzError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum OutputFormat {
Json,
Passthrough,
}
impl std::str::FromStr for OutputFormat {
type Err = DapzError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim().to_lowercase().as_str() {
"json" => Ok(Self::Json),
"passthrough" => Ok(Self::Passthrough),
_ => Err(DapzError::Config(format!("unknown output format: {s}"))),
}
}
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct CappingConfig {
pub max_frames: usize,
pub max_variables: usize,
pub max_output_length: usize,
}
impl CappingConfig {
pub fn any_enabled(&self) -> bool {
self.max_frames > 0 || self.max_variables > 0 || self.max_output_length > 0
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct Config {
pub backend_cmd: String,
#[serde(default)]
pub capping: CappingConfig,
#[serde(default = "default_true")]
pub enable_output_compress: bool,
#[serde(default = "default_true")]
pub enable_variables_compress: bool,
#[serde(default = "default_true")]
pub enable_stacktrace_compress: bool,
#[serde(default = "default_output_format")]
pub output_format: OutputFormat,
#[serde(default = "default_log_level")]
pub log_level: String,
}
fn default_true() -> bool {
true
}
fn default_output_format() -> OutputFormat {
OutputFormat::Json
}
fn default_log_level() -> String {
"info".into()
}
impl Default for Config {
fn default() -> Self {
Self {
backend_cmd: String::new(),
capping: CappingConfig::default(),
enable_output_compress: true,
enable_variables_compress: true,
enable_stacktrace_compress: true,
output_format: OutputFormat::Json,
log_level: "info".into(),
}
}
}
impl Config {
pub fn builder() -> ConfigBuilder {
ConfigBuilder::default()
}
pub fn from_file(path: impl AsRef<Path>) -> Result<Self, DapzError> {
let content = std::fs::read_to_string(path.as_ref())
.map_err(|e| DapzError::Config(format!("cannot read config file: {e}")))?;
toml::from_str(&content).map_err(|e| DapzError::Config(format!("invalid config file: {e}")))
}
pub fn is_interceptor_enabled(&self, name: &str) -> bool {
match name {
"capping" => self.capping.any_enabled(),
"output_compressor" => self.enable_output_compress,
"variables_compressor" => self.enable_variables_compress,
"stacktrace_compressor" => self.enable_stacktrace_compress,
_ => true,
}
}
}
#[derive(Debug, Default)]
pub struct ConfigBuilder {
backend_cmd: Option<String>,
capping: Option<CappingConfig>,
enable_output_compress: Option<bool>,
enable_variables_compress: Option<bool>,
enable_stacktrace_compress: Option<bool>,
output_format: Option<OutputFormat>,
log_level: Option<String>,
}
impl ConfigBuilder {
pub fn backend_cmd(mut self, cmd: impl Into<String>) -> Self {
self.backend_cmd = Some(cmd.into());
self
}
pub fn enable_output_compress(mut self, enable: bool) -> Self {
self.enable_output_compress = Some(enable);
self
}
pub fn enable_variables_compress(mut self, enable: bool) -> Self {
self.enable_variables_compress = Some(enable);
self
}
pub fn enable_stacktrace_compress(mut self, enable: bool) -> Self {
self.enable_stacktrace_compress = Some(enable);
self
}
pub fn output_format(mut self, fmt: OutputFormat) -> Self {
self.output_format = Some(fmt);
self
}
pub fn log_level(mut self, level: impl Into<String>) -> Self {
self.log_level = Some(level.into());
self
}
pub fn capping(mut self, capping: CappingConfig) -> Self {
self.capping = Some(capping);
self
}
pub fn build(self) -> Result<Config, DapzError> {
let backend_cmd = self
.backend_cmd
.or_else(|| std::env::var("DAPZ_BACKEND_CMD").ok())
.ok_or_else(|| DapzError::Config("backend_cmd is required".into()))?;
let enable_output_compress = self
.enable_output_compress
.or_else(|| {
std::env::var("DAPZ_ENABLE_OUTPUT_COMPRESS")
.ok()
.and_then(|v| v.parse().ok())
})
.unwrap_or(true);
let enable_variables_compress = self
.enable_variables_compress
.or_else(|| {
std::env::var("DAPZ_ENABLE_VARIABLES_COMPRESS")
.ok()
.and_then(|v| v.parse().ok())
})
.unwrap_or(true);
let enable_stacktrace_compress = self
.enable_stacktrace_compress
.or_else(|| {
std::env::var("DAPZ_ENABLE_STACKTRACE_COMPRESS")
.ok()
.and_then(|v| v.parse().ok())
})
.unwrap_or(true);
let log_level = self
.log_level
.or_else(|| std::env::var("DAPZ_LOG_LEVEL").ok())
.unwrap_or_else(|| "info".into());
let output_format = self
.output_format
.or_else(|| {
std::env::var("DAPZ_OUTPUT_FORMAT")
.ok()
.and_then(|v| OutputFormat::from_str(&v).ok())
})
.unwrap_or(OutputFormat::Json);
let capping = self.capping.unwrap_or_else(|| CappingConfig {
max_frames: std::env::var("DAPZ_MAX_FRAMES")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(0),
max_variables: std::env::var("DAPZ_MAX_VARIABLES")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(0),
max_output_length: std::env::var("DAPZ_MAX_OUTPUT_LENGTH")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(0),
});
Ok(Config {
backend_cmd,
capping,
enable_output_compress,
enable_variables_compress,
enable_stacktrace_compress,
output_format,
log_level,
})
}
}