1#![forbid(unsafe_code)]
17#![warn(rust_2024_compatibility, missing_debug_implementations)]
18#![allow(
22 missing_docs,
23 clippy::disallowed_types,
24 clippy::disallowed_methods,
25 clippy::expect_used,
26 clippy::indexing_slicing
27)]
28#![cfg_attr(test, allow(clippy::unwrap_used))]
29
30mod codegen;
31mod config;
32pub mod lints;
33mod options;
34
35pub use config::{
36 Config, DescriptorSource, EMBEDDED_ENUMS_PROTO, EMBEDDED_OPTIONS_PROTO,
37 materialise_embedded_options,
38};
39pub use lints::{
40 LintError, LintField, LintInput, LintProtoType, emit_cross_event_lints, emit_lints,
41};
42pub use options::{CodegenError, EventOptions, FieldOptions, MetricSpec};
43
44pub mod reflect {
49 use buffa_reflect::{DescriptorPool, Kind};
50 use obs_proto::obs::v1::{Cardinality, Classification, FieldKind, Severity, Tier};
51
52 use crate::{
53 lints::LintProtoType,
54 options::{
55 CodegenError, EventOptions, FieldOptions, read_event_options, read_field_options,
56 },
57 };
58
59 #[derive(Debug)]
61 #[non_exhaustive]
62 pub struct AnnotatedEvent {
63 pub full_name: String,
65 pub event: EventOptions,
67 pub fields: Vec<AnnotatedField>,
69 }
70
71 #[derive(Debug)]
73 #[non_exhaustive]
74 pub struct AnnotatedField {
75 pub name: String,
77 pub number: u32,
79 pub options: FieldOptions,
81 pub proto_type: LintProtoType,
84 }
85
86 impl AnnotatedField {
87 #[must_use]
89 pub fn kind(&self) -> FieldKind {
90 self.options.kind.unwrap_or(FieldKind::Attribute)
91 }
92 #[must_use]
94 pub fn cardinality(&self) -> Cardinality {
95 self.options.cardinality.unwrap_or(Cardinality::Unspecified)
96 }
97 #[must_use]
99 pub fn classification(&self) -> Classification {
100 self.options
101 .classification
102 .unwrap_or(Classification::Internal)
103 }
104 }
105
106 impl AnnotatedEvent {
107 #[must_use]
109 pub fn tier(&self) -> Tier {
110 self.event.tier.unwrap_or(Tier::Log)
111 }
112 #[must_use]
114 pub fn default_sev(&self) -> Severity {
115 self.event.default_sev.unwrap_or(Severity::Info)
116 }
117 #[must_use]
123 pub fn schema_hash(&self) -> u64 {
124 let mut s = String::new();
125 s.push_str(&self.full_name);
126 s.push('|');
127 s.push_str(self.tier().as_str());
128 s.push('|');
129 s.push_str(self.default_sev().as_str());
130 s.push('|');
131 for f in &self.fields {
132 s.push_str(&f.name);
133 s.push(':');
134 s.push_str(f.kind().as_str());
135 s.push(':');
136 s.push_str(f.cardinality().as_str());
137 s.push(':');
138 s.push_str(f.classification().as_str());
139 s.push(',');
140 }
141 let h = blake3::hash(s.as_bytes());
142 let bytes = h.as_bytes();
143 let arr = <[u8; 8]>::try_from(&bytes[..8]).expect("blake3 always produces 32 bytes");
144 u64::from_le_bytes(arr)
145 }
146 }
147
148 pub fn scan_pool(pool: &DescriptorPool) -> Result<Vec<AnnotatedEvent>, CodegenError> {
157 let mut events: Vec<AnnotatedEvent> = Vec::new();
158 for msg in pool.all_messages() {
159 let dp = msg.descriptor_proto();
160 if !dp.options.is_set() {
161 continue;
162 }
163 let mut bytes = Vec::new();
164 dp.options.__buffa_unknown_fields.write_to(&mut bytes);
165 let Some(event_opts) = read_event_options(&bytes, msg.full_name())? else {
166 continue;
167 };
168 let mut decl = AnnotatedEvent {
169 full_name: msg.full_name().to_string(),
170 event: event_opts,
171 fields: Vec::new(),
172 };
173 for f in msg.fields() {
174 let fdp = f.descriptor_proto();
175 let mut fbytes = Vec::new();
176 if fdp.options.is_set() {
177 fdp.options.__buffa_unknown_fields.write_to(&mut fbytes);
178 }
179 let opts =
180 read_field_options(&fbytes, &format!("{}/{}", msg.full_name(), f.name()))?
181 .unwrap_or_default();
182 decl.fields.push(AnnotatedField {
183 name: f.name().to_string(),
184 number: f.number(),
185 options: opts,
186 proto_type: map_kind_to_lint_type(&f.kind()),
187 });
188 }
189 events.push(decl);
190 }
191 events.sort_by(|a, b| a.full_name.cmp(&b.full_name));
192 Ok(events)
193 }
194
195 fn map_kind_to_lint_type(kind: &Kind) -> LintProtoType {
196 match kind {
197 Kind::String => LintProtoType::String,
198 Kind::Bytes => LintProtoType::Bytes,
199 Kind::Bool => LintProtoType::Bool,
200 Kind::Double | Kind::Float => LintProtoType::Float,
201 Kind::Int32
202 | Kind::Int64
203 | Kind::Sint32
204 | Kind::Sint64
205 | Kind::Sfixed32
206 | Kind::Sfixed64 => LintProtoType::SignedInteger,
207 Kind::Uint32 | Kind::Uint64 | Kind::Fixed32 | Kind::Fixed64 => {
208 LintProtoType::UnsignedInteger
209 }
210 Kind::Enum(e) => LintProtoType::Other(e.full_name().to_string()),
211 Kind::Message(m) => LintProtoType::Other(m.full_name().to_string()),
212 other => LintProtoType::Other(format!("{other:?}")),
213 }
214 }
215}