debugbundle 1.0.0-alpha.1

Safe, bounded runtime error reporting for Rust, Axum and Actix Web
Documentation
mod helpers;
use debugbundle::{privacy::Privacy, wire, Client, Context, ExceptionSnapshot, TraceOrigin};
use helpers::{configured, emit_until, Recorder};
use serde_json::{json, Value};
use std::sync::Arc;

fn near_budget_snapshot() -> ExceptionSnapshot {
    ExceptionSnapshot::new("Primary", "primary failure")
        .with_stack(&"a".repeat(16384), TraceOrigin::RuntimeReported)
        .with_cause(
            &ExceptionSnapshot::new("Cause", "cause")
                .with_stack(&"b".repeat(15800), TraceOrigin::RuntimeReported),
        )
}
fn reference() -> Value {
    json!({"version":1,"failure_ref":"11111111-1111-4111-8111-111111111111","scope":{"kind":"operation","operation_id":"22222222-2222-4222-8222-222222222222"}})
}
fn add_over_budget_evidence(event: &mut Value) {
    event["payload"]["exception_evidence"] = near_budget_snapshot().evidence().clone();
    assert!(wire::valid_event(event), "graph alone must be admissible");
    event["context"]["evidence_reference"] = reference();
    let additions = json!({"exception_evidence":event["payload"]["exception_evidence"],"evidence_reference":reference()});
    assert!(wire::canonical_size(&additions).unwrap() > 32768);
    assert!(
        !wire::valid_event(event),
        "combined budget must still apply"
    );
}

#[test]
fn combined_evidence_budget_reduces_optional_data_before_discarding_the_primary() {
    let corpus: Value =
        serde_json::from_str(include_str!("fixtures/evidence-wire-cases.json")).unwrap();
    let mut event = corpus["events"][0]["event"].clone();
    event["context"] = json!({});
    add_over_budget_evidence(&mut event);
    let protected = wire::protect_event(&Privacy::new(&[]).unwrap(), &event)
        .expect("optional evidence budget must not discard an otherwise valid primary");
    assert!(wire::valid_event(&protected));
    assert_eq!(protected["event_id"], event["event_id"]);
    assert_eq!(protected["payload"]["message"], event["payload"]["message"]);
    assert!(protected["payload"].get("exception_evidence").is_none());
    assert!(protected["context"].get("evidence_reference").is_none());
    assert_eq!(
        protected["context"]["evidence_limits"]["exception_evidence"],
        "budget_exceeded"
    );
}

#[test]
fn combined_evidence_budget_keeps_owner_hook_replacement_instead_of_restoring_original() {
    let recorder = Arc::new(Recorder::default());
    let mut config = configured(recorder.clone());
    config.before_send = Some(Arc::new(|mut event| {
        event["payload"]["message"] = json!("owner-approved replacement");
        add_over_budget_evidence(&mut event);
        Some(event)
    }));
    let client = Client::new(config);
    emit_until(
        &client,
        || {
            client.capture_exception(
                &ExceptionSnapshot::new("Primary", "original must stay removed"),
                &Context::default(),
            )
        },
        || !recorder.events_without_pressure().is_empty(),
    );
    let events = recorder.events_without_pressure();
    assert_eq!(events.len(), 1);
    assert_eq!(
        events[0]["payload"]["message"],
        "owner-approved replacement"
    );
    assert!(events[0]["payload"].get("exception_evidence").is_none());
    assert!(events[0]["context"].get("evidence_reference").is_none());
    assert!(wire::valid_event(&events[0]));
}