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