mz_proto/
lib.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! Generated protobuf code and companion impls.
11
12use std::borrow::Cow;
13use std::char::CharTryFromError;
14use std::collections::{BTreeMap, BTreeSet};
15use std::fmt;
16use std::num::{NonZeroU64, TryFromIntError};
17use std::sync::Arc;
18
19use mz_ore::Overflowing;
20use mz_ore::cast::CastFrom;
21use mz_ore::num::{NonNeg, NonNegError};
22use num::Signed;
23use proptest::prelude::Strategy;
24use prost::UnknownEnumValue;
25
26#[cfg(feature = "chrono")]
27pub mod chrono;
28
29include!(concat!(env!("OUT_DIR"), "/mz_proto.rs"));
30
31/// An error thrown when trying to convert from a `*.proto`-generated type
32/// `Proto$T` to `$T`.
33#[derive(Debug)]
34pub enum TryFromProtoError {
35    /// A wrapped [`TryFromIntError`] due to failed integer downcast.
36    TryFromIntError(TryFromIntError),
37    /// A wrapped [`NonNegError`] due to non-negative invariant being violated.
38    NonNegError(NonNegError),
39    /// A wrapped [`CharTryFromError`] due to failed [`char`] conversion.
40    CharTryFromError(CharTryFromError),
41    /// A date conversion failed
42    DateConversionError(String),
43    /// A regex compilation failed
44    RegexError(regex::Error),
45    /// A mz_repr::Row conversion failed
46    RowConversionError(String),
47    /// A JSON deserialization failed.
48    /// TODO: Remove this when we have complete coverage for source and sink structs.
49    DeserializationError(serde_json::Error),
50    /// Indicates an `Option<U>` field in the `Proto$T` that should be set,
51    /// but for some reason it is not. In practice this should never occur.
52    MissingField(String),
53    /// Indicates an unknown enum variant in `Proto$T`.
54    UnknownEnumVariant(String),
55    /// Indicates that the serialized ShardId value failed to deserialize, according
56    /// to its custom deserialization logic.
57    InvalidShardId(String),
58    /// Indicates that the serialized persist state declared a codec different
59    /// than the one declared in the state.
60    CodecMismatch(String),
61    /// Indicates that the serialized persist state being decoded was internally inconsistent.
62    InvalidPersistState(String),
63    /// Failed to parse a semver::Version.
64    InvalidSemverVersion(String),
65    /// Failed to parse a serialized URI
66    InvalidUri(http::uri::InvalidUri),
67    /// Failed to read back a serialized Glob
68    GlobError(globset::Error),
69    /// Failed to parse a serialized URL
70    InvalidUrl(url::ParseError),
71    /// Failed to parse bitflags.
72    InvalidBitFlags(String),
73    /// Failed to deserialize a LIKE/ILIKE pattern.
74    LikePatternDeserializationError(String),
75    /// A field represented invalid semantics.
76    InvalidFieldError(String),
77}
78
79impl TryFromProtoError {
80    /// Construct a new [`TryFromProtoError::MissingField`] instance.
81    pub fn missing_field<T: ToString>(s: T) -> TryFromProtoError {
82        TryFromProtoError::MissingField(s.to_string())
83    }
84
85    /// Construct a new [`TryFromProtoError::UnknownEnumVariant`] instance.
86    pub fn unknown_enum_variant<T: ToString>(s: T) -> TryFromProtoError {
87        TryFromProtoError::UnknownEnumVariant(s.to_string())
88    }
89}
90
91impl From<TryFromIntError> for TryFromProtoError {
92    fn from(error: TryFromIntError) -> Self {
93        TryFromProtoError::TryFromIntError(error)
94    }
95}
96
97impl From<NonNegError> for TryFromProtoError {
98    fn from(error: NonNegError) -> Self {
99        TryFromProtoError::NonNegError(error)
100    }
101}
102
103impl From<CharTryFromError> for TryFromProtoError {
104    fn from(error: CharTryFromError) -> Self {
105        TryFromProtoError::CharTryFromError(error)
106    }
107}
108
109impl From<UnknownEnumValue> for TryFromProtoError {
110    fn from(UnknownEnumValue(n): UnknownEnumValue) -> Self {
111        TryFromProtoError::UnknownEnumVariant(format!("value {n}"))
112    }
113}
114
115// These From impls pull a bunch of deps into this crate that are otherwise
116// unnecessary. Are they worth it?
117
118impl From<regex::Error> for TryFromProtoError {
119    fn from(error: regex::Error) -> Self {
120        TryFromProtoError::RegexError(error)
121    }
122}
123
124impl From<serde_json::Error> for TryFromProtoError {
125    fn from(error: serde_json::Error) -> Self {
126        TryFromProtoError::DeserializationError(error)
127    }
128}
129
130impl From<http::uri::InvalidUri> for TryFromProtoError {
131    fn from(error: http::uri::InvalidUri) -> Self {
132        TryFromProtoError::InvalidUri(error)
133    }
134}
135
136impl From<globset::Error> for TryFromProtoError {
137    fn from(error: globset::Error) -> Self {
138        TryFromProtoError::GlobError(error)
139    }
140}
141
142impl From<url::ParseError> for TryFromProtoError {
143    fn from(error: url::ParseError) -> Self {
144        TryFromProtoError::InvalidUrl(error)
145    }
146}
147
148impl std::fmt::Display for TryFromProtoError {
149    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150        use TryFromProtoError::*;
151        match self {
152            TryFromIntError(error) => error.fmt(f),
153            NonNegError(error) => error.fmt(f),
154            CharTryFromError(error) => error.fmt(f),
155            DateConversionError(msg) => write!(f, "Date conversion failed: `{}`", msg),
156            RegexError(error) => error.fmt(f),
157            DeserializationError(error) => error.fmt(f),
158            RowConversionError(msg) => write!(f, "Row packing failed: `{}`", msg),
159            MissingField(field) => write!(f, "Missing value for `{}`", field),
160            UnknownEnumVariant(field) => write!(f, "Unknown enum value for `{}`", field),
161            InvalidShardId(value) => write!(f, "Invalid value of ShardId found: `{}`", value),
162            CodecMismatch(error) => error.fmt(f),
163            InvalidPersistState(error) => error.fmt(f),
164            InvalidSemverVersion(error) => error.fmt(f),
165            InvalidUri(error) => error.fmt(f),
166            GlobError(error) => error.fmt(f),
167            InvalidUrl(error) => error.fmt(f),
168            InvalidBitFlags(error) => error.fmt(f),
169            LikePatternDeserializationError(inner_error) => write!(
170                f,
171                "Protobuf deserialization failed for a LIKE/ILIKE pattern: `{}`",
172                inner_error
173            ),
174            InvalidFieldError(error) => error.fmt(f),
175        }
176    }
177}
178
179/// Allow `?` operator on `Result<_, TryFromProtoError>` in contexts
180/// where the error type is a `String`.
181impl From<TryFromProtoError> for String {
182    fn from(error: TryFromProtoError) -> Self {
183        error.to_string()
184    }
185}
186
187impl std::error::Error for TryFromProtoError {
188    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
189        use TryFromProtoError::*;
190        match self {
191            TryFromIntError(error) => Some(error),
192            NonNegError(error) => Some(error),
193            CharTryFromError(error) => Some(error),
194            RegexError(error) => Some(error),
195            DeserializationError(error) => Some(error),
196            DateConversionError(_) => None,
197            RowConversionError(_) => None,
198            MissingField(_) => None,
199            UnknownEnumVariant(_) => None,
200            InvalidShardId(_) => None,
201            CodecMismatch(_) => None,
202            InvalidPersistState(_) => None,
203            InvalidSemverVersion(_) => None,
204            InvalidUri(error) => Some(error),
205            GlobError(error) => Some(error),
206            InvalidUrl(error) => Some(error),
207            InvalidBitFlags(_) => None,
208            LikePatternDeserializationError(_) => None,
209            InvalidFieldError(_) => None,
210        }
211    }
212}
213
214/// A trait that declares that `Self::Proto` is the default
215/// Protobuf representation for `Self`.
216pub trait ProtoRepr: Sized + RustType<Self::Proto> {
217    type Proto: ::prost::Message;
218}
219
220/// A trait for representing a Rust type `Self` as a value of
221/// type `Proto` for the purpose of serializing this
222/// value as (part of) a Protobuf message.
223///
224/// To encode a value, use [`RustType::into_proto()`] (which
225/// should always be an infallible conversion).
226///
227/// To decode a value, use the fallible [`RustType::from_proto()`].
228/// Since the representation type can be "bigger" than the original,
229/// decoding may fail, indicated by returning a [`TryFromProtoError`]
230/// wrapped in a [`Result::Err`].
231///
232/// Convenience syntax for the above methods is available from the
233/// matching [`ProtoType`].
234pub trait RustType<Proto>: Sized {
235    /// Convert a `Self` into a `Proto` value.
236    fn into_proto(&self) -> Proto;
237
238    /// A zero clone version of [`Self::into_proto`] that types can
239    /// optionally implement, otherwise, the default implementation
240    /// delegates to [`Self::into_proto`].
241    fn into_proto_owned(self) -> Proto {
242        self.into_proto()
243    }
244
245    /// Consume and convert a `Proto` back into a `Self` value.
246    ///
247    /// Since `Proto` can be "bigger" than the original, this
248    /// may fail, indicated by returning a [`TryFromProtoError`]
249    /// wrapped in a [`Result::Err`].
250    fn from_proto(proto: Proto) -> Result<Self, TryFromProtoError>;
251}
252
253/// A trait that allows `Self` to be used as an entry in a
254/// `Vec<Self>` representing a Rust `*Map<K, V>`.
255pub trait ProtoMapEntry<K, V> {
256    fn from_rust<'a>(entry: (&'a K, &'a V)) -> Self;
257    fn into_rust(self) -> Result<(K, V), TryFromProtoError>;
258}
259
260macro_rules! rust_type_id(
261    ($($t:ty),*) => (
262        $(
263            /// Identity type for $t.
264            impl RustType<$t> for $t {
265                #[inline]
266                fn into_proto(&self) -> $t {
267                    self.clone()
268                }
269
270                #[inline]
271                fn from_proto(proto: $t) -> Result<Self, TryFromProtoError> {
272                    Ok(proto)
273                }
274            }
275        )+
276    );
277);
278
279rust_type_id![bool, f32, f64, i32, i64, String, u32, u64, Vec<u8>];
280
281impl RustType<u64> for Option<NonZeroU64> {
282    fn into_proto(&self) -> u64 {
283        match self {
284            Some(d) => d.get(),
285            None => 0,
286        }
287    }
288
289    fn from_proto(proto: u64) -> Result<Self, TryFromProtoError> {
290        Ok(NonZeroU64::new(proto)) // 0 is correctly mapped to None
291    }
292}
293
294impl RustType<i64> for Overflowing<i64> {
295    #[inline(always)]
296    fn into_proto(&self) -> i64 {
297        self.into_inner()
298    }
299
300    #[inline(always)]
301    fn from_proto(proto: i64) -> Result<Self, TryFromProtoError> {
302        Ok(proto.into())
303    }
304}
305
306/// Blanket implementation for `BTreeMap<K, V>` where there exists `T` such
307/// that `T` implements `ProtoMapEntry<K, V>`.
308impl<K, V, T> RustType<Vec<T>> for BTreeMap<K, V>
309where
310    K: std::cmp::Eq + std::cmp::Ord,
311    T: ProtoMapEntry<K, V>,
312{
313    fn into_proto(&self) -> Vec<T> {
314        self.iter().map(T::from_rust).collect()
315    }
316
317    fn from_proto(proto: Vec<T>) -> Result<Self, TryFromProtoError> {
318        proto
319            .into_iter()
320            .map(T::into_rust)
321            .collect::<Result<BTreeMap<_, _>, _>>()
322    }
323}
324
325/// Blanket implementation for `BTreeSet<R>` where `R` is a [`RustType`].
326impl<R, P> RustType<Vec<P>> for BTreeSet<R>
327where
328    R: RustType<P> + std::cmp::Ord,
329{
330    fn into_proto(&self) -> Vec<P> {
331        self.iter().map(R::into_proto).collect()
332    }
333
334    fn from_proto(proto: Vec<P>) -> Result<Self, TryFromProtoError> {
335        proto
336            .into_iter()
337            .map(R::from_proto)
338            .collect::<Result<BTreeSet<_>, _>>()
339    }
340}
341
342/// Blanket implementation for `Vec<R>` where `R` is a [`RustType`].
343impl<R, P> RustType<Vec<P>> for Vec<R>
344where
345    R: RustType<P>,
346{
347    fn into_proto(&self) -> Vec<P> {
348        self.iter().map(R::into_proto).collect()
349    }
350
351    fn from_proto(proto: Vec<P>) -> Result<Self, TryFromProtoError> {
352        proto.into_iter().map(R::from_proto).collect()
353    }
354}
355
356/// Blanket implementation for `Box<[R]>` where `R` is a [`RustType`].
357impl<R, P> RustType<Vec<P>> for Box<[R]>
358where
359    R: RustType<P>,
360{
361    fn into_proto(&self) -> Vec<P> {
362        self.iter().map(R::into_proto).collect()
363    }
364
365    fn from_proto(proto: Vec<P>) -> Result<Self, TryFromProtoError> {
366        proto.into_iter().map(R::from_proto).collect()
367    }
368}
369
370/// Blanket implementation for `Option<R>` where `R` is a [`RustType`].
371impl<R, P> RustType<Option<P>> for Option<R>
372where
373    R: RustType<P>,
374{
375    fn into_proto(&self) -> Option<P> {
376        self.as_ref().map(R::into_proto)
377    }
378
379    fn from_proto(proto: Option<P>) -> Result<Self, TryFromProtoError> {
380        proto.map(R::from_proto).transpose()
381    }
382}
383
384/// Blanket implementation for `Box<R>` where `R` is a [`RustType`].
385impl<R, P> RustType<Box<P>> for Box<R>
386where
387    R: RustType<P>,
388{
389    fn into_proto(&self) -> Box<P> {
390        Box::new((**self).into_proto())
391    }
392
393    fn from_proto(proto: Box<P>) -> Result<Self, TryFromProtoError> {
394        (*proto).into_rust().map(Box::new)
395    }
396}
397
398impl<R, P> RustType<P> for Arc<R>
399where
400    R: RustType<P>,
401{
402    fn into_proto(&self) -> P {
403        (**self).into_proto()
404    }
405
406    fn from_proto(proto: P) -> Result<Self, TryFromProtoError> {
407        proto.into_rust().map(Arc::new)
408    }
409}
410
411impl<R1, R2, P1, P2> RustType<(P1, P2)> for (R1, R2)
412where
413    R1: RustType<P1>,
414    R2: RustType<P2>,
415{
416    fn into_proto(&self) -> (P1, P2) {
417        (self.0.into_proto(), self.1.into_proto())
418    }
419
420    fn from_proto(proto: (P1, P2)) -> Result<Self, TryFromProtoError> {
421        let first = proto.0.into_rust()?;
422        let second = proto.1.into_rust()?;
423
424        Ok((first, second))
425    }
426}
427
428impl RustType<()> for () {
429    fn into_proto(&self) -> () {
430        *self
431    }
432
433    fn from_proto(proto: ()) -> Result<Self, TryFromProtoError> {
434        Ok(proto)
435    }
436}
437
438impl RustType<u64> for usize {
439    fn into_proto(&self) -> u64 {
440        u64::cast_from(*self)
441    }
442
443    fn from_proto(proto: u64) -> Result<Self, TryFromProtoError> {
444        usize::try_from(proto).map_err(TryFromProtoError::from)
445    }
446}
447
448impl RustType<u32> for char {
449    fn into_proto(&self) -> u32 {
450        (*self).into()
451    }
452
453    fn from_proto(proto: u32) -> Result<Self, TryFromProtoError> {
454        char::try_from(proto).map_err(TryFromProtoError::from)
455    }
456}
457
458impl RustType<u32> for u8 {
459    fn into_proto(&self) -> u32 {
460        u32::from(*self)
461    }
462
463    fn from_proto(proto: u32) -> Result<Self, TryFromProtoError> {
464        u8::try_from(proto).map_err(TryFromProtoError::from)
465    }
466}
467
468impl RustType<u32> for u16 {
469    fn into_proto(&self) -> u32 {
470        u32::from(*self)
471    }
472
473    fn from_proto(repr: u32) -> Result<Self, TryFromProtoError> {
474        u16::try_from(repr).map_err(TryFromProtoError::from)
475    }
476}
477
478impl RustType<i32> for i8 {
479    fn into_proto(&self) -> i32 {
480        i32::from(*self)
481    }
482
483    fn from_proto(proto: i32) -> Result<Self, TryFromProtoError> {
484        i8::try_from(proto).map_err(TryFromProtoError::from)
485    }
486}
487
488impl RustType<i32> for i16 {
489    fn into_proto(&self) -> i32 {
490        i32::from(*self)
491    }
492
493    fn from_proto(repr: i32) -> Result<Self, TryFromProtoError> {
494        i16::try_from(repr).map_err(TryFromProtoError::from)
495    }
496}
497
498impl RustType<u64> for std::num::NonZeroUsize {
499    fn into_proto(&self) -> u64 {
500        usize::from(*self).into_proto()
501    }
502
503    fn from_proto(proto: u64) -> Result<Self, TryFromProtoError> {
504        Ok(usize::from_proto(proto)?.try_into()?)
505    }
506}
507
508impl<T> RustType<T> for NonNeg<T>
509where
510    T: Clone + Signed + fmt::Display,
511{
512    fn into_proto(&self) -> T {
513        (**self).clone()
514    }
515
516    fn from_proto(proto: T) -> Result<Self, TryFromProtoError> {
517        Ok(NonNeg::<T>::try_from(proto)?)
518    }
519}
520
521impl RustType<ProtoDuration> for std::time::Duration {
522    fn into_proto(&self) -> ProtoDuration {
523        ProtoDuration {
524            secs: self.as_secs(),
525            nanos: self.subsec_nanos(),
526        }
527    }
528
529    fn from_proto(proto: ProtoDuration) -> Result<Self, TryFromProtoError> {
530        Ok(std::time::Duration::new(proto.secs, proto.nanos))
531    }
532}
533
534impl<'a> RustType<String> for Cow<'a, str> {
535    fn into_proto(&self) -> String {
536        self.to_string()
537    }
538    fn from_proto(proto: String) -> Result<Self, TryFromProtoError> {
539        Ok(Cow::Owned(proto))
540    }
541}
542
543impl RustType<String> for Box<str> {
544    fn into_proto(&self) -> String {
545        self.to_string()
546    }
547    fn from_proto(proto: String) -> Result<Self, TryFromProtoError> {
548        Ok(proto.into())
549    }
550}
551
552/// The symmetric counterpart of [`RustType`], similar to
553/// what [`Into`] is to [`From`].
554///
555/// The `Rust` parameter is generic, as opposed to the `Proto`
556/// associated type in [`RustType`] because the same Protobuf type
557/// can be used to encode many different Rust types.
558///
559/// Clients should only implement [`RustType`].
560pub trait ProtoType<Rust>: Sized {
561    /// See [`RustType::from_proto`].
562    fn into_rust(self) -> Result<Rust, TryFromProtoError>;
563
564    /// See [`RustType::into_proto`].
565    fn from_rust(rust: &Rust) -> Self;
566}
567
568/// Blanket implementation for [`ProtoType`], so clients only need
569/// to implement [`RustType`].
570impl<P, R> ProtoType<R> for P
571where
572    R: RustType<P>,
573{
574    #[inline]
575    fn into_rust(self) -> Result<R, TryFromProtoError> {
576        R::from_proto(self)
577    }
578
579    #[inline]
580    fn from_rust(rust: &R) -> Self {
581        R::into_proto(rust)
582    }
583}
584
585pub fn any_duration() -> impl Strategy<Value = std::time::Duration> {
586    (0..u64::MAX, 0..1_000_000_000u32)
587        .prop_map(|(secs, nanos)| std::time::Duration::new(secs, nanos))
588}
589
590/// Convenience syntax for trying to convert a `Self` value of type
591/// `Option<U>` to `T` if the value is `Some(value)`, or returning
592/// [`TryFromProtoError::MissingField`] if the value is `None`.
593pub trait IntoRustIfSome<T> {
594    fn into_rust_if_some<S: ToString>(self, field: S) -> Result<T, TryFromProtoError>;
595}
596
597/// A blanket implementation for `Option<U>` where `U` is the
598/// `RustType::Proto` type for `T`.
599impl<R, P> IntoRustIfSome<R> for Option<P>
600where
601    R: RustType<P>,
602{
603    fn into_rust_if_some<S: ToString>(self, field: S) -> Result<R, TryFromProtoError> {
604        R::from_proto(self.ok_or_else(|| TryFromProtoError::missing_field(field))?)
605    }
606}
607
608/// Convenience syntax for trying to convert a `Self` value of type
609/// `Option<U>` to `T` if the value is `Some(value)`, or returning
610/// [`TryFromProtoError::MissingField`] if the value is `None`.
611pub trait TryIntoIfSome<T> {
612    fn try_into_if_some<S: ToString>(self, field: S) -> Result<T, TryFromProtoError>;
613}
614
615/// A blanket implementation for `Option<U>` where `U` is the
616/// `Proto$T` type for `T`.
617impl<T, U> TryIntoIfSome<T> for Option<U>
618where
619    T: TryFrom<U, Error = TryFromProtoError>,
620{
621    fn try_into_if_some<S: ToString>(self, field: S) -> Result<T, TryFromProtoError> {
622        self.ok_or_else(|| TryFromProtoError::missing_field(field))?
623            .try_into()
624    }
625}
626
627/// Blanket command for testing if `R` can be converted to its corresponding
628/// `ProtoType` and back.
629pub fn protobuf_roundtrip<R, P>(val: &R) -> anyhow::Result<R>
630where
631    P: ProtoType<R> + ::prost::Message + Default,
632{
633    let vec = P::from_rust(val).encode_to_vec();
634    let val = P::decode(&*vec)?.into_rust()?;
635    Ok(val)
636}
637
638#[cfg(test)]
639mod tests {
640    use mz_ore::assert_ok;
641    use proptest::prelude::*;
642
643    use super::*;
644
645    proptest! {
646        #![proptest_config(ProptestConfig::with_cases(4096))]
647
648        #[mz_ore::test]
649        #[cfg_attr(miri, ignore)] // too slow
650        fn duration_protobuf_roundtrip(expect in any_duration() ) {
651            let actual = protobuf_roundtrip::<_, ProtoDuration>(&expect);
652            assert_ok!(actual);
653            assert_eq!(actual.unwrap(), expect);
654        }
655    }
656}