nidus-integrations 1.0.15

Stable envelope, correlation, lifecycle telemetry, and adapter composition primitives for Nidus integrations.
Documentation
use nidus_integrations::{
    DEFAULT_MAX_ENVELOPE_BYTES, EnvelopeMetadata, IntegrationError, MessageEnvelope,
};
use serde::{Deserialize, Serialize};

#[derive(Clone, Deserialize, Eq, PartialEq, Serialize)]
struct UserCreated {
    email: String,
}

#[test]
fn envelope_round_trips_without_exposing_payload_or_header_values_in_debug() {
    let metadata = EnvelopeMetadata::new()
        .correlation_id("request-42")
        .unwrap()
        .traceparent("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01")
        .unwrap()
        .header("authorization", "Bearer secret")
        .unwrap();
    let envelope = MessageEnvelope::new(
        "user.created",
        UserCreated {
            email: "ada@example.com".to_owned(),
        },
    )
    .unwrap()
    .with_metadata(metadata);

    let debug = format!("{envelope:?}");
    assert!(!debug.contains("ada@example.com"));
    assert!(!debug.contains("Bearer secret"));
    assert!(debug.contains("authorization"));

    let encoded = envelope.to_json().unwrap();
    let decoded = MessageEnvelope::<UserCreated>::from_json(&encoded).unwrap();
    assert_eq!(decoded, envelope);
}

#[test]
fn envelope_rejects_invalid_trace_context_headers_and_oversized_payloads() {
    assert!(matches!(
        EnvelopeMetadata::new().traceparent("not-a-traceparent"),
        Err(IntegrationError::InvalidTraceparent)
    ));
    assert!(matches!(
        EnvelopeMetadata::new().header("bad header", "value"),
        Err(IntegrationError::InvalidHeaderName)
    ));

    let envelope = MessageEnvelope::new("large", vec![0_u8; DEFAULT_MAX_ENVELOPE_BYTES]).unwrap();
    let actual = serde_json::to_vec(&envelope).unwrap().len();
    assert!(matches!(
        envelope.to_json(),
        Err(IntegrationError::EnvelopeTooLarge {
            actual: reported,
            maximum: DEFAULT_MAX_ENVELOPE_BYTES,
        }) if reported == actual
    ));
}

#[test]
fn traceparent_validation_honors_versioning_and_security_boundaries() {
    let current = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01";
    let future = "01-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01";
    let future_extended = format!("{future}-future-data");

    for traceparent in [current, future, future_extended.as_str()] {
        assert!(
            EnvelopeMetadata::new().traceparent(traceparent).is_ok(),
            "{traceparent}"
        );
    }

    for traceparent in [
        "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01-extra",
        "ff-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
        "00-4BF92F3577B34DA6A3CE929D0E0E4736-00f067aa0ba902b7-01",
        "00-00000000000000000000000000000000-00f067aa0ba902b7-01",
        "00-4bf92f3577b34da6a3ce929d0e0e4736-0000000000000000-01",
        "01-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01\n",
    ] {
        assert!(
            matches!(
                EnvelopeMetadata::new().traceparent(traceparent),
                Err(IntegrationError::InvalidTraceparent)
            ),
            "{traceparent:?}"
        );
    }

    let oversized = format!("{future}-{}", "x".repeat(4_096));
    assert!(matches!(
        EnvelopeMetadata::new().traceparent(oversized),
        Err(IntegrationError::InvalidTraceparent)
    ));
}

#[test]
fn explicit_envelope_limit_preserves_exact_boundary_behavior() {
    let envelope = MessageEnvelope::new(
        "small",
        UserCreated {
            email: "ada@example.com".to_owned(),
        },
    )
    .unwrap();
    let expected = serde_json::to_vec(&envelope).unwrap();

    assert_eq!(
        envelope.to_json_with_limit(expected.len()).unwrap(),
        expected
    );
    assert!(matches!(
        envelope.to_json_with_limit(expected.len() - 1),
        Err(IntegrationError::EnvelopeTooLarge { actual, maximum })
            if actual == expected.len() && maximum == expected.len() - 1
    ));
}

#[test]
fn explicit_envelope_parts_are_validated_after_deserialization() {
    let invalid = br#"{
        "id":"",
        "name":"event",
        "schema_version":1,
        "occurred_at_ms":0,
        "metadata":{"correlation_id":null,"causation_id":null,"traceparent":null,"headers":{}},
        "payload":{}
    }"#;
    assert!(matches!(
        MessageEnvelope::<serde_json::Value>::from_json(invalid),
        Err(IntegrationError::InvalidCorrelationId)
    ));
}