use crate::{Error, Result};
use camino::{Utf8Path, Utf8PathBuf};
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TraceConfig {
pub lake_root: Utf8PathBuf,
pub recursive: bool,
#[serde(with = "duration_seconds")]
pub timeout: Duration,
pub keep_raw_output: bool,
pub include_warnings: bool,
pub include_passes: bool,
#[serde(default)]
pub exclude: Vec<String>,
}
impl TraceConfig {
#[must_use]
pub fn new(lake_root: Utf8PathBuf) -> Self {
Self {
lake_root,
recursive: false,
timeout: Duration::from_secs(60),
keep_raw_output: false,
include_warnings: true,
include_passes: true,
exclude: Vec::new(),
}
}
#[must_use]
pub const fn recursive(mut self, recursive: bool) -> Self {
self.recursive = recursive;
self
}
#[must_use]
pub const fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
#[must_use]
pub const fn keep_raw_output(mut self, keep_raw_output: bool) -> Self {
self.keep_raw_output = keep_raw_output;
self
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ReportConfig {
pub sample_limit: usize,
}
impl Default for ReportConfig {
fn default() -> Self {
Self { sample_limit: 10 }
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FileConfig {
#[serde(default)]
pub project: ProjectConfig,
#[serde(default)]
pub trace: TraceFileConfig,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ProjectConfig {
pub name: Option<String>,
pub lake_root: Option<Utf8PathBuf>,
#[serde(default)]
pub source_roots: Vec<Utf8PathBuf>,
#[serde(default)]
pub exclude: Vec<String>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct TraceFileConfig {
pub timeout_secs: Option<u64>,
pub keep_raw_output: Option<bool>,
pub include_warnings: Option<bool>,
pub only_failures: Option<bool>,
}
impl FileConfig {
pub fn load(path: &Utf8Path) -> Result<Self> {
let text = std::fs::read_to_string(path).map_err(|source| Error::ConfigRead {
path: path.to_path_buf(),
source,
})?;
toml::from_str(&text).map_err(|source| Error::ConfigParse {
path: path.to_path_buf(),
source,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn parse(text: &str) -> Result<FileConfig> {
toml::from_str(text).map_err(|source| Error::ConfigParse {
path: Utf8PathBuf::from("<test>"),
source,
})
}
#[test]
fn parses_full_config() -> Result<()> {
let config = parse(
r#"
[project]
name = "demo"
lake_root = "."
source_roots = ["src", "test"]
exclude = [".lake/"]
[trace]
timeout_secs = 45
keep_raw_output = true
include_warnings = false
only_failures = true
"#,
)?;
assert_eq!(config.project.name.as_deref(), Some("demo"));
assert_eq!(config.project.source_roots.len(), 2);
assert_eq!(config.project.exclude, vec![".lake/".to_owned()]);
assert_eq!(config.trace.timeout_secs, Some(45));
assert_eq!(config.trace.keep_raw_output, Some(true));
assert_eq!(config.trace.include_warnings, Some(false));
assert_eq!(config.trace.only_failures, Some(true));
Ok(())
}
#[test]
fn empty_config_is_all_defaults() -> Result<()> {
let config = parse("")?;
assert!(config.project.name.is_none());
assert!(config.project.source_roots.is_empty());
assert!(config.trace.timeout_secs.is_none());
Ok(())
}
#[test]
fn unknown_field_is_rejected() {
assert!(parse("[trace]\nbogus = 1\n").is_err());
}
}
mod duration_seconds {
use serde::{Deserialize, Deserializer, Serializer};
use std::time::Duration;
pub(crate) fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u64(duration.as_secs())
}
pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
let seconds = u64::deserialize(deserializer)?;
Ok(Duration::from_secs(seconds))
}
}