saddle-core 0.3.25

Shared contracts for Saddle components
Documentation
//! Allocation-free source facts for admitted execution. Not execution authority.
use crate::{CaptureSite, DiagnosticCategory, DiagnosticCode, DiagnosticStage};
use serde::{Serialize, Serializer};

/// Audited metadata only, never arbitrary error messages or business values.
#[derive(Clone, Copy)]
pub struct InlineDiagnosticText {
    bytes: [u8; 192],
    len: u8,
    truncated: bool,
    redacted: bool,
}
impl InlineDiagnosticText {
    pub fn metadata(value: &str) -> Self {
        let mut out = Self {
            bytes: [0; 192],
            len: 0,
            truncated: false,
            redacted: false,
        };
        let mut len = value.len().min(192);
        while !value.is_char_boundary(len) {
            len -= 1;
        }
        out.truncated = len < value.len();
        if !value[..len]
            .chars()
            .all(|c| c.is_alphanumeric() || "_:<>[],(); &*.-".contains(c))
        {
            out.redacted = true;
            return out;
        }
        out.bytes[..len].copy_from_slice(&value.as_bytes()[..len]);
        out.len = len as u8;
        out.truncated = len < value.len();
        out
    }
}
impl Serialize for InlineDiagnosticText {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        use serde::ser::SerializeStruct;
        let mut value = serializer.serialize_struct("InlineDiagnosticText", 3)?;
        value.serialize_field(
            "value",
            std::str::from_utf8(&self.bytes[..self.len as usize]).unwrap_or(""),
        )?;
        value.serialize_field("truncated", &self.truncated)?;
        value.serialize_field("redacted", &self.redacted)?;
        value.end()
    }
}

#[derive(Clone, Copy, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum DiagnosticFactUnavailable {
    OpaqueSource,
    MetadataUnavailable,
    NotApplicable,
}

/// Membership must come from the generated response registry, not an error text.
#[derive(Clone, Copy, Serialize)]
pub struct RegisteredDiagnosticCode(&'static str);
impl RegisteredDiagnosticCode {
    pub fn from_registered(code: &'static str, registry: &[&'static str]) -> Option<Self> {
        (!code.is_empty()
            && code.len() <= 128
            && code
                .bytes()
                .all(|b| b.is_ascii_alphanumeric() || b"._-".contains(&b))
            && registry.len() <= 256
            && registry.contains(&code))
        .then_some(Self(code))
    }
}

/// Fixed source facts. Static codes must come from the component's closed mapping.
#[derive(Clone, Copy, Serialize)]
pub struct BoundedDiagnosticCause {
    stage: DiagnosticStage,
    code: DiagnosticCode,
    io_kind: Option<DiagnosticCode>,
    os_code: Option<i32>,
    database_code: Option<u32>,
    sqlstate: Option<InlineDiagnosticText>,
    column_index: Option<u64>,
    column_count: Option<u64>,
    target_rust_type: Option<InlineDiagnosticText>,
    actual_db_type: Option<InlineDiagnosticText>,
    object: Option<InlineDiagnosticText>,
    unavailable: Option<DiagnosticFactUnavailable>,
    target_type_unavailable: Option<DiagnosticFactUnavailable>,
    actual_type_unavailable: Option<DiagnosticFactUnavailable>,
}
impl BoundedDiagnosticCause {
    pub fn new(stage: DiagnosticStage, code: DiagnosticCode) -> Self {
        Self {
            stage,
            code,
            io_kind: None,
            os_code: None,
            database_code: None,
            sqlstate: None,
            column_index: None,
            column_count: None,
            target_rust_type: None,
            actual_db_type: None,
            object: None,
            unavailable: None,
            target_type_unavailable: Some(DiagnosticFactUnavailable::MetadataUnavailable),
            actual_type_unavailable: Some(DiagnosticFactUnavailable::MetadataUnavailable),
        }
    }
    pub fn with_system(mut self, kind: DiagnosticCode, os: Option<i32>) -> Self {
        self.io_kind = Some(kind);
        self.os_code = os;
        self
    }
    pub fn with_database_code(mut self, code: u32) -> Self {
        self.database_code = Some(code);
        self
    }
    pub fn with_sqlstate(mut self, state: &str) -> Self {
        self.sqlstate = (state.len() == 5
            && state
                .bytes()
                .all(|b| b.is_ascii_uppercase() || b.is_ascii_digit()))
        .then(|| InlineDiagnosticText::metadata(state));
        self
    }
    pub fn with_column(mut self, index: Option<u64>, count: Option<u64>) -> Self {
        self.column_index = index;
        self.column_count = count;
        self
    }
    pub fn with_types(
        mut self,
        target: Option<InlineDiagnosticText>,
        actual: Option<InlineDiagnosticText>,
        unavailable: Option<DiagnosticFactUnavailable>,
    ) -> Self {
        self.target_rust_type = target;
        self.actual_db_type = actual;
        self.unavailable = unavailable;
        self.target_type_unavailable = if target.is_some() {
            None
        } else {
            Some(unavailable.unwrap_or(DiagnosticFactUnavailable::MetadataUnavailable))
        };
        self.actual_type_unavailable = if actual.is_some() {
            None
        } else {
            Some(unavailable.unwrap_or(DiagnosticFactUnavailable::MetadataUnavailable))
        };
        self
    }
    pub fn with_object(mut self, object: InlineDiagnosticText) -> Self {
        self.object = Some(object);
        self
    }
}

