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}