detritus-protocol 0.1.0

Wire protocol types for Detritus telemetry and crash ingestion
Documentation
//! Protocol roundtrip smoke tests.

use chrono::Utc;
use detritus_protocol::{
    AttachmentManifest, BuildInfo, CrashAttachment, CrashEnvelope, CrashKind, CrashMetadata,
    PROTOCOL_VERSION, SourceId,
    otlp::{
        common::{AnyValue, KeyValue, any_value},
        logs::LogRecord,
    },
};
use prost::Message;
use serde_json::json;
use uuid::Uuid;

#[test]
fn roundtrip_log_record() {
    let record = LogRecord {
        time_unix_nano: 42,
        observed_time_unix_nano: 43,
        severity_number: 9,
        severity_text: "INFO".to_owned(),
        body: Some(AnyValue {
            value: Some(any_value::Value::StringValue("hello".to_owned())),
        }),
        attributes: vec![KeyValue {
            key: "message".to_owned(),
            value: Some(AnyValue {
                value: Some(any_value::Value::StringValue("one log".to_owned())),
            }),
        }],
        dropped_attributes_count: 0,
        flags: 0,
        trace_id: vec![1; 16],
        span_id: vec![2; 8],
        event_name: String::new(),
    };

    let mut encoded = Vec::new();
    record.encode(&mut encoded).expect("record encodes");
    let decoded = LogRecord::decode(encoded.as_slice()).expect("record decodes");
    assert_eq!(record, decoded);
}

#[test]
fn roundtrip_crash_metadata_json() {
    for kind in [
        CrashKind::Minidump,
        CrashKind::PanicTarball,
        CrashKind::RustcIce,
    ] {
        let metadata = crash_metadata(kind);
        let encoded = serde_json::to_vec(&metadata).expect("metadata encodes");
        let decoded: CrashMetadata = serde_json::from_slice(&encoded).expect("metadata decodes");
        assert_eq!(metadata, decoded);
        assert_eq!(
            "detritus/linux/0.1.0/11111111-1111-1111-1111-111111111111",
            decoded.source.canonical()
        );
    }
}

#[tokio::test]
async fn roundtrip_multipart() {
    let mut metadata = crash_metadata(CrashKind::Minidump);
    metadata.attachments = vec![
        AttachmentManifest {
            key: "events".to_owned(),
            filename: Some("recent_events.json".to_owned()),
            content_type: "application/json".to_owned(),
            len: 11,
        },
        AttachmentManifest {
            key: "state".to_owned(),
            filename: Some("state_snapshot.txt".to_owned()),
            content_type: "text/plain".to_owned(),
            len: 5,
        },
    ];
    let envelope = CrashEnvelope {
        metadata,
        dump: b"minidump-bytes".to_vec(),
        attachments: vec![
            CrashAttachment {
                key: "events".to_owned(),
                content_type: "application/json".to_owned(),
                bytes: b"{\"turn\": 1}".to_vec(),
            },
            CrashAttachment {
                key: "state".to_owned(),
                content_type: "text/plain".to_owned(),
                bytes: b"board".to_vec(),
            },
        ],
    };

    let mut encoded = Vec::new();
    envelope
        .write_to(&mut encoded)
        .await
        .expect("multipart writes");
    let mut cursor = std::io::Cursor::new(encoded);
    let decoded = CrashEnvelope::read_from(&mut cursor)
        .await
        .expect("multipart reads");

    assert_eq!(envelope.metadata, decoded.metadata);
    assert_eq!(envelope.dump, decoded.dump);
    assert_eq!(envelope.attachments, decoded.attachments);
}

fn crash_metadata(kind: CrashKind) -> CrashMetadata {
    let source = SourceId {
        project: "detritus".to_owned(),
        platform: "linux".to_owned(),
        version: "0.1.0".to_owned(),
        install_id: Uuid::parse_str("11111111-1111-1111-1111-111111111111").expect("valid uuid"),
    };
    CrashMetadata {
        schema_version: PROTOCOL_VERSION,
        source,
        timestamp: Utc::now(),
        kind,
        build: BuildInfo {
            git_sha: "abcdef0".to_owned(),
            profile: "release".to_owned(),
            target_triple: "x86_64-unknown-linux-gnu".to_owned(),
        },
        panic_text: Some("panic text".to_owned()),
        context: json!({ "tick": 10, "rng_seed": 123 }),
        attachments: Vec::new(),
    }
}