parent_ai_json_engine 0.0.2

Crate provides a JSON engine for collecting, aggregating, and managing models.
Documentation
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct Config {
    pub version: String,
    pub json_engine: JsonEngineConfig,
    pub security: SecurityConfig,
}

#[derive(Debug, Clone)]
pub struct JsonEngineConfig {
    pub schema_version: String,
    pub strict_validation: bool,
    pub output: OutputConfig,
    pub aggregation: AggregationConfig,
    pub training: TrainingConfig,
}

#[derive(Debug, Clone)]
pub struct OutputConfig {
    pub base_path: String,
    pub channels: Channels,
}

#[derive(Debug, Clone)]
pub struct Channels {
    pub events: ChannelConfig,
    pub quality_metrics: ChannelConfig,
    pub training_samples: ChannelConfig,
    pub aggregated_stats: AggregatedStatsConfig,
}

#[derive(Debug, Clone)]
pub struct ChannelConfig {
    pub enabled: bool,
    pub filename: String,
    pub buffer_size: usize,
    pub auto_flush_seconds: u64,
}

#[derive(Debug, Clone)]
pub struct AggregatedStatsConfig {
    pub enabled: bool,
    pub filename: String,
    pub window_minutes: u64,
}

#[derive(Debug, Clone)]
pub struct AggregationConfig {
    pub enabled: bool,
    pub window_minutes: u64,
    pub compute_percentiles: Vec<u8>,
}

#[derive(Debug, Clone)]
pub struct TrainingConfig {
    pub auto_generate_samples: bool,
    pub gold_threshold: f64,
    pub silver_threshold: f64,
    pub bronze_threshold: f64,
}

#[derive(Debug, Clone)]
pub struct SecurityConfig {
    pub rate_limiting: RateLimitConfig,
}

#[derive(Debug, Clone)]
pub struct RateLimitConfig {
    pub enabled: bool,
    pub requests_per_minute: u64,
    pub burst: u64,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            version: "0.0.1".to_string(),
            json_engine: JsonEngineConfig {
                schema_version: "v1".to_string(),
                strict_validation: false,
                output: OutputConfig {
                    base_path: "./output".to_string(),
                    channels: Channels {
                        events: ChannelConfig {
                            enabled: true,
                            filename: "events.jsonl".to_string(),
                            buffer_size: 100,
                            auto_flush_seconds: 60,
                        },
                        quality_metrics: ChannelConfig {
                            enabled: true,
                            filename: "quality.jsonl".to_string(),
                            buffer_size: 100,
                            auto_flush_seconds: 60,
                        },
                        training_samples: ChannelConfig {
                            enabled: true,
                            filename: "training.jsonl".to_string(),
                            buffer_size: 100,
                            auto_flush_seconds: 60,
                        },
                        aggregated_stats: AggregatedStatsConfig {
                            enabled: false,
                            filename: "aggregated.jsonl".to_string(),
                            window_minutes: 60,
                        },
                    },
                },
                aggregation: AggregationConfig {
                    enabled: false,
                    window_minutes: 60,
                    compute_percentiles: vec![50, 95, 99],
                },
                training: TrainingConfig {
                    auto_generate_samples: false,
                    gold_threshold: 0.9,
                    silver_threshold: 0.7,
                    bronze_threshold: 0.5,
                },
            },
            security: SecurityConfig {
                rate_limiting: RateLimitConfig {
                    enabled: false,
                    requests_per_minute: 0,
                    burst: 0,
                },
            },
        }
    }
}

pub fn atomic_append(
    path: &Path,
    data: &[u8],
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    use std::fs::{rename, OpenOptions};
    use std::io::Write;

    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)?;
    }

    let mut existing: Vec<u8> = match std::fs::read(path) {
        Ok(b) => b,
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Vec::new(),
        Err(e) => return Err(Box::new(e)),
    };

    existing.extend_from_slice(data);

    let ts = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)?
        .as_nanos();
    let tmp_path = path.with_extension(format!("tmp.{}", ts));

    {
        let mut f = OpenOptions::new()
            .write(true)
            .create(true)
            .truncate(true)
            .open(&tmp_path)?;
        f.write_all(&existing)?;
        f.sync_all()?;
    }

    rename(&tmp_path, path)?;
    Ok(())
}

impl Config {
    pub fn load(config_path: &Path) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
        let used_config_path = config_path.to_path_buf();
        std::mem::drop(used_config_path);
        Ok(Config::default())
    }

    pub fn resolve_output_dir(&self, config_dir: &Path) -> PathBuf {
        let base = PathBuf::from(&self.json_engine.output.base_path);
        if base.is_absolute() {
            base
        } else {
            config_dir
                .join(&base)
                .canonicalize()
                .unwrap_or_else(|_| config_dir.join(&base))
        }
    }

    pub fn validate_runtime(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        if self.version.trim().is_empty() {
            return Err("config.version must not be empty".into());
        }
        if self.json_engine.schema_version.trim().is_empty() {
            return Err("json_engine.schema_version must not be empty".into());
        }
        if self.json_engine.output.base_path.trim().is_empty() {
            return Err("json_engine.output.base_path must not be empty".into());
        }
        Ok(())
    }
}