/// Four fixed causes plus explicit overflow. Capture never invokes Backtrace or
/// an arbitrary source formatter. Retain alongside the original Result, not in it.
#[derive(Serialize)]
pub struct BoundedDiagnostic {
    diagnostic_id: u64,
    primary_diagnostic_id: Option<u64>,
    category: DiagnosticCategory,
    capture_site: CaptureSite,
    origin_file: &'static str,
    origin_line: u32,
    origin_column: u32,
    causes: [Option<BoundedDiagnosticCause>; 4],
    cause_count: usize,
    omitted_causes: u64,
    stack_status: &'static str,
}
impl BoundedDiagnostic {
    #[track_caller]
    pub fn capture(
        category: DiagnosticCategory,
        site: CaptureSite,
        cause: BoundedDiagnosticCause,
    ) -> Self {
        let origin = std::panic::Location::caller();
        let file = origin
            .file()
            .rsplit("/crates/")
            .next()
            .unwrap_or(origin.file());
        let file = if file.starts_with('/') || file.contains('\\') {
            file.rsplit(['/', '\\']).next().unwrap_or("unknown")
        } else {
            file
        };
        Self {
            diagnostic_id: crate::diagnostic::NEXT_ID
                .fetch_add(1, std::sync::atomic::Ordering::Relaxed),
            primary_diagnostic_id: None,
            category,
            capture_site: site,
            origin_file: file,
            origin_line: origin.line(),
            origin_column: origin.column(),
            causes: [Some(cause), None, None, None],
            cause_count: 1,
            omitted_causes: 0,
            stack_status: "unavailable_bounded_capture",
        }
    }
    pub fn wrap(mut self, cause: BoundedDiagnosticCause) -> Self {
        if self.cause_count == self.causes.len() {
            self.omitted_causes = self.omitted_causes.saturating_add(1);
        } else {
            self.causes.copy_within(0..self.cause_count, 1);
            self.causes[0] = Some(cause);
            self.cause_count += 1;
        }
        self
    }
    pub fn during_cleanup_of(mut self, primary: &Self) -> Self {
        self.primary_diagnostic_id = Some(primary.diagnostic_id);
        self
    }
    pub fn id(&self) -> u64 {
        self.diagnostic_id
    }
    pub fn category(&self) -> DiagnosticCategory {
        self.category
    }
    /// Links cleanup to an existing light source reference without retaining its body.
    pub fn during_cleanup_occurrence(mut self, primary: DiagnosticOccurrence) -> Self {
        self.primary_diagnostic_id = Some(primary.source_id());
        self
    }
    pub fn occurrence(&self) -> DiagnosticOccurrence {
        DiagnosticOccurrence {
            diagnostic_id: self.diagnostic_id,
            primary_diagnostic_id: self.primary_diagnostic_id,
        }
    }
}

/// Read-only correlation, not delivery confirmation or execution authority.
///
/// ```compile_fail
/// use saddle_core::DiagnosticOccurrence;
/// let forged = DiagnosticOccurrence { diagnostic_id: 1, primary_diagnostic_id: None };
/// ```
#[derive(Clone, Copy, Serialize)]
pub struct DiagnosticOccurrence {
    diagnostic_id: u64,
    primary_diagnostic_id: Option<u64>,
}
impl DiagnosticOccurrence {
    pub(crate) const fn source_id(&self) -> u64 {
        self.diagnostic_id
    }
    pub(crate) fn from_diagnostic(diagnostic: &crate::Diagnostic) -> Self {
        Self {
            diagnostic_id: diagnostic.id(),
            primary_diagnostic_id: diagnostic.primary_id_for_projection(),
        }
    }
}

