rufish 0.4.0

An asynchronous Redfish client library for BMC/server management in Rust.
Documentation
//! BIOS configuration export/import for fleet-wide deployment.

use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;

/// A portable BIOS configuration that can be exported from one system
/// and imported to another.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BiosConfig {
    /// Source system info (for reference only).
    pub source: Option<BiosConfigSource>,
    /// The BIOS attributes to apply.
    pub attributes: HashMap<String, Value>,
}

/// Metadata about where this config was exported from.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BiosConfigSource {
    pub host: Option<String>,
    pub system_id: Option<String>,
    pub model: Option<String>,
    pub bios_version: Option<String>,
    pub exported_at: Option<String>,
}

/// Result of comparing two BIOS configs.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BiosConfigDiff {
    /// Attributes that differ between source and target.
    pub changed: HashMap<String, (Value, Value)>,
    /// Attributes only in source.
    pub only_in_source: HashMap<String, Value>,
    /// Attributes only in target.
    pub only_in_target: HashMap<String, Value>,
}

impl BiosConfig {
    /// Compare this config against another, returning differences.
    pub fn diff(&self, other: &BiosConfig) -> BiosConfigDiff {
        let mut changed = HashMap::new();
        let mut only_in_source = HashMap::new();
        let mut only_in_target = HashMap::new();

        for (key, val) in &self.attributes {
            match other.attributes.get(key) {
                Some(other_val) if val != other_val => {
                    changed.insert(key.clone(), (val.clone(), other_val.clone()));
                }
                None => {
                    only_in_source.insert(key.clone(), val.clone());
                }
                _ => {}
            }
        }

        for (key, val) in &other.attributes {
            if !self.attributes.contains_key(key) {
                only_in_target.insert(key.clone(), val.clone());
            }
        }

        BiosConfigDiff { changed, only_in_source, only_in_target }
    }

    /// Serialize to JSON string.
    pub fn to_json(&self) -> Result<String, serde_json::Error> {
        serde_json::to_string_pretty(self)
    }

    /// Deserialize from JSON string.
    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
        serde_json::from_str(json)
    }

    /// Filter attributes to only include the specified keys.
    pub fn filter(&self, keys: &[&str]) -> Self {
        let attributes = self.attributes
            .iter()
            .filter(|(k, _)| keys.contains(&k.as_str()))
            .map(|(k, v)| (k.clone(), v.clone()))
            .collect();
        Self { source: self.source.clone(), attributes }
    }
}