use serde::{Deserialize, Serialize};
use std::path::Path;
use crate::{MarkerPosition, PayloadConfig, PayloadError};
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Hash)]
#[serde(default)]
pub struct PayloadConfigFile {
pub max_per_category: usize,
pub deduplicate: bool,
pub marker_prefix: String,
pub marker_position: String,
pub target_runtime: Option<Vec<String>>,
pub exclude_categories: Vec<String>,
pub include_categories: Vec<String>,
pub grammar_dirs: Vec<String>,
pub max_payload_length: usize,
}
impl Default for PayloadConfigFile {
fn default() -> Self {
Self {
max_per_category: 0,
deduplicate: true,
marker_prefix: "SLN".into(),
marker_position: "prefix".into(),
target_runtime: None,
exclude_categories: Vec::new(),
include_categories: Vec::new(),
grammar_dirs: Vec::new(),
max_payload_length: 100_000,
}
}
}
impl PayloadConfigFile {
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, PayloadError> {
let content = std::fs::read_to_string(path.as_ref())?;
Self::from_toml(&content, path.as_ref().display().to_string())
}
pub fn from_toml(toml_str: &str, source: String) -> Result<Self, PayloadError> {
toml::from_str(toml_str).map_err(|e| PayloadError::ConfigParse {
file: source,
source: Box::new(e),
})
}
pub fn into_config(self) -> Result<PayloadConfig, PayloadError> {
Ok(PayloadConfig {
max_per_category: self.max_per_category,
deduplicate: self.deduplicate,
marker_prefix: self.marker_prefix,
exclude_categories: self.exclude_categories,
include_categories: self.include_categories,
target_runtime: self.target_runtime,
marker_position: parse_marker_position(&self.marker_position)
.map_err(PayloadError::InvalidConfig)?,
max_payload_length: self.max_payload_length,
})
}
pub fn grammar_dirs(&self) -> &[String] {
&self.grammar_dirs
}
}
impl std::fmt::Display for PayloadConfigFile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"PayloadConfigFile(max_per_category={}, grammar_dirs={})",
self.max_per_category,
self.grammar_dirs.len()
)
}
}
pub fn parse_marker_position(s: &str) -> Result<MarkerPosition, String> {
match s {
"prefix" => Ok(MarkerPosition::Prefix),
"suffix" => Ok(MarkerPosition::Suffix),
"inline" => Ok(MarkerPosition::Inline),
s if s.starts_with("replace:") => Ok(MarkerPosition::Replace(s[8..].to_string())),
_ => Err(format!("invalid marker_position '{s}': expected 'prefix', 'suffix', 'inline', or 'replace:PLACEHOLDER'.")),
}
}