prost_convert/
lib.rs

1//! Traits for conversions between native and proto types.
2//!
3
4// TODO: change it to `warn(..)` if we go open source. Indeed `deny(..)` could break user code if it uses a
5// newer version of rust with new warnings)
6#![deny(
7    clippy::all,
8    clippy::cargo,
9    missing_docs,
10    missing_debug_implementations,
11    rust_2018_idioms,
12    unreachable_pub
13)]
14// FIXME: upgrade syn to 2.0
15#![allow(clippy::multiple_crate_versions)]
16
17use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
18use std::num::TryFromIntError;
19use std::path::PathBuf;
20use std::{collections::HashMap, net::IpAddr};
21
22/// Used to do value-to-value conversions while consuming the input value. It is the reciprocal of
23/// [`IntoProto`].
24///
25/// One should always prefer implementing `FromNative` over [`IntoProto`]
26/// because implementing `FromNative` automatically provides one with an implementation of [`IntoProto`]
27/// thanks to the blanket implementation in this crate.
28///
29/// This can't failed because every prost type is just a subset of the native one.
30///
31/// # Generic Implementations
32///
33/// - `FromNative<T> for U` implies [`IntoProto`]`<U> for T`
34///
35/// You should probabily use the derive macro to impl `FromNative<P>`
36pub trait FromNative<N>: Sized {
37    /// Performs the conversion.
38    fn from_native(value: N) -> Self;
39}
40
41/// A value-to-value conversion that consumes the input value. The
42/// opposite of [`FromNative`].
43///
44/// One should avoid implementing `IntoProto` and implement [`FromNative`] instead.
45/// Implementing [`FromNative`] automatically provides one with an implementation of `IntoProto`
46/// thanks to the blanket implementation in the standard library.
47///
48/// Prefer using `IntoProto` over [`FromNative`] when specifying trait bounds on a generic function
49/// to ensure that types that only implement `IntoProto` can be used as well.
50///
51///
52/// # Generic Implementations
53///
54/// - [`FromNative`]`<T> for U` implies `IntoProto<U> for T`
55pub trait IntoProto<P>: Sized {
56    /// Performs the conversion.
57    fn into_proto(self) -> P;
58}
59
60/// Simple and safe type conversions that may fail in a controlled
61/// way under some circumstances. It is the reciprocal of [`TryIntoNative`].
62///
63/// This is useful when you are doing a type conversion that may
64/// trivially succeed but may also need special handling.
65/// For example, the proto struct may have an `Option` field that is required
66/// in the native side.
67///
68/// # Generic Implementations
69///
70/// - `TryFromProto<T> for U` implies [`TryIntoNative`]`<U> for T`
71///
72/// You should probabily use the derive macro to impl `TryFromProto<P>`
73pub trait TryFromProto<P>: Sized {
74    /// Performs the conversion.
75    fn try_from_proto(value: P) -> Result<Self, ProstConvertError>;
76}
77
78/// An attempted conversion that consumes `self`, which may or may not be
79/// expensive.
80///
81/// Library authors should usually not directly implement this trait,
82/// but should prefer implementing the [`TryFromProto`] trait, which offers
83/// greater flexibility and provides an equivalent `TryIntoNative`
84/// implementation for free, thanks to a blanket implementation in this
85/// crate.
86pub trait TryIntoNative<N>: Sized {
87    /// Performs the conversion.
88    fn try_into_native(self) -> Result<N, ProstConvertError>;
89}
90
91// FIXME:
92// - if we want user to impl there custom TryFromProto/FromNative
93//   we must add a `dyn Error` Variant. Indeed their convertion function
94//   might return something like "`MyCustomTypeParseError". As we don't know
95//   what this type will be, we can't add a variant for it.
96// - Do we use the Infallible variant?
97#[allow(missing_docs)]
98#[derive(thiserror::Error, Debug)]
99pub enum ProstConvertError {
100    #[error("prost struct miss a required field")]
101    MissingRequiredField,
102    #[error("infallible")]
103    Infallible(#[from] std::convert::Infallible),
104    #[error("invalid ip address")]
105    AddrParseError(#[from] std::net::AddrParseError),
106    #[error("invalid uuid address")]
107    UuidEroor(#[from] uuid::Error),
108    #[error("int convertion error")]
109    TryFromIntError(#[from] TryFromIntError),
110    #[error("try to parse a type and failed")]
111    TypeParseError(#[from] anyhow::Error),
112}
113
114////////////////////////////////////////////////////////////////////////////////
115// GENERIC IMPLS
116////////////////////////////////////////////////////////////////////////////////
117
118// `TryFromProto` implies `TryIntoNative`.
119impl<T, U> TryIntoNative<U> for T
120where
121    U: TryFromProto<T>,
122{
123    fn try_into_native(self) -> Result<U, ProstConvertError> {
124        U::try_from_proto(self)
125    }
126}
127
128// `FromNative` implies `IntoProto`.
129impl<T, U> IntoProto<U> for T
130where
131    U: FromNative<T>,
132{
133    fn into_proto(self) -> U {
134        U::from_native(self)
135    }
136}
137
138// If the field in proto is optional but not the native one, we considered it required.
139// If a type T can be created from U, so it can be created from an `Option<U>`.
140impl<T, U> TryFromProto<Option<U>> for T
141where
142    T: TryFromProto<U>,
143{
144    fn try_from_proto(value: Option<U>) -> Result<Self, ProstConvertError> {
145        match value {
146            Some(value) => value.try_into_native(),
147            None => Err(ProstConvertError::MissingRequiredField),
148        }
149    }
150}
151
152////////////////////////////////////////////////////////////////////////////////
153// CONCRETE IMPLS
154////////////////////////////////////////////////////////////////////////////////
155
156// If the native type T can be easily convert into an `Option<T>`
157// if required by the proto.
158impl<T, U> FromNative<U> for Option<T>
159where
160    T: FromNative<U>,
161{
162    fn from_native(value: U) -> Self {
163        Some(value.into_proto())
164    }
165}
166
167// Make this trait usable recursively on vectors.
168impl<T, U> TryFromProto<Vec<U>> for Vec<T>
169where
170    T: TryFromProto<U>,
171{
172    fn try_from_proto(value: Vec<U>) -> Result<Self, ProstConvertError> {
173        let mut native = Vec::with_capacity(value.len());
174        for element in value {
175            native.push(element.try_into_native()?)
176        }
177        Ok(native)
178    }
179}
180
181// Make this trait usable recursively on vectors.
182impl<T, U> FromNative<Vec<U>> for Vec<T>
183where
184    T: FromNative<U>,
185{
186    fn from_native(value: Vec<U>) -> Self {
187        let mut proto = Vec::new();
188        for element in value {
189            proto.push(element.into_proto());
190        }
191        proto
192    }
193}
194
195/// We provide an implementation for all prost scalar value.
196/// <https://github.com/tokio-rs/prost#scalar-values>
197macro_rules! impl_scalar {
198    ( $($t:ty),* ) => {
199        $(
200            impl TryFromProto<$t> for $t {
201                fn try_from_proto(value: $t) -> Result<Self, ProstConvertError> {
202                    Ok(value)
203                }
204            }
205
206            impl FromNative<$t> for $t {
207                fn from_native(value: $t) -> Self {
208                    value
209                }
210            }
211        )*
212
213    };
214}
215
216impl_scalar!(f32, f64, i32, i64, u32, u64, bool, String, Vec<u8>);
217
218macro_rules! impl_map {
219    ( $($t:ty),* ) => {
220
221        $(
222            impl<T, U> TryFromProto<HashMap<$t, U>> for HashMap<$t, T>
223            where
224                T: TryFromProto<U>,
225            {
226                fn try_from_proto(value: HashMap<$t, U>) -> Result<Self, ProstConvertError> {
227                    let mut native = HashMap::with_capacity(value.len());
228                    for (key, value) in value {
229                        native.insert(key, value.try_into_native()?);
230                    }
231                    Ok(native)
232                }
233            }
234
235            impl<T, U> FromNative<HashMap<$t, U>> for HashMap<$t, T>
236            where
237                T: FromNative<U>,
238            {
239                fn from_native(value: HashMap<$t, U>) -> Self {
240                    let mut proto = HashMap::with_capacity(value.len());
241                    for (key, value) in value {
242                        proto.insert(key, value.into_proto());
243                    }
244                    proto
245                }
246            }
247        )*
248    };
249}
250
251// Hashmap key supported by protobuf are only integer or string types
252// https://developers.google.com/protocol-buffers/docs/proto3#maps
253impl_map!(i32, i64, u32, u64, bool, String);
254
255impl FromNative<PathBuf> for String {
256    fn from_native(value: PathBuf) -> Self {
257        value.to_string_lossy().into_owned()
258    }
259}
260
261impl TryFromProto<String> for PathBuf {
262    fn try_from_proto(value: String) -> Result<Self, ProstConvertError> {
263        Ok(value.parse()?)
264    }
265}
266
267impl FromNative<IpAddr> for String {
268    fn from_native(value: IpAddr) -> Self {
269        value.to_string()
270    }
271}
272
273impl TryFromProto<String> for IpAddr {
274    fn try_from_proto(value: String) -> Result<Self, ProstConvertError> {
275        Ok(value.parse()?)
276    }
277}
278
279impl FromNative<Ipv4Addr> for String {
280    fn from_native(value: Ipv4Addr) -> Self {
281        value.to_string()
282    }
283}
284
285impl TryFromProto<String> for Ipv4Addr {
286    fn try_from_proto(value: String) -> Result<Self, ProstConvertError> {
287        Ok(value.parse()?)
288    }
289}
290
291impl FromNative<Ipv6Addr> for String {
292    fn from_native(value: Ipv6Addr) -> Self {
293        value.to_string()
294    }
295}
296
297impl TryFromProto<String> for Ipv6Addr {
298    fn try_from_proto(value: String) -> Result<Self, ProstConvertError> {
299        Ok(value.parse()?)
300    }
301}
302
303impl FromNative<SocketAddr> for String {
304    fn from_native(value: SocketAddr) -> Self {
305        value.to_string()
306    }
307}
308
309impl TryFromProto<String> for SocketAddr {
310    fn try_from_proto(value: String) -> Result<Self, ProstConvertError> {
311        Ok(value.parse()?)
312    }
313}
314
315impl FromNative<()> for () {
316    fn from_native(_: ()) -> Self {}
317}
318
319impl TryFromProto<()> for () {
320    fn try_from_proto(_: ()) -> Result<Self, ProstConvertError> {
321        Ok(())
322    }
323}
324
325// We can't directly define u16 in proto
326impl TryFromProto<u32> for u16 {
327    fn try_from_proto(value: u32) -> Result<Self, ProstConvertError> {
328        Ok(value.try_into()?)
329    }
330}
331
332impl FromNative<u16> for u32 {
333    fn from_native(value: u16) -> Self {
334        value.into()
335    }
336}
337
338// We can't directly define u8 in proto
339impl TryFromProto<u32> for u8 {
340    fn try_from_proto(value: u32) -> Result<Self, ProstConvertError> {
341        Ok(value.try_into()?)
342    }
343}
344
345impl FromNative<u8> for u32 {
346    fn from_native(value: u8) -> Self {
347        value.into()
348    }
349}
350
351// We can't directly define i16 in proto
352impl TryFromProto<i32> for i16 {
353    fn try_from_proto(value: i32) -> Result<Self, ProstConvertError> {
354        Ok(value.try_into()?)
355    }
356}
357
358impl FromNative<i16> for i32 {
359    fn from_native(value: i16) -> Self {
360        value.into()
361    }
362}
363
364// We can't directly define i8 in proto
365impl TryFromProto<i32> for i8 {
366    fn try_from_proto(value: i32) -> Result<Self, ProstConvertError> {
367        Ok(value.try_into()?)
368    }
369}
370
371impl FromNative<i8> for i32 {
372    fn from_native(value: i8) -> Self {
373        value.into()
374    }
375}
376
377// TODO: This should be under feature flag because it add a depency to uuid which is not mandatory in most use cases.
378// Ideally, uuid should have a feature "prost_convert" (like for serde).
379impl FromNative<uuid::Uuid> for String {
380    fn from_native(value: uuid::Uuid) -> Self {
381        value.to_string()
382    }
383}
384
385impl TryFromProto<String> for uuid::Uuid {
386    fn try_from_proto(value: String) -> Result<Self, ProstConvertError> {
387        Ok(value.parse()?)
388    }
389}
390
391// Re-export #[derive(ProstConvert)].
392//
393// The reason re-exporting is not enabled by default is that disabling it would
394// be annoying for crates that provide handwritten impls. They
395// would need to disable default features and then explicitly re-enable std.
396
397#[cfg(feature = "prost-convert-derive")]
398#[doc(hidden)]
399pub use prost_convert_derive::ProstConvert;