weavatrix-refactor-plan 0.1.0

Evidence metadata, validation profiles, and canonical fingerprints for Weavatrix refactor plans
Documentation
use crate::{PlanError, PlanErrorCode, RefactorPlanLimits};

pub(super) struct TextBudget {
    limits: RefactorPlanLimits,
    total: usize,
}

impl TextBudget {
    pub(super) const fn new(limits: RefactorPlanLimits) -> Self {
        Self { limits, total: 0 }
    }

    pub(super) fn require_option(
        &mut self,
        value: Option<&str>,
        field: &str,
    ) -> Result<(), PlanError> {
        value.map_or_else(|| Err(missing(field)), |value| self.require(value, field))
    }

    pub(super) fn require(&mut self, value: &str, field: &str) -> Result<(), PlanError> {
        if value.is_empty() {
            return Err(invalid("evidence text must not be empty", field));
        }
        self.total = self
            .total
            .checked_add(value.len())
            .ok_or_else(|| too_large("evidence text byte total overflow"))?;
        if self.total > self.limits.max_evidence_text_bytes {
            return Err(too_large("evidence text exceeds its combined byte limit").at_field(field));
        }
        Ok(())
    }

    pub(super) fn code(&mut self, value: &str, field: &str) -> Result<(), PlanError> {
        if value.len() > self.limits.max_code_bytes {
            return Err(too_large("evidence code exceeds its byte limit").at_field(field));
        }
        self.require(value, field)
    }
}

pub(super) fn missing(field: &str) -> PlanError {
    PlanError::new(
        PlanErrorCode::EvidenceMissing,
        format!("strict evidence requires {field}"),
    )
    .at_field(field)
}

pub(super) fn invalid(message: impl Into<String>, field: &str) -> PlanError {
    PlanError::new(PlanErrorCode::EvidenceInvalid, message).at_field(field)
}

pub(super) fn too_large(message: impl Into<String>) -> PlanError {
    PlanError::new(PlanErrorCode::EvidenceTooLarge, message)
}