agent-migrate-core 0.6.1

Core models and transactional filesystem engine for agent configuration migration
Documentation
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};

use serde::{Deserialize, Serialize};

use crate::BundleRelativePath;
use crate::transaction::SnapshotId;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConflictPolicy {
    Fail,
    Replace,
    Merge,
    Rename,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ChangeKind {
    Add,
    Identical,
    Merge,
    Replace,
    Rename,
    Skip,
    Conflict,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PlannedWrite {
    pub path: BundleRelativePath,
    pub content: Vec<u8>,
    pub mode: u32,
}

impl PlannedWrite {
    #[must_use]
    pub const fn new(path: BundleRelativePath, content: Vec<u8>) -> Self {
        Self {
            path,
            content,
            mode: 0o600,
        }
    }

    #[must_use]
    pub const fn with_mode(mut self, mode: u32) -> Self {
        self.mode = mode & 0o777;
        self
    }
}

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct WritePlan {
    writes: BTreeMap<BundleRelativePath, PlannedWrite>,
}

impl WritePlan {
    pub fn insert(&mut self, write: PlannedWrite) {
        let _previous = self.writes.insert(write.path.clone(), write);
    }

    #[must_use]
    pub fn get(&self, path: &BundleRelativePath) -> Option<&PlannedWrite> {
        self.writes.get(path)
    }

    #[must_use]
    pub fn writes(&self) -> impl ExactSizeIterator<Item = &PlannedWrite> {
        self.writes.values()
    }

    #[must_use]
    pub fn len(&self) -> usize {
        self.writes.len()
    }

    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.writes.is_empty()
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransactionOptions {
    root: PathBuf,
    state_root: PathBuf,
    conflict_policy: ConflictPolicy,
    dry_run: bool,
}

impl TransactionOptions {
    #[must_use]
    pub fn new(root: &Path, state_root: &Path) -> Self {
        Self {
            root: root.to_path_buf(),
            state_root: state_root.to_path_buf(),
            conflict_policy: ConflictPolicy::Fail,
            dry_run: false,
        }
    }

    #[must_use]
    pub const fn with_conflict_policy(mut self, conflict_policy: ConflictPolicy) -> Self {
        self.conflict_policy = conflict_policy;
        self
    }

    #[must_use]
    pub const fn dry_run(mut self) -> Self {
        self.dry_run = true;
        self
    }

    #[must_use]
    pub fn root(&self) -> &Path {
        &self.root
    }

    #[must_use]
    pub fn state_root(&self) -> &Path {
        &self.state_root
    }

    #[must_use]
    pub const fn conflict_policy(&self) -> ConflictPolicy {
        self.conflict_policy
    }

    #[must_use]
    pub const fn is_dry_run(&self) -> bool {
        self.dry_run
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TransactionResult {
    pub snapshot_id: SnapshotId,
    pub dry_run: bool,
    pub writes: usize,
}