use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NppesConfig {
#[serde(default = "default_enable_progress_bar")]
pub enable_progress_bar: bool,
#[serde(default)]
pub parallel_threads: Option<usize>,
#[serde(default)]
pub validation_level: ValidationLevel,
#[serde(default = "default_index_on_load")]
pub index_on_load: bool,
#[serde(default)]
pub default_export_format: crate::ExportFormat,
#[serde(default)]
pub skip_invalid_records: bool,
#[serde(default)]
pub memory_limit: Option<usize>,
#[serde(default = "default_batch_size")]
pub batch_size: usize,
#[serde(default)]
pub temp_dir: Option<PathBuf>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ValidationLevel {
None,
Basic,
Standard,
Strict,
}
impl Default for ValidationLevel {
fn default() -> Self {
ValidationLevel::Standard
}
}
impl Default for NppesConfig {
fn default() -> Self {
Self {
enable_progress_bar: default_enable_progress_bar(),
parallel_threads: None,
validation_level: ValidationLevel::Standard,
index_on_load: default_index_on_load(),
default_export_format: crate::ExportFormat::Json,
skip_invalid_records: false,
memory_limit: None,
batch_size: default_batch_size(),
temp_dir: None,
}
}
}
fn default_enable_progress_bar() -> bool {
true
}
fn default_index_on_load() -> bool {
true
}
fn default_batch_size() -> usize {
10_000
}
impl NppesConfig {
pub fn new() -> Self {
Self::default()
}
pub fn from_env() -> Self {
let mut config = Self::default();
if let Ok(val) = std::env::var("NPPES_PROGRESS_BAR") {
config.enable_progress_bar = val.to_lowercase() == "true";
}
if let Ok(val) = std::env::var("NPPES_PARALLEL_THREADS") {
config.parallel_threads = match val.to_lowercase().as_str() {
"auto" | "0" => None,
num => num.parse().ok(),
};
}
if let Ok(val) = std::env::var("NPPES_VALIDATION_LEVEL") {
config.validation_level = match val.to_lowercase().as_str() {
"none" => ValidationLevel::None,
"basic" => ValidationLevel::Basic,
"standard" => ValidationLevel::Standard,
"strict" => ValidationLevel::Strict,
_ => ValidationLevel::Standard,
};
}
if let Ok(val) = std::env::var("NPPES_INDEX_ON_LOAD") {
config.index_on_load = val.to_lowercase() == "true";
}
if let Ok(val) = std::env::var("NPPES_SKIP_INVALID") {
config.skip_invalid_records = val.to_lowercase() == "true";
}
if let Ok(val) = std::env::var("NPPES_MEMORY_LIMIT") {
config.memory_limit = val.parse().ok();
}
if let Ok(val) = std::env::var("NPPES_BATCH_SIZE") {
if let Ok(size) = val.parse() {
config.batch_size = size;
}
}
if let Ok(val) = std::env::var("NPPES_TEMP_DIR") {
config.temp_dir = Some(PathBuf::from(val));
}
config
}
pub fn from_file<P: AsRef<Path>>(path: P) -> crate::Result<Self> {
let contents = std::fs::read_to_string(path.as_ref())?;
let config: Self = toml::from_str(&contents)
.map_err(|e| crate::NppesError::Configuration {
message: format!("Failed to parse config file: {}", e),
suggestion: Some("Check that the file is valid TOML format".to_string()),
})?;
Ok(config)
}
pub fn save<P: AsRef<Path>>(&self, path: P) -> crate::Result<()> {
let contents = toml::to_string_pretty(self)
.map_err(|e| crate::NppesError::Configuration {
message: format!("Failed to serialize config: {}", e),
suggestion: None,
})?;
std::fs::write(path, contents)?;
Ok(())
}
pub fn default_config_path() -> Option<PathBuf> {
directories::ProjectDirs::from("", "", "nppes")
.map(|dirs| dirs.config_dir().join("config.toml"))
}
pub fn load() -> Self {
if let Some(config_path) = Self::default_config_path() {
if config_path.exists() {
if let Ok(config) = Self::from_file(&config_path) {
return config;
}
}
}
Self::from_env()
}
pub fn performance() -> Self {
Self {
enable_progress_bar: false,
parallel_threads: None, validation_level: ValidationLevel::Basic,
index_on_load: true,
default_export_format: crate::ExportFormat::Json,
skip_invalid_records: true,
memory_limit: None,
batch_size: 50_000,
temp_dir: None,
}
}
pub fn safe() -> Self {
Self {
enable_progress_bar: true,
parallel_threads: Some(1), validation_level: ValidationLevel::Strict,
index_on_load: true,
default_export_format: crate::ExportFormat::Json,
skip_invalid_records: false,
memory_limit: Some(8 * 1024 * 1024 * 1024), batch_size: 1_000,
temp_dir: None,
}
}
}
use std::sync::RwLock;
lazy_static::lazy_static! {
static ref GLOBAL_CONFIG: RwLock<Option<NppesConfig>> = RwLock::new(None);
}
pub fn set_global_config(config: NppesConfig) {
*GLOBAL_CONFIG.write().unwrap() = Some(config);
}
pub fn global_config() -> NppesConfig {
GLOBAL_CONFIG.read().unwrap()
.as_ref()
.cloned()
.unwrap_or_else(NppesConfig::load)
}
pub fn clear_global_config() {
*GLOBAL_CONFIG.write().unwrap() = None;
}
pub struct ConfigBuilder {
config: NppesConfig,
}
impl ConfigBuilder {
pub fn new() -> Self {
Self {
config: NppesConfig::default(),
}
}
pub fn progress_bar(mut self, enabled: bool) -> Self {
self.config.enable_progress_bar = enabled;
self
}
pub fn parallel_threads(mut self, threads: Option<usize>) -> Self {
self.config.parallel_threads = threads;
self
}
pub fn validation_level(mut self, level: ValidationLevel) -> Self {
self.config.validation_level = level;
self
}
pub fn index_on_load(mut self, enabled: bool) -> Self {
self.config.index_on_load = enabled;
self
}
pub fn skip_invalid_records(mut self, skip: bool) -> Self {
self.config.skip_invalid_records = skip;
self
}
pub fn memory_limit(mut self, limit: Option<usize>) -> Self {
self.config.memory_limit = limit;
self
}
pub fn batch_size(mut self, size: usize) -> Self {
self.config.batch_size = size;
self
}
pub fn temp_dir<P: AsRef<Path>>(mut self, dir: P) -> Self {
self.config.temp_dir = Some(dir.as_ref().to_path_buf());
self
}
pub fn build(self) -> NppesConfig {
self.config
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_defaults() {
let config = NppesConfig::default();
assert!(config.enable_progress_bar);
assert!(config.index_on_load);
assert_eq!(config.validation_level, ValidationLevel::Standard);
}
#[test]
fn test_config_builder() {
let config = ConfigBuilder::new()
.progress_bar(false)
.parallel_threads(Some(4))
.validation_level(ValidationLevel::Strict)
.skip_invalid_records(true)
.batch_size(20_000)
.build();
assert!(!config.enable_progress_bar);
assert_eq!(config.parallel_threads, Some(4));
assert_eq!(config.validation_level, ValidationLevel::Strict);
assert!(config.skip_invalid_records);
assert_eq!(config.batch_size, 20_000);
}
}