1use crate::codec::WireTypeId;
7use crate::encoder::FxHashMap;
8use crate::types::FieldType;
9use std::borrow::Cow;
10
11#[non_exhaustive]
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct FieldAnnotation {
19 field_index: u16,
20 key: Cow<'static, str>,
21 value: Cow<'static, str>,
22}
23
24impl FieldAnnotation {
25 pub fn new(field_index: u16, key: impl Into<String>, value: impl Into<String>) -> Self {
27 Self {
28 field_index,
29 key: Cow::Owned(key.into()),
30 value: Cow::Owned(value.into()),
31 }
32 }
33
34 pub fn field_index(&self) -> u16 {
37 self.field_index
38 }
39
40 pub fn key(&self) -> &str {
42 &self.key
43 }
44
45 pub fn value(&self) -> &str {
47 &self.value
48 }
49}
50
51#[non_exhaustive]
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct FieldDef {
55 pub(crate) name: String,
56 pub(crate) field_type: FieldType,
57}
58
59impl FieldDef {
60 pub fn new(name: impl Into<String>, field_type: FieldType) -> Self {
69 Self {
70 name: name.into(),
71 field_type,
72 }
73 }
74
75 pub fn name(&self) -> &str {
77 &self.name
78 }
79
80 pub fn field_type(&self) -> FieldType {
82 self.field_type
83 }
84}
85
86#[non_exhaustive]
89#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct SchemaEntry {
91 pub(crate) name: String,
92 pub(crate) has_timestamp: bool,
93 pub(crate) fields: Vec<FieldDef>,
94 pub(crate) annotations: Vec<FieldAnnotation>,
95}
96
97impl SchemaEntry {
98 pub fn new(
100 name: impl Into<String>,
101 has_timestamp: bool,
102 fields: impl IntoIterator<Item = FieldDef>,
103 ) -> Self {
104 Self {
105 name: name.into(),
106 has_timestamp,
107 fields: fields.into_iter().collect(),
108 annotations: Vec::new(),
109 }
110 }
111
112 pub fn with_annotations(
114 name: impl Into<String>,
115 has_timestamp: bool,
116 fields: impl IntoIterator<Item = FieldDef>,
117 annotations: impl IntoIterator<Item = FieldAnnotation>,
118 ) -> Self {
119 Self {
120 name: name.into(),
121 has_timestamp,
122 fields: fields.into_iter().collect(),
123 annotations: annotations.into_iter().collect(),
124 }
125 }
126
127 pub fn name(&self) -> &str {
129 &self.name
130 }
131
132 pub fn has_timestamp(&self) -> bool {
134 self.has_timestamp
135 }
136
137 pub fn fields(&self) -> &[FieldDef] {
139 &self.fields
140 }
141
142 pub fn annotations(&self) -> &[FieldAnnotation] {
144 &self.annotations
145 }
146}
147
148#[derive(Debug, Clone)]
149pub struct SchemaRegistry {
150 pub(crate) schemas: FxHashMap<WireTypeId, SchemaEntry>,
151 pub(crate) next_id: u16,
152}
153
154impl Default for SchemaRegistry {
155 fn default() -> Self {
156 Self {
157 schemas: FxHashMap::default(),
158 next_id: crate::STATIC_WIRE_ID_LIMIT,
161 }
162 }
163}
164
165impl SchemaRegistry {
166 pub fn new() -> Self {
167 Self::default()
168 }
169
170 pub fn clear(&mut self) {
172 self.next_id = crate::STATIC_WIRE_ID_LIMIT;
173 self.schemas.clear();
174 }
175
176 pub fn register(&mut self, type_id: WireTypeId, entry: SchemaEntry) -> Result<(), String> {
178 if let Some(existing) = self.schemas.get(&type_id) {
179 if *existing == entry {
180 return Ok(());
181 }
182 return Err(format!(
183 "type_id {:?} already registered with different schema",
184 type_id
185 ));
186 }
187 self.schemas.insert(type_id, entry);
188 Ok(())
189 }
190
191 pub fn get(&self, type_id: WireTypeId) -> Option<&SchemaEntry> {
192 self.schemas.get(&type_id)
193 }
194
195 pub fn entries(&self) -> impl Iterator<Item = (WireTypeId, &SchemaEntry)> {
196 self.schemas.iter().map(|(&id, entry)| (id, entry))
197 }
198
199 pub fn next_type_id(&mut self) -> WireTypeId {
201 let id = WireTypeId(self.next_id);
202 self.next_id += 1;
203 id
204 }
205
206 pub fn sync_next_id(&mut self) {
211 self.next_id = crate::STATIC_WIRE_ID_LIMIT;
212 for &id in self.schemas.keys() {
213 if id.0 >= self.next_id {
214 self.next_id = id.0 + 1;
215 }
216 }
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223
224 #[test]
225 fn register_and_lookup() {
226 let mut reg = SchemaRegistry::new();
227 let id = reg.next_type_id();
228 let entry = SchemaEntry {
229 name: "PollStart".into(),
230 has_timestamp: true,
231 fields: vec![
232 FieldDef {
233 name: "timestamp_ns".into(),
234 field_type: FieldType::Varint,
235 },
236 FieldDef {
237 name: "worker".into(),
238 field_type: FieldType::Varint,
239 },
240 ],
241 annotations: Vec::new(),
242 };
243 reg.register(id, entry.clone()).unwrap();
244 assert_eq!(reg.get(id), Some(&entry));
245 assert_eq!(reg.get(WireTypeId(99)), None);
246 }
247
248 #[test]
249 fn duplicate_type_id_same_schema_ok() {
250 let mut reg = SchemaRegistry::new();
251 let id = reg.next_type_id();
252 let entry = SchemaEntry {
253 name: "A".into(),
254 has_timestamp: true,
255 fields: vec![],
256 annotations: Vec::new(),
257 };
258 reg.register(id, entry.clone()).unwrap();
259 reg.register(id, entry).unwrap();
260 }
261
262 #[test]
263 fn duplicate_type_id_different_schema_rejected() {
264 let mut reg = SchemaRegistry::new();
265 let id = reg.next_type_id();
266 reg.register(
267 id,
268 SchemaEntry {
269 name: "A".into(),
270 has_timestamp: true,
271 fields: vec![],
272 annotations: Vec::new(),
273 },
274 )
275 .unwrap();
276 assert!(
277 reg.register(
278 id,
279 SchemaEntry {
280 name: "B".into(),
281 has_timestamp: true,
282 fields: vec![],
283 annotations: Vec::new(),
284 }
285 )
286 .is_err()
287 );
288 }
289
290 #[test]
291 fn multiple_schemas() {
292 let mut reg = SchemaRegistry::new();
293 let id1 = reg.next_type_id();
294 reg.register(
295 id1,
296 SchemaEntry {
297 name: "A".into(),
298 has_timestamp: true,
299 fields: vec![],
300 annotations: Vec::new(),
301 },
302 )
303 .unwrap();
304 let id2 = reg.next_type_id();
305 reg.register(
306 id2,
307 SchemaEntry {
308 name: "B".into(),
309 has_timestamp: true,
310 fields: vec![],
311 annotations: Vec::new(),
312 },
313 )
314 .unwrap();
315 assert_eq!(reg.entries().count(), 2);
316 }
317
318 #[test]
319 fn next_type_id_auto_increments() {
320 let mut reg = SchemaRegistry::new();
321 let id1 = reg.next_type_id();
322 let id2 = reg.next_type_id();
323 assert_ne!(id1, id2);
324 assert_eq!(id1, WireTypeId(crate::STATIC_WIRE_ID_LIMIT));
325 assert_eq!(id2, WireTypeId(crate::STATIC_WIRE_ID_LIMIT + 1));
326 }
327}