1use 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#[derive(Debug)]
38pub enum TryFromProtoError {
39 TryFromIntError(TryFromIntError),
41 NonNegError(NonNegError),
43 CharTryFromError(CharTryFromError),
45 DateConversionError(String),
47 RegexError(regex::Error),
49 RowConversionError(String),
51 DeserializationError(serde_json::Error),
54 MissingField(String),
57 UnknownEnumVariant(String),
59 InvalidShardId(String),
62 CodecMismatch(String),
65 InvalidPersistState(String),
67 InvalidSemverVersion(String),
69 InvalidUri(http::uri::InvalidUri),
71 GlobError(globset::Error),
73 InvalidUrl(url::ParseError),
75 InvalidBitFlags(String),
77 LikePatternDeserializationError(String),
79 InvalidFieldError(String),
81}
82
83impl TryFromProtoError {
84 pub fn missing_field<T: ToString>(s: T) -> TryFromProtoError {
86 TryFromProtoError::MissingField(s.to_string())
87 }
88
89 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
119impl 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
183impl 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
222pub trait ProtoRepr: Sized + RustType<Self::Proto> {
225 type Proto: ::prost::Message;
226}
227
228pub trait RustType<Proto>: Sized {
243 fn into_proto(&self) -> Proto;
245
246 fn into_proto_owned(self) -> Proto {
250 self.into_proto()
251 }
252
253 fn from_proto(proto: Proto) -> Result<Self, TryFromProtoError>;
259}
260
261pub 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 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)) }
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
314impl<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
333impl<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
350impl<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
364impl<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
378impl<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
392impl<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 #[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
584pub trait ProtoType<Rust>: Sized {
593 fn into_rust(self) -> Result<Rust, TryFromProtoError>;
595
596 fn from_rust(rust: &Rust) -> Self;
598}
599
600impl<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
622pub trait IntoRustIfSome<T> {
626 fn into_rust_if_some<S: ToString>(self, field: S) -> Result<T, TryFromProtoError>;
627}
628
629impl<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
640pub trait TryIntoIfSome<T> {
644 fn try_into_if_some<S: ToString>(self, field: S) -> Result<T, TryFromProtoError>;
645}
646
647impl<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
659pub 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)] 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}