#[derive(Clone, Copy, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum OperationOutcome {
    Succeeded,
    Rejected,
    Failed,
    Cancelled,
    TimedOut,
    Panicked,
    Unknown,
}
#[derive(Clone, Copy, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum PhysicalDispositionFact {
    NotUsed,
    Returned,
    Discarded,
    Unknown,
}
#[derive(Clone, Copy, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum BusinessOutcome {
    Success,
    Failure,
    Unknown,
}
#[derive(Clone, Copy, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ResponseDelivery {
    NotStarted,
    Partial,
    LocalWriteComplete,
    Failed,
    Cancelled,
    TimedOut,
    Unknown,
}
#[derive(Clone, Copy, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum CleanupOutcome {
    NotRun,
    Succeeded,
    Failed,
    Unknown,
}

/// Each domain reports only facts it owns; defaults are deliberately unknown.
/// These are observations, not proof of delivery, finalization or execution.
#[derive(Clone, Copy, Serialize)]
pub struct DiagnosticOutcomeAxes {
    pub operation: OperationOutcome,
    pub physical: PhysicalDispositionFact,
    pub business: BusinessOutcome,
    pub business_code: Option<RegisteredDiagnosticCode>,
    pub delivery: ResponseDelivery,
    pub bytes_written: Option<u64>,
    pub cleanup: CleanupOutcome,
}
impl Default for DiagnosticOutcomeAxes {
    fn default() -> Self {
        Self {
            operation: OperationOutcome::Unknown,
            physical: PhysicalDispositionFact::Unknown,
            business: BusinessOutcome::Unknown,
            business_code: None,
            delivery: ResponseDelivery::Unknown,
            bytes_written: None,
            cleanup: CleanupOutcome::Unknown,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn bounded_causes_primary_cleanup_metadata_and_business_code() {
        let cause = BoundedDiagnosticCause::new(
            DiagnosticStage::RequestDb,
            DiagnosticCode::new("db.decode").unwrap(),
        )
        .with_database_code(1064)
        .with_sqlstate("42000")
        .with_types(
            None,
            Some(InlineDiagnosticText::metadata("VARCHAR")),
            Some(DiagnosticFactUnavailable::OpaqueSource),
        );
        let mut primary = BoundedDiagnostic::capture(
            DiagnosticCategory::UnexpectedError,
            CaptureSite::FirstObserved,
            cause,
        );
        let id = primary.id();
        for _ in 0..6 {
            primary = primary.wrap(cause);
        }
        let p = serde_json::to_value(&primary).unwrap();
        assert_eq!(p["diagnostic_id"], id);
        assert_eq!(p["omitted_causes"], 3);
        assert_eq!(p["causes"][0]["sqlstate"]["value"], "42000");
        assert_eq!(p["causes"][0]["target_type_unavailable"], "opaque_source");
        assert!(p["causes"][0]["actual_type_unavailable"].is_null());
        let cleanup = BoundedDiagnostic::capture(
            DiagnosticCategory::UnexpectedError,
            CaptureSite::FirstObserved,
            cause,
        )
        .during_cleanup_of(&primary);
        assert_ne!(cleanup.id(), id);
        assert_eq!(
            serde_json::to_value(cleanup).unwrap()["primary_diagnostic_id"],
            id
        );
        let secret =
            serde_json::to_value(InlineDiagnosticText::metadata("mysql://SECRET@host")).unwrap();
        assert_eq!(secret["redacted"], true);
        assert!(!secret.to_string().contains("SECRET"));
        let long = serde_json::to_value(InlineDiagnosticText::metadata(&"".repeat(100))).unwrap();
        assert_eq!(long["truncated"], true);
        assert!(
            RegisteredDiagnosticCode::from_registered("INVALID_VALUE", &["INVALID_VALUE"])
                .is_some()
        );
        assert!(RegisteredDiagnosticCode::from_registered("FOREIGN", &["INVALID_VALUE"]).is_none());
        assert!(
            RegisteredDiagnosticCode::from_registered("SECRET@host", &["SECRET@host"]).is_none()
        );
    }
}