Skip to main content

obs_proto/
lib.rs

1//! Canonical obs/v1 protobuf schemas.
2//!
3//! This crate ships the wire-format types every other crate consumes:
4//!
5//! - `obs::v1::ObsEnvelope`, `ObsBatch` — the envelope & batch shape (spec 10).
6//! - `obs::v1::*` enums — vocabulary mirrors of the [`obs_types`] enums.
7//! - `obs::v1::*` events — user-facing built-ins (spec 61 § 2.2).
8//! - `obs::runtime::v1::*` events — SDK self-events (spec 11 § 10).
9//! - [`BUILTIN_FDS`] — the `FileDescriptorSet` bytes for everything in this crate, embedded at
10//!   compile time.
11//!
12//! The generated buffa code lives under `$OUT_DIR` (idiomatic Cargo) and is
13//! wired in via `include!(concat!(env!("OUT_DIR"), "/mod.rs"))` below.
14
15#![forbid(unsafe_code)]
16#![warn(rust_2024_compatibility)]
17#![allow(missing_docs, missing_debug_implementations)]
18#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))]
19
20#[allow(
21    clippy::all,
22    clippy::pedantic,
23    clippy::restriction,
24    clippy::indexing_slicing,
25    clippy::expect_used,
26    clippy::unwrap_used,
27    clippy::panic,
28    missing_docs
29)]
30mod pb {
31    include!(concat!(env!("OUT_DIR"), "/mod.rs"));
32}
33
34pub use pb::*;
35
36mod ext;
37
38/// Error type returned by the [`FromStr`](std::str::FromStr) impls on
39/// every enum in this crate. Mirrors the shape obs-types used to
40/// provide; re-exported here so consumers have a single import path.
41pub use ext::UnknownVariant;
42
43/// Bytes of the `FileDescriptorSet` covering every `.proto` file in this
44/// crate, captured at build time.
45///
46/// Used by `obs-core::registry` to discover the built-in schemas without
47/// depending on linkme registrations from this crate (which would
48/// require user binaries to `use obs_proto as _;`).
49pub static BUILTIN_FDS: &[u8] = include_bytes!(env!("OBS_PROTO_FDS"));
50
51/// Wire-format version of the `ObsEnvelope` / `ObsBatch` shape.
52///
53/// Bumped to `2` alongside the move from JSON-payload (Phase-1
54/// `#[derive(Event)]`) to buffa-encoded payload bytes for both
55/// authoring paths (decision D6-1 in spec 93).
56///
57/// Bumped to `3` in Phase 7 alongside spec 94 § P0-A:
58/// `ObsSpanCompleted` / `ObsSpanEntered` gained typed
59/// `trace_id`/`span_id`/`parent_span_id` fields and the bridge
60/// switched from raw-byte payloads to buffa-encoded typed payloads
61/// (spec 94 § P1-B). Both producers and consumers therefore must
62/// agree on the new wire shape. Any further change to the field
63/// layout of `obs/v1/envelope.proto` (adding, removing, renumbering,
64/// or repurposing fields) requires bumping this constant **and** the
65/// corresponding `format_ver` field on every encoder/decoder. The CI
66/// guard at `.github/workflows/format-ver-guard.yml` fails any commit
67/// that touches `envelope.proto` without also bumping this value.
68///
69/// Spec 90 § 3.3 / spec 93 § 1 P0-2 + decision D6-1 / spec 94 D7-1.
70pub const ENVELOPE_FORMAT_VER: u32 = 3;
71
72/// Re-export buffa traits user code rarely touches but generated code
73/// needs in scope.
74pub mod __private {
75    pub use buffa::{EnumValue, Enumeration, Message, MessageField, UnknownFields};
76}
77
78#[cfg(test)]
79mod tests {
80    use buffa::Message as _;
81    use buffa_descriptor::generated::descriptor::FileDescriptorSet;
82
83    use super::*;
84
85    #[test]
86    fn test_should_decode_builtin_fds() {
87        let fds = FileDescriptorSet::decode_from_slice(BUILTIN_FDS).unwrap();
88        let names: Vec<_> = fds.file.iter().filter_map(|f| f.name.as_deref()).collect();
89        assert!(names.iter().any(|n| n.ends_with("envelope.proto")));
90        assert!(names.iter().any(|n| n.ends_with("builtin.proto")));
91        assert!(names.iter().any(|n| n.ends_with("self_events.proto")));
92    }
93
94    #[test]
95    fn test_envelope_format_ver_locked_at_three() {
96        // Spec 90 § 3.3 / spec 94 D7-1: bumped from 2 to 3 alongside
97        // P0-A (typed trace fields on ObsSpanCompleted / ObsSpanEntered)
98        // and P1-B (bridge typed-payload encoding). Both producers and
99        // consumers must agree on the new wire shape. The CI guard at
100        // `.github/workflows/format-ver-guard.yml` forces a bump on any
101        // `envelope.proto` edit; this assertion is the second line of
102        // defence so a forced merge cannot quietly desync the const
103        // from the proto.
104        assert_eq!(ENVELOPE_FORMAT_VER, 3);
105    }
106
107    #[test]
108    fn test_should_round_trip_envelope() {
109        let env = obs::v1::ObsEnvelope {
110            full_name: "obs.v1.ObsHelloEmitted".to_string(),
111            schema_hash: 0x1234_5678_9ABC_DEF0,
112            ts_ns: 1_700_000_000_000_000_000,
113            service: "test".to_string(),
114            ..Default::default()
115        };
116        let mut buf = Vec::new();
117        env.encode(&mut buf);
118        let decoded = obs::v1::ObsEnvelope::decode_from_slice(&buf).unwrap();
119        assert_eq!(decoded.full_name, env.full_name);
120        assert_eq!(decoded.schema_hash, env.schema_hash);
121        assert_eq!(decoded.service, env.service);
122    }
123}