Skip to main content

obs_core/envelope/
projection.rs

1//! `EventSchema` — the trait codegen targets when emitting per-event
2//! impls. Generic and `Sized`, so it carries associated `const`s and
3//! `encode_payload` / `project` methods that do not require object
4//! safety. The object-safe complement is
5//! [`crate::registry::EventSchemaErased`].
6
7use bytes::BytesMut;
8use obs_proto::obs::v1::{Cardinality, Classification, FieldKind, ObsEnvelope, Severity, Tier};
9
10/// Compile-time field metadata emitted by codegen alongside each
11/// `EventSchema` impl. Mirrors the proto-side `obs.v1.FieldMeta`.
12#[derive(Debug, Clone, Copy)]
13pub struct FieldMeta {
14    /// Proto field name (`route`, `latency_ms`, …).
15    pub name: &'static str,
16    /// Proto field number.
17    pub number: u32,
18    /// Codegen-classified role.
19    pub role: FieldRole,
20    /// `LABEL` cardinality cap; `Unspecified` for non-labels.
21    pub cardinality: Cardinality,
22    /// Classification (drives redaction / SECRET strip).
23    pub classification: Classification,
24}
25
26impl FieldMeta {
27    /// Construct a [`FieldMeta`]. Used by codegen and tests.
28    #[must_use]
29    pub const fn new(
30        name: &'static str,
31        number: u32,
32        role: FieldRole,
33        cardinality: Cardinality,
34        classification: Classification,
35    ) -> Self {
36        Self {
37            name,
38            number,
39            role,
40            cardinality,
41            classification,
42        }
43    }
44}
45
46/// Codegen-derived classification of a field. Decoupled from the
47/// `FieldKind` enum so codegen can stamp this from the descriptor
48/// without re-parsing.
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50#[non_exhaustive]
51pub enum FieldRole {
52    /// `kind: LABEL`.
53    Label,
54    /// `kind: ATTRIBUTE`.
55    Attribute,
56    /// `kind: MEASUREMENT`.
57    Measurement,
58    /// `kind: TRACE_ID`.
59    TraceId,
60    /// `kind: SPAN_ID`.
61    SpanId,
62    /// `kind: PARENT_SPAN_ID`.
63    ParentSpanId,
64    /// `kind: TIMESTAMP_NS`.
65    TimestampNs,
66    /// `kind: DURATION_NS`.
67    DurationNs,
68    /// `kind: FORENSIC`.
69    Forensic,
70}
71
72impl From<FieldKind> for FieldRole {
73    fn from(k: FieldKind) -> Self {
74        match k {
75            FieldKind::Label => Self::Label,
76            FieldKind::Attribute => Self::Attribute,
77            FieldKind::Measurement => Self::Measurement,
78            FieldKind::TraceId => Self::TraceId,
79            FieldKind::SpanId => Self::SpanId,
80            FieldKind::ParentSpanId => Self::ParentSpanId,
81            FieldKind::TimestampNs => Self::TimestampNs,
82            FieldKind::DurationNs => Self::DurationNs,
83            FieldKind::Forensic => Self::Forensic,
84            // FieldKind is #[non_exhaustive]; defensively map any
85            // unrecognised future variant + Unspecified to Attribute.
86            _ => Self::Attribute,
87        }
88    }
89}
90
91/// Trait implemented by every `Obs*` event type.
92///
93/// Codegen emits one impl per `.proto` message or `#[derive(Event)]`
94/// struct. Sinks consume the object-safe sibling
95/// [`crate::registry::EventSchemaErased`]; this trait is for the
96/// monomorphised emit path.
97pub trait EventSchema: Send + Sync + Sized + 'static {
98    /// Fully qualified event name (`myapp.v1.ObsXxx`).
99    const FULL_NAME: &'static str;
100    /// Tier the schema declares.
101    const TIER: Tier;
102    /// Default severity the schema declares.
103    const DEFAULT_SEV: Severity;
104    /// Per-field metadata table.
105    const FIELDS: &'static [FieldMeta];
106    /// First 8 bytes of BLAKE3 over the canonical descriptor; baked at
107    /// build time. See spec 10 § 6 + spec 12 § 3.5.
108    const SCHEMA_HASH: u64;
109
110    /// Encode this event's payload using buffa's encoder into a reused
111    /// buffer. The codegen impl forwards to `buffa::Message::encode`.
112    fn encode_payload(&self, buf: &mut BytesMut);
113
114    /// Project labels and lift trace/span ids onto the envelope.
115    /// Generated; never hand-written. See spec 11 pipeline § 4.1.
116    fn project(&self, env: &mut ObsEnvelope);
117
118    /// For `MEASUREMENT`-annotated fields, emit metric data points.
119    /// Phase-1 default impl is a no-op so MEASUREMENT-bearing schemas
120    /// authored in Phase 1 do not error in metric sinks; Phase 2
121    /// codegen overrides this. Spec 12 § 3.2.
122    fn project_metrics(&self, sink: &mut dyn crate::metric::MetricEmitter) {
123        let _ = sink;
124    }
125
126    /// When this event participates in a Started/Completed pair, the
127    /// schema's full name of its sibling event. The OTLP trace sink
128    /// matches paired events on this constant rather than suffix
129    /// sniffing. Schemas that are not paired return `None`.
130    /// Spec 20 § 2.5 B / spec 93 P1-7.
131    const SPANS_PAIRED_WITH: Option<&'static str> = None;
132}