1use std::{
18 collections::BTreeMap,
19 io::Write,
20 ops::{Deref, RangeInclusive},
21 sync::LazyLock,
22};
23
24use crate::{AsJsonValue, AsKafkaRecord, Error, Generator, Result, Validator};
25
26use bytes::{BufMut, Bytes, BytesMut};
27use fake::Fake;
28
29use protobuf::{
30 CodedInputStream, MessageDyn, UnknownValueRef,
31 descriptor::{self, FieldDescriptorProto},
32 reflect::{
33 EnumDescriptor, FileDescriptor, MessageDescriptor, ReflectValueBox, ReflectValueRef,
34 RuntimeFieldType, RuntimeType,
35 },
36 well_known_types,
37};
38use protobuf_json_mapping::{parse_dyn_from_str, print_to_string};
39use rand::prelude::*;
40use rhai::{Engine, packages::Package};
41use rhai_rand::RandomPackage;
42use serde_json::{Map, Value};
43use tansu_sans_io::{ErrorCode, record::inflated::Batch};
44use tempfile::{NamedTempFile, tempdir};
45use tracing::{debug, error};
46
47#[cfg(any(feature = "parquet", feature = "iceberg", feature = "delta"))]
48mod arrow;
49
50#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
51pub enum MessageKind {
52 Key,
53 Meta,
54 Value,
55}
56
57impl AsRef<str> for MessageKind {
58 fn as_ref(&self) -> &str {
59 match self {
60 MessageKind::Key => "Key",
61 MessageKind::Meta => "Meta",
62 MessageKind::Value => "Value",
63 }
64 }
65}
66
67#[derive(Clone, Debug, Eq, PartialEq)]
68pub struct Schema {
69 file_descriptors: Vec<FileDescriptor>,
70}
71
72impl Schema {
73 fn message_by_package_relative_name(
74 &self,
75 message_kind: MessageKind,
76 ) -> Option<MessageDescriptor> {
77 self.file_descriptors
78 .iter()
79 .find_map(|fd| fd.message_by_package_relative_name(message_kind.as_ref()))
80 }
81
82 fn value_to_message(
83 &self,
84 message_kind: MessageKind,
85 json: &Value,
86 ) -> Result<Box<dyn MessageDyn>> {
87 self.file_descriptors
88 .iter()
89 .find_map(|fd| fd.message_by_package_relative_name(message_kind.as_ref()))
90 .ok_or(Error::Message(format!(
91 "message {message_kind:?} not found"
92 )))
93 .and_then(|message_descriptor| {
94 serde_json::to_string(json)
95 .map_err(Error::from)
96 .and_then(|json| {
97 parse_dyn_from_str(&message_descriptor, json.as_str()).map_err(Into::into)
98 })
99 })
100 }
101
102 pub fn encode_from_value(&self, message_kind: MessageKind, json: &Value) -> Result<Bytes> {
103 self.value_to_message(message_kind, json)
104 .and_then(message_to_bytes)
105 }
106
107 fn message_generator(&self) -> Option<MessageGenerator> {
108 self.file_descriptors
109 .iter()
110 .find_map(|fd| fd.message_by_package_relative_name("Generator"))
111 .map(|generator_descriptor| MessageGenerator {
112 generator_descriptor,
113 })
114 }
115
116 fn generate_message_kind(&self, message_kind: MessageKind) -> Result<Option<Bytes>> {
117 debug!(?message_kind);
118
119 let engine = {
120 let mut engine = Engine::new();
121
122 _ = engine
123 .register_fn("first_name", || {
124 fake::faker::name::raw::FirstName(fake::locales::EN).fake::<String>()
125 })
126 .register_fn("last_name", || {
127 fake::faker::name::raw::LastName(fake::locales::EN).fake::<String>()
128 })
129 .register_fn("safe_email", || {
130 fake::faker::internet::raw::SafeEmail(fake::locales::EN).fake::<String>()
131 })
132 .register_fn("building_number", || {
133 fake::faker::address::raw::BuildingNumber(fake::locales::EN).fake::<String>()
134 })
135 .register_fn("street_name", || {
136 fake::faker::address::raw::StreetName(fake::locales::EN).fake::<String>()
137 })
138 .register_fn("city_name", || {
139 fake::faker::address::raw::CityName(fake::locales::EN).fake::<String>()
140 })
141 .register_fn("post_code", || {
142 fake::faker::address::raw::PostCode(fake::locales::EN).fake::<String>()
143 })
144 .register_fn("country_name", || {
145 fake::faker::address::raw::CountryName(fake::locales::EN).fake::<String>()
146 })
147 .register_fn("industry", || {
148 fake::faker::company::raw::Industry(fake::locales::EN).fake::<String>()
149 });
150
151 let random = RandomPackage::new();
152 _ = random.register_into_engine(&mut engine);
153 engine
154 };
155
156 self.message_by_package_relative_name(message_kind)
157 .map_or(Ok(None), |message_descriptor| {
158 self.message_generator()
159 .map_or(Ok(None), |message_generator| {
160 message_generator
161 .generate(&engine, &message_descriptor)
162 .and_then(message_to_bytes)
163 .map(Some)
164 })
165 })
166 }
167
168 fn message_value_as_bytes(
169 &self,
170 message_kind: MessageKind,
171 json: &Value,
172 ) -> Result<Option<Bytes>> {
173 self.message_by_package_relative_name(message_kind)
174 .map(|message_descriptor| {
175 serde_json::to_string(json)
176 .map_err(Error::from)
177 .inspect(|json| debug!(%json))
178 .and_then(|json| {
179 parse_dyn_from_str(&message_descriptor, json.as_str()).map_err(Into::into)
180 })
181 .inspect(|message| debug!(%message))
182 .and_then(|message| {
183 let mut w = BytesMut::new().writer();
184 message
185 .write_to_writer_dyn(&mut w)
186 .map(|()| Bytes::from(w.into_inner()))
187 .map_err(Into::into)
188 })
189 .inspect_err(|err| error!(?err))
190 })
191 .transpose()
192 }
193}
194
195#[derive(Clone, Debug, Eq, Hash, PartialEq)]
196struct MessageGenerator {
197 generator_descriptor: MessageDescriptor,
198}
199
200impl MessageGenerator {
201 fn generate(
202 &self,
203 engine: &Engine,
204 message_descriptor: &MessageDescriptor,
205 ) -> Result<Box<dyn MessageDyn>> {
206 debug!(message_descriptor = message_descriptor.full_name());
207
208 let mut message_dyn = message_descriptor.new_instance();
209
210 for (field_proto, field) in message_descriptor
211 .proto()
212 .field
213 .iter()
214 .zip(message_dyn.descriptor_dyn().fields())
215 {
216 let field_generator = FieldGenerator {
217 generator_descriptor: &self.generator_descriptor,
218 configuration: FieldGeneratorConfiguration::with_field_generator(
219 field_proto,
220 &self.generator_descriptor,
221 ),
222 };
223
224 if field_generator.configuration.skip() {
225 continue;
226 }
227
228 match field.runtime_field_type() {
229 RuntimeFieldType::Singular(ref singular) => field_generator
230 .singular_value(engine, singular)
231 .map(|value| field.set_singular_field(message_dyn.as_mut(), value))?,
232
233 RuntimeFieldType::Repeated(ref repeated) => {
234 let mut r = field.mut_repeated(message_dyn.as_mut());
235 for element in field_generator.repeated_value(engine, repeated)? {
236 r.push(element);
237 }
238 }
239
240 RuntimeFieldType::Map(key, value) => todo!("key={key:?} value={value:?}"),
241 }
242 }
243
244 Ok(message_dyn)
245 }
246}
247
248struct FieldGenerator<'a> {
249 generator_descriptor: &'a MessageDescriptor,
250 configuration: FieldGeneratorConfiguration,
251}
252
253impl<'a> FieldGenerator<'a> {
254 fn singular_value(
255 &self,
256 engine: &Engine,
257 runtime_type: &RuntimeType,
258 ) -> Result<ReflectValueBox> {
259 let mut rng = rand::rng();
260 match runtime_type {
261 RuntimeType::I32 => self
262 .configuration
263 .script()
264 .inspect(|script| debug!(script))
265 .map_or(Ok(rng.random()), |script| {
266 engine
267 .eval::<i32>(script)
268 .inspect_err(|err| debug!(script, ?err))
269 })
270 .inspect(|result| debug!(?result))
271 .map(ReflectValueBox::from)
272 .map_err(Into::into),
273
274 RuntimeType::I64 => self
275 .configuration
276 .script()
277 .inspect(|script| debug!(script))
278 .map_or(Ok(rng.random()), |script| {
279 engine
280 .eval::<i64>(script)
281 .inspect_err(|err| debug!(script, ?err))
282 })
283 .inspect(|result| debug!(?result))
284 .map(ReflectValueBox::from)
285 .map_err(Into::into),
286
287 RuntimeType::U32 => self
288 .configuration
289 .script()
290 .inspect(|script| debug!(script))
291 .map_or(Ok(rng.random()), |script| {
292 engine
293 .eval::<u32>(script)
294 .inspect_err(|err| debug!(script, ?err))
295 })
296 .inspect(|result| debug!(?result))
297 .map(ReflectValueBox::from)
298 .map_err(Into::into),
299
300 RuntimeType::U64 => self
301 .configuration
302 .script()
303 .inspect(|script| debug!(script))
304 .map_or(Ok(rng.random()), |script| {
305 engine
306 .eval::<u64>(script)
307 .inspect_err(|err| debug!(script, ?err))
308 })
309 .inspect(|result| debug!(?result))
310 .map(ReflectValueBox::from)
311 .map_err(Into::into),
312
313 RuntimeType::F32 => self
314 .configuration
315 .script()
316 .inspect(|script| debug!(script))
317 .map_or(Ok(rng.random()), |script| {
318 engine
319 .eval::<f32>(script)
320 .inspect_err(|err| debug!(script, ?err))
321 })
322 .inspect(|result| debug!(?result))
323 .map(ReflectValueBox::from)
324 .map_err(Into::into),
325
326 RuntimeType::F64 => self
327 .configuration
328 .script()
329 .inspect(|script| debug!(script))
330 .map_or(Ok(rng.random()), |script| {
331 engine
332 .eval::<f64>(script)
333 .inspect_err(|err| debug!(script, ?err))
334 })
335 .inspect(|result| debug!(?result))
336 .map(ReflectValueBox::from)
337 .map_err(Into::into),
338
339 RuntimeType::Bool => self
340 .configuration
341 .script()
342 .inspect(|script| debug!(script))
343 .map_or(Ok(rng.random()), |script| {
344 engine
345 .eval::<bool>(script)
346 .inspect_err(|err| debug!(script, ?err))
347 })
348 .inspect(|result| debug!(?result))
349 .map(ReflectValueBox::from)
350 .map_err(Into::into),
351
352 RuntimeType::String => self
353 .configuration
354 .script()
355 .inspect(|script| debug!(script))
356 .map_or(Ok(String::from("abc")), |script| {
357 engine
358 .eval::<String>(script)
359 .inspect_err(|err| debug!(script, ?err))
360 })
361 .inspect(|result| debug!(?result))
362 .map(ReflectValueBox::from)
363 .map_err(Into::into),
364
365 RuntimeType::VecU8 => todo!(),
366
367 RuntimeType::Enum(descriptor) => self
368 .configuration
369 .script()
370 .inspect(|script| debug!(script))
371 .map_or(Ok(ReflectValueBox::Enum(descriptor.clone(), 1)), |script| {
372 engine
373 .eval::<String>(script)
374 .inspect(|name| debug!(name))
375 .map(|name| {
376 descriptor
377 .value_by_name(&name[..])
378 .inspect(|value_descriptor| debug!(?value_descriptor))
379 .map(|value_descriptor| {
380 ReflectValueBox::Enum(
381 descriptor.clone(),
382 value_descriptor.value(),
383 )
384 })
385 .inspect(|value| debug!(?value))
386 .unwrap()
387 })
388 .inspect_err(|err| debug!(script, ?err))
389 })
390 .inspect(|result| debug!(?result))
391 .map_err(Into::into),
392
393 RuntimeType::Message(message_descriptor) => {
394 let generator = MessageGenerator {
395 generator_descriptor: self.generator_descriptor.to_owned(),
396 };
397
398 generator
399 .generate(engine, message_descriptor)
400 .map(ReflectValueBox::Message)
401 }
402 }
403 }
404
405 fn repeated_value(
406 &self,
407 engine: &Engine,
408 runtime_type: &RuntimeType,
409 ) -> Result<Vec<ReflectValueBox>> {
410 let upper = self.configuration.repeated_len().unwrap_or_else(|| {
411 rand::rng().random_range(self.configuration.repeated_range().unwrap_or(0..=1))
412 });
413
414 (0..upper)
415 .inspect(|i| debug!(i))
416 .map(|_| self.singular_value(engine, runtime_type))
417 .collect::<Result<Vec<_>>>()
418 }
419}
420
421#[derive(Clone, Debug, PartialEq, PartialOrd)]
422enum FieldGeneratorConfiguration {
423 Bool(bool),
424 Bytes(Bytes),
425 F32(f32),
426 F64(f64),
427 I32(i32),
428 I64(i64),
429 List(Vec<FieldGeneratorConfiguration>),
430 Message(BTreeMap<String, FieldGeneratorConfiguration>),
431 String(String),
432 U32(u32),
433 U64(u64),
434}
435
436impl Default for FieldGeneratorConfiguration {
437 fn default() -> Self {
438 Self::Message(Default::default())
439 }
440}
441
442impl FieldGeneratorConfiguration {
443 fn with_field_generator(
444 field: &FieldDescriptorProto,
445 generator: &MessageDescriptor,
446 ) -> FieldGeneratorConfiguration {
447 debug!(field = field.name(), generator = generator.full_name(),);
448
449 field
450 .options
451 .special_fields
452 .unknown_fields()
453 .iter()
454 .find_map(|(id, unknown)| {
455 if id != 51215 {
456 None
457 } else if let UnknownValueRef::LengthDelimited(items) = unknown {
458 let mut message = generator.new_instance();
459
460 _ = message
461 .merge_from_bytes_dyn(items)
462 .inspect_err(|err| debug!(?err))
463 .ok();
464
465 Some(Self::from(message.as_ref()))
466 } else {
467 None
468 }
469 })
470 .unwrap_or_default()
471 }
472
473 fn skip(&self) -> bool {
474 self.get("skip")
475 .cloned()
476 .and_then(|value| value.as_bool())
477 .inspect(|skip| debug!(?skip))
478 .unwrap_or_default()
479 }
480
481 fn script(&self) -> Option<&str> {
482 self.get("script")
483 .inspect(|script| debug!(?script))
484 .and_then(|value| value.as_str())
485 .inspect(|script| debug!(?script))
486 .or(self.repeated_script())
487 }
488
489 fn repeated_range(&self) -> Option<RangeInclusive<u32>> {
490 self.get("repeated")
491 .inspect(|repeated| debug!(?repeated))
492 .and_then(|repeated| {
493 repeated
494 .get("range")
495 .inspect(|range| debug!(?range))
496 .and_then(|range| {
497 range
498 .get("min")
499 .inspect(|min| debug!(?min))
500 .and_then(|min| min.as_u32())
501 .and_then(|min| {
502 range
503 .get("max")
504 .inspect(|max| debug!(?max))
505 .and_then(|max| max.as_u32())
506 .map(|max| min..=max)
507 })
508 })
509 })
510 }
511
512 fn repeated_len(&self) -> Option<u32> {
513 self.get("repeated")
514 .and_then(|repeated| repeated.get("len"))
515 .and_then(|len| len.as_u32())
516 }
517
518 fn repeated_script(&self) -> Option<&str> {
519 self.get("repeated")
520 .and_then(|repeated| repeated.get("script"))
521 .and_then(|script| script.as_str())
522 }
523
524 fn get(&self, key: &str) -> Option<&FieldGeneratorConfiguration> {
525 if let Self::Message(message) = self {
526 message.get(key)
527 } else {
528 None
529 }
530 }
531
532 fn as_bool(&self) -> Option<bool> {
533 if let Self::Bool(flag) = self {
534 Some(*flag)
535 } else {
536 None
537 }
538 }
539
540 fn as_str(&self) -> Option<&str> {
541 if let Self::String(value) = self {
542 Some(value)
543 } else {
544 None
545 }
546 }
547
548 fn as_u32(&self) -> Option<u32> {
549 if let Self::U32(value) = self {
550 Some(*value)
551 } else {
552 None
553 }
554 }
555}
556
557impl From<EnumDescriptor> for FieldGeneratorConfiguration {
558 fn from(_value: EnumDescriptor) -> Self {
559 todo!()
560 }
561}
562
563impl<'a> From<ReflectValueRef<'a>> for FieldGeneratorConfiguration {
564 fn from(value: ReflectValueRef<'a>) -> Self {
565 match value {
566 ReflectValueRef::U32(value) => Self::U32(value),
567 ReflectValueRef::U64(value) => Self::U64(value),
568 ReflectValueRef::I32(value) => Self::I32(value),
569 ReflectValueRef::I64(value) => Self::I64(value),
570 ReflectValueRef::F32(value) => Self::F32(value),
571 ReflectValueRef::F64(value) => Self::F64(value),
572 ReflectValueRef::Bool(value) => Self::Bool(value),
573 ReflectValueRef::String(value) => Self::String(value.to_owned()),
574 ReflectValueRef::Bytes(items) => Self::Bytes(Bytes::copy_from_slice(items)),
575 ReflectValueRef::Enum(enum_descriptor, _) => Self::from(enum_descriptor),
576 ReflectValueRef::Message(message_ref) => Self::from(message_ref.deref()),
577 }
578 }
579}
580
581impl From<&dyn MessageDyn> for FieldGeneratorConfiguration {
582 fn from(message: &dyn MessageDyn) -> Self {
583 debug!(%message);
584
585 Self::Message(
586 message
587 .descriptor_dyn()
588 .fields()
589 .inspect(|field| debug!(field = field.name()))
590 .filter_map(|field| match field.runtime_field_type() {
591 RuntimeFieldType::Singular(singular) => {
592 debug!(?singular);
593 field
594 .get_singular(message)
595 .inspect(|value| debug!(field = field.name(), ?value))
596 .map(|value| (field.name().to_owned(), Self::from(value)))
597 }
598
599 RuntimeFieldType::Repeated(repeated) => {
600 debug!(?repeated);
601 Some((
602 field.name().to_owned(),
603 Self::List(
604 field
605 .get_repeated(message)
606 .into_iter()
607 .map(Self::from)
608 .inspect(|configuration| debug!(?configuration))
609 .collect::<Vec<_>>(),
610 ),
611 ))
612 }
613
614 RuntimeFieldType::Map(key, value) => todo!("key={key:?} value={value:?}"),
615 })
616 .inspect(|(field, value)| debug!(field, ?value))
617 .collect::<BTreeMap<String, FieldGeneratorConfiguration>>(),
618 )
619 }
620}
621
622fn message_to_bytes(message: Box<dyn MessageDyn>) -> Result<Bytes> {
623 let mut w = BytesMut::new().writer();
624 message
625 .write_to_writer_dyn(&mut w)
626 .and(Ok(Bytes::from(w.into_inner())))
627 .map_err(Into::into)
628}
629
630impl AsKafkaRecord for Schema {
631 fn as_kafka_record(&self, value: &Value) -> Result<tansu_sans_io::record::Builder> {
632 debug!(?value);
633
634 let mut builder = tansu_sans_io::record::Record::builder();
635
636 if let Some(value) = value.get("key") {
637 debug!(?value);
638
639 if let Some(encoded) = self.message_value_as_bytes(MessageKind::Key, value)? {
640 builder = builder.key(encoded.into());
641 }
642 };
643
644 if let Some(value) = value.get("value") {
645 debug!(?value);
646
647 if let Some(encoded) = self.message_value_as_bytes(MessageKind::Value, value)? {
648 builder = builder.value(encoded.into());
649 }
650 };
651
652 Ok(builder)
653 }
654}
655
656impl Generator for Schema {
657 fn generate(&self) -> Result<tansu_sans_io::record::Builder> {
658 let mut builder = tansu_sans_io::record::Record::builder();
659
660 if let Some(generated) = self.generate_message_kind(MessageKind::Key)? {
661 builder = builder.key(generated.into());
662 }
663
664 if let Some(generated) = self.generate_message_kind(MessageKind::Value)? {
665 builder = builder.value(generated.into());
666 }
667
668 Ok(builder)
669 }
670}
671
672fn decode(
673 message_descriptor: Option<MessageDescriptor>,
674 encoded: Option<Bytes>,
675) -> Result<Option<Box<dyn MessageDyn>>> {
676 debug!(?message_descriptor, ?encoded);
677
678 message_descriptor.map_or(Ok(None), |message_descriptor| {
679 encoded.map_or(Err(Error::Api(ErrorCode::InvalidRecord)), |encoded| {
680 let mut message = message_descriptor.new_instance();
681
682 message
683 .merge_from_dyn(&mut CodedInputStream::from_tokio_bytes(&encoded))
684 .inspect_err(|err| error!(?err))
685 .map_err(|_err| Error::Api(ErrorCode::InvalidRecord))
686 .and(Ok(Some(message)))
687 .inspect(|message| debug!(?message))
688 })
689 })
690}
691
692fn validate(message_descriptor: Option<MessageDescriptor>, encoded: Option<Bytes>) -> Result<()> {
693 decode(message_descriptor, encoded).and(Ok(()))
694}
695
696impl Validator for Schema {
697 fn validate(&self, batch: &Batch) -> Result<()> {
698 debug!(?batch);
699
700 for record in &batch.records {
701 debug!(?record);
702
703 validate(
704 self.message_by_package_relative_name(MessageKind::Key),
705 record.key.clone(),
706 )
707 .and(validate(
708 self.message_by_package_relative_name(MessageKind::Value),
709 record.value.clone(),
710 ))
711 .inspect_err(|err| error!(?err))?
712 }
713
714 Ok(())
715 }
716}
717
718impl TryFrom<Bytes> for Schema {
719 type Error = Error;
720
721 fn try_from(proto: Bytes) -> Result<Self, Self::Error> {
722 make_fd(proto)
723 .map(|mut protos| {
724 debug!(
725 protos = ?protos
726 .iter()
727 .flat_map(|proto| {
728 proto
729 .messages()
730 .map(|message| message.name_to_package().to_owned())
731 })
732 .collect::<Vec<_>>()
733 );
734
735 if let Some(mut meta) = META_FILE_DESCRIPTOR.clone() {
736 debug!(
737 meta = ?meta
738 .iter()
739 .flat_map(|proto| {
740 proto
741 .messages()
742 .map(|message| message.name_to_package().to_owned())
743 })
744 .collect::<Vec<_>>()
745 );
746
747 protos.append(&mut meta);
748 }
749
750 protos
751 })
752 .map(|file_descriptors| Self { file_descriptors })
753 }
754}
755
756static WELL_KNOWN_TYPES: LazyLock<Vec<FileDescriptor>> = LazyLock::new(|| {
757 vec![
758 descriptor::file_descriptor().to_owned(),
759 well_known_types::duration::file_descriptor().to_owned(),
760 well_known_types::empty::file_descriptor().to_owned(),
761 well_known_types::source_context::file_descriptor().to_owned(),
762 well_known_types::timestamp::file_descriptor().to_owned(),
763 well_known_types::wrappers::file_descriptor().to_owned(),
764 ]
765});
766
767static META_FILE_DESCRIPTOR: LazyLock<Option<Vec<FileDescriptor>>> =
768 LazyLock::new(|| make_fd(Bytes::from_static(include_bytes!("meta.proto"))).ok());
769
770fn make_fd(proto: Bytes) -> Result<Vec<FileDescriptor>> {
771 tempdir().map_err(Into::into).and_then(|temp_dir| {
772 NamedTempFile::new_in(&temp_dir)
773 .inspect(|temp_dir| debug!(?temp_dir))
774 .map_err(Into::into)
775 .and_then(|mut temp_file| {
776 temp_file.write_all(&proto).map_err(Into::into).and(
777 protobuf_parse::Parser::new()
778 .pure()
779 .input(&temp_file)
780 .include(&temp_dir)
781 .parse_and_typecheck()
782 .inspect_err(|err| debug!(?err))
783 .map_err(Into::into)
784 .and_then(|parsed| {
785 parsed
786 .file_descriptors
787 .into_iter()
788 .map(|file_descriptor_proto| {
789 FileDescriptor::new_dynamic(
790 file_descriptor_proto,
791 &WELL_KNOWN_TYPES[..],
792 )
793 .inspect_err(|err| debug!(?err))
794 .map_err(Into::into)
795 })
796 .collect::<Result<Vec<_>>>()
797 }),
798 )
799 })
800 })
801}
802
803impl Schema {
804 fn to_json_value(
805 &self,
806 message_kind: MessageKind,
807 encoded: Option<Bytes>,
808 ) -> Result<(String, Value)> {
809 decode(self.message_by_package_relative_name(message_kind), encoded)
810 .inspect(|decoded| debug!(?decoded))
811 .and_then(|decoded| {
812 decoded.map_or(
813 Ok((message_kind.as_ref().to_lowercase(), Value::Null)),
814 |message| {
815 print_to_string(message.as_ref())
816 .inspect(|s| debug!(s))
817 .map_err(Into::into)
818 .and_then(|s| serde_json::from_str::<Value>(&s).map_err(Into::into))
819 .map(|value| (message_kind.as_ref().to_lowercase(), value))
820 .inspect(|(k, v)| debug!(k, ?v))
821 },
822 )
823 })
824 }
825}
826
827impl AsJsonValue for Schema {
828 fn as_json_value(&self, batch: &Batch) -> Result<Value> {
829 Ok(Value::Array(
830 batch
831 .records
832 .iter()
833 .inspect(|record| debug!(?record))
834 .map(|record| {
835 Value::Object(Map::from_iter(
836 self.to_json_value(MessageKind::Key, record.key.clone())
837 .into_iter()
838 .chain(self.to_json_value(MessageKind::Value, record.value.clone())),
839 ))
840 })
841 .collect::<Vec<_>>(),
842 ))
843 }
844}
845
846#[cfg(test)]
847mod tests {
848 use crate::Registry;
849
850 use super::*;
851
852 use object_store::{ObjectStoreExt, PutPayload, memory::InMemory, path::Path};
853
854 use serde_json::json;
855 use std::{fs::File, sync::Arc, thread};
856 use tansu_sans_io::record::Record;
857 use tracing::subscriber::DefaultGuard;
858 use tracing_subscriber::EnvFilter;
859
860 fn init_tracing() -> Result<DefaultGuard> {
861 Ok(tracing::subscriber::set_default(
862 tracing_subscriber::fmt()
863 .with_level(true)
864 .with_line_number(true)
865 .with_thread_names(false)
866 .with_env_filter(
867 EnvFilter::from_default_env()
868 .add_directive(format!("{}=debug", env!("CARGO_CRATE_NAME")).parse()?),
869 )
870 .with_writer(
871 thread::current()
872 .name()
873 .ok_or(Error::Message(String::from("unnamed thread")))
874 .and_then(|name| {
875 File::create(format!("../logs/{}/{name}.log", env!("CARGO_PKG_NAME"),))
876 .map_err(Into::into)
877 })
878 .map(Arc::new)?,
879 )
880 .finish(),
881 ))
882 }
883
884 #[tokio::test]
885 async fn key_only_invalid_record() -> Result<()> {
886 let _guard = init_tracing()?;
887
888 let topic = "def";
889
890 let proto = Bytes::from_static(
891 br#"
892 syntax = 'proto3';
893
894 message Key {
895 int32 id = 1;
896 }
897
898 message Value {
899 string name = 1;
900 string email = 2;
901 }
902 "#,
903 );
904
905 let object_store = InMemory::new();
906 let location = Path::from(format!("{topic}.proto"));
907 let payload = PutPayload::from(proto.clone());
908 _ = object_store.put(&location, payload).await?;
909
910 let registry = Registry::new(object_store);
911
912 let key = Schema::try_from(proto.clone())
913 .and_then(|schema| schema.encode_from_value(MessageKind::Key, &json!({"id": 12321})))?;
914
915 let batch = Batch::builder()
916 .record(Record::builder().key(key.clone().into()))
917 .build()?;
918
919 assert!(matches!(
920 registry.validate(topic, &batch).await,
921 Err(Error::Api(ErrorCode::InvalidRecord))
922 ));
923
924 Ok(())
925 }
926
927 #[tokio::test]
928 async fn value_only_invalid_record() -> Result<()> {
929 let _guard = init_tracing()?;
930
931 let topic = "def";
932
933 let proto = Bytes::from_static(
934 br#"
935 syntax = 'proto3';
936
937 message Key {
938 int32 id = 1;
939 }
940
941 message Value {
942 string name = 1;
943 string email = 2;
944 }
945 "#,
946 );
947
948 let object_store = InMemory::new();
949 let location = Path::from(format!("{topic}.proto"));
950 let payload = PutPayload::from(proto.clone());
951 _ = object_store.put(&location, payload).await?;
952
953 let registry = Registry::new(object_store);
954
955 let value = Schema::try_from(proto).and_then(|schema| {
956 schema.encode_from_value(
957 MessageKind::Value,
958 &json!({
959 "name": "alice",
960 "email": "alice@example.com"
961 }),
962 )
963 })?;
964
965 let batch = Batch::builder()
966 .record(Record::builder().value(value.clone().into()))
967 .build()?;
968
969 assert!(matches!(
970 registry.validate(topic, &batch).await,
971 Err(Error::Api(ErrorCode::InvalidRecord))
972 ));
973
974 Ok(())
975 }
976
977 #[tokio::test]
978 async fn key_and_value() -> Result<()> {
979 let _guard = init_tracing()?;
980
981 let topic = "def";
982
983 let proto = Bytes::from_static(
984 br#"
985 syntax = 'proto3';
986
987 message Key {
988 int32 id = 1;
989 }
990
991 message Value {
992 string name = 1;
993 string email = 2;
994 }
995 "#,
996 );
997
998 let object_store = InMemory::new();
999 let location = Path::from(format!("{topic}.proto"));
1000 let payload = PutPayload::from(proto.clone());
1001 _ = object_store.put(&location, payload).await?;
1002
1003 let registry = Registry::new(object_store);
1004
1005 let schema = Schema::try_from(proto.clone())?;
1006
1007 let key = schema.encode_from_value(MessageKind::Key, &json!({"id": 12321}))?;
1008 let value = schema.encode_from_value(
1009 MessageKind::Value,
1010 &json!({
1011 "name": "alice",
1012 "email": "alice@example.com"
1013 }),
1014 )?;
1015
1016 let batch = Batch::builder()
1017 .record(
1018 Record::builder()
1019 .key(key.clone().into())
1020 .value(value.clone().into()),
1021 )
1022 .build()?;
1023
1024 registry.validate(topic, &batch).await
1025 }
1026
1027 #[tokio::test]
1028 async fn no_schema() -> Result<()> {
1029 let _guard = init_tracing()?;
1030
1031 let topic = "def";
1032
1033 let registry = Registry::new(InMemory::new());
1034
1035 let key = Bytes::from_static(b"Lorem ipsum dolor sit amet");
1036 let value = Bytes::from_static(b"Consectetur adipiscing elit");
1037
1038 let batch = Batch::builder()
1039 .record(
1040 Record::builder()
1041 .key(key.clone().into())
1042 .value(value.clone().into()),
1043 )
1044 .build()?;
1045
1046 registry.validate(topic, &batch).await
1047 }
1048
1049 #[tokio::test]
1050 async fn empty_schema() -> Result<()> {
1051 let _guard = init_tracing()?;
1052
1053 let topic = "def";
1054
1055 let proto = Bytes::from_static(br#"syntax = 'proto3';"#);
1056
1057 let object_store = InMemory::new();
1058 let location = Path::from(format!("{topic}.proto"));
1059 let payload = PutPayload::from(proto.clone());
1060 _ = object_store.put(&location, payload).await?;
1061
1062 let registry = Registry::new(object_store);
1063
1064 let key = Bytes::from_static(b"Lorem ipsum dolor sit amet");
1065 let value = Bytes::from_static(b"Consectetur adipiscing elit");
1066
1067 let batch = Batch::builder()
1068 .record(
1069 Record::builder()
1070 .key(key.clone().into())
1071 .value(value.clone().into()),
1072 )
1073 .build()?;
1074
1075 registry.validate(topic, &batch).await
1076 }
1077
1078 #[tokio::test]
1079 async fn key_schema_only() -> Result<()> {
1080 let _guard = init_tracing()?;
1081
1082 let topic = "def";
1083
1084 let proto = Bytes::from_static(
1085 br#"
1086 syntax = 'proto3';
1087
1088 message Key {
1089 int32 id = 1;
1090 }
1091 "#,
1092 );
1093
1094 let object_store = InMemory::new();
1095 let location = Path::from(format!("{topic}.proto"));
1096 let payload = PutPayload::from(proto.clone());
1097 _ = object_store.put(&location, payload).await?;
1098
1099 let registry = Registry::new(object_store);
1100
1101 let schema = Schema::try_from(proto)?;
1102
1103 let key = schema.encode_from_value(MessageKind::Key, &json!({"id": 12321}))?;
1104 let value = Bytes::from_static(b"Consectetur adipiscing elit");
1105
1106 let batch = Batch::builder()
1107 .record(
1108 Record::builder()
1109 .key(key.clone().into())
1110 .value(value.clone().into()),
1111 )
1112 .build()?;
1113
1114 registry.validate(topic, &batch).await
1115 }
1116
1117 #[tokio::test]
1118 async fn bad_key() -> Result<()> {
1119 let _guard = init_tracing()?;
1120
1121 let topic = "def";
1122
1123 let proto = Bytes::from_static(
1124 br#"
1125 syntax = 'proto3';
1126
1127 message Key {
1128 int32 id = 1;
1129 }
1130 "#,
1131 );
1132
1133 let object_store = InMemory::new();
1134 let location = Path::from(format!("{topic}.proto"));
1135 let payload = PutPayload::from(proto.clone());
1136 _ = object_store.put(&location, payload).await?;
1137
1138 let registry = Registry::new(object_store);
1139
1140 let key = Bytes::from_static(b"Lorem ipsum dolor sit amet");
1141
1142 let batch = Batch::builder()
1143 .record(Record::builder().key(key.clone().into()))
1144 .build()?;
1145
1146 assert!(matches!(
1147 registry.validate(topic, &batch).await,
1148 Err(Error::Api(ErrorCode::InvalidRecord))
1149 ));
1150
1151 Ok(())
1152 }
1153
1154 #[tokio::test]
1155 async fn value_schema_only() -> Result<()> {
1156 let _guard = init_tracing()?;
1157
1158 let topic = "def";
1159
1160 let proto = Bytes::from_static(
1161 br#"
1162 syntax = 'proto3';
1163
1164 message Value {
1165 string name = 1;
1166 string email = 2;
1167 }
1168 "#,
1169 );
1170
1171 let object_store = InMemory::new();
1172 let location = Path::from(format!("{topic}.proto"));
1173 let payload = PutPayload::from(proto.clone());
1174 _ = object_store.put(&location, payload).await?;
1175
1176 let registry = Registry::new(object_store);
1177
1178 let schema = Schema::try_from(proto)?;
1179
1180 let key = Bytes::from_static(b"Lorem ipsum dolor sit amet");
1181
1182 let value = schema.encode_from_value(
1183 MessageKind::Value,
1184 &json!({
1185 "name": "alice",
1186 "email": "alice@example.com"
1187 }),
1188 )?;
1189
1190 let batch = Batch::builder()
1191 .record(
1192 Record::builder()
1193 .key(key.clone().into())
1194 .value(value.clone().into()),
1195 )
1196 .build()?;
1197
1198 registry.validate(topic, &batch).await
1199 }
1200
1201 #[tokio::test]
1202 async fn bad_value() -> Result<()> {
1203 let _guard = init_tracing()?;
1204
1205 let topic = "def";
1206
1207 let proto = Bytes::from_static(
1208 br#"
1209 syntax = 'proto3';
1210
1211 message Value {
1212 string name = 1;
1213 string email = 2;
1214 }
1215 "#,
1216 );
1217
1218 let object_store = InMemory::new();
1219 let location = Path::from(format!("{topic}.proto"));
1220 let payload = PutPayload::from(proto.clone());
1221 _ = object_store.put(&location, payload).await?;
1222
1223 let registry = Registry::new(object_store);
1224
1225 let value = Bytes::from_static(b"Consectetur adipiscing elit");
1226
1227 let batch = Batch::builder()
1228 .record(Record::builder().value(value.clone().into()))
1229 .build()?;
1230
1231 assert!(matches!(
1232 registry.validate(topic, &batch).await,
1233 Err(Error::Api(ErrorCode::InvalidRecord))
1234 ));
1235
1236 Ok(())
1237 }
1238
1239 #[test]
1240 fn timestamp_well_known_type() -> Result<()> {
1241 let _guard = init_tracing()?;
1242 let proto = Bytes::from_static(
1243 br#"
1244 syntax = "proto3";
1245
1246 import "google/protobuf/timestamp.proto";
1247
1248 message Value {
1249 google.protobuf.Timestamp timestamp = 1;
1250 }
1251 "#,
1252 );
1253
1254 let _file_descriptor = make_fd(proto).inspect(|fds| debug!(?fds))?;
1255
1256 Ok(())
1257 }
1258
1259 #[tokio::test]
1260 async fn customer_002_user_id() -> Result<()> {
1261 let _guard = init_tracing()?;
1262
1263 let schema = Schema::try_from(Bytes::from_static(include_bytes!(
1264 "../tests/customer-002.proto"
1265 )))?;
1266
1267 let message_generator = schema.message_generator().unwrap();
1268
1269 let user_id = schema
1270 .message_by_package_relative_name(MessageKind::Value)
1271 .and_then(|message_descriptor| message_descriptor.field_by_name("user_id"))
1272 .unwrap();
1273
1274 let configuration = FieldGeneratorConfiguration::with_field_generator(
1275 user_id.proto(),
1276 &message_generator.generator_descriptor,
1277 );
1278
1279 assert!(configuration.skip());
1280
1281 Ok(())
1282 }
1283
1284 #[tokio::test]
1285 async fn customer_002_email_address() -> Result<()> {
1286 let _guard = init_tracing()?;
1287
1288 let schema = Schema::try_from(Bytes::from_static(include_bytes!(
1289 "../tests/customer-002.proto"
1290 )))?;
1291
1292 let message_generator = schema.message_generator().unwrap();
1293
1294 let user_id = schema
1295 .message_by_package_relative_name(MessageKind::Value)
1296 .and_then(|message_descriptor| message_descriptor.field_by_name("email_address"))
1297 .unwrap();
1298
1299 let configuration = FieldGeneratorConfiguration::with_field_generator(
1300 user_id.proto(),
1301 &message_generator.generator_descriptor,
1302 );
1303
1304 assert!(!configuration.skip());
1305 assert_eq!(Some("\"lorem\""), configuration.script());
1306
1307 Ok(())
1308 }
1309
1310 #[tokio::test]
1311 async fn customer_002_industry() -> Result<()> {
1312 let _guard = init_tracing()?;
1313
1314 let schema = Schema::try_from(Bytes::from_static(include_bytes!(
1315 "../tests/customer-002.proto"
1316 )))?;
1317
1318 let message_generator = schema.message_generator().unwrap();
1319
1320 let user_id = schema
1321 .message_by_package_relative_name(MessageKind::Value)
1322 .and_then(|message_descriptor| message_descriptor.field_by_name("industry"))
1323 .unwrap();
1324
1325 let configuration = FieldGeneratorConfiguration::with_field_generator(
1326 user_id.proto(),
1327 &message_generator.generator_descriptor,
1328 );
1329
1330 assert!(!configuration.skip());
1331 assert_eq!(Some(3), configuration.repeated_len());
1332 assert_eq!(Some("\"elit\""), configuration.repeated_script());
1333
1334 Ok(())
1335 }
1336
1337 #[tokio::test]
1338 async fn customer_003_industry() -> Result<()> {
1339 let _guard = init_tracing()?;
1340
1341 let schema = Schema::try_from(Bytes::from_static(include_bytes!(
1342 "../tests/customer-003.proto"
1343 )))?;
1344
1345 let message_generator = schema.message_generator().unwrap();
1346
1347 let user_id = schema
1348 .message_by_package_relative_name(MessageKind::Value)
1349 .and_then(|message_descriptor| message_descriptor.field_by_name("industry"))
1350 .unwrap();
1351
1352 let configuration = FieldGeneratorConfiguration::with_field_generator(
1353 user_id.proto(),
1354 &message_generator.generator_descriptor,
1355 );
1356
1357 assert!(!configuration.skip());
1358 assert_eq!(Some(1..=3), configuration.repeated_range());
1359 assert_eq!(Some("\"elit\""), configuration.repeated_script());
1360
1361 Ok(())
1362 }
1363}