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