Skip to main content

tansu_schema/
proto.rs

1// Copyright ⓒ 2024-2025 Peter Morgan <peter.james.morgan@gmail.com>
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Protocol Buffer message schema
16
17use 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}