Skip to main content

musli_core/
display.rs

1//! Encode a value through [`Display`] and decode it through [`FromStr`].
2//!
3//! This module can be used as a field adapter with
4//! [`#[musli(with = musli::display)]`][with], which makes a field a string on
5//! the wire. Its [`option`] submodule does the same for an `Option<T>` field.
6//!
7//! For a type which should always be a string on the wire, the derives also
8//! accept [`#[musli(display)]`][display] on the type itself.
9//!
10//! Decoding fails with the [`FromStr`] error, formatted through its
11//! [`Display`] implementation, if the string can not be parsed.
12//!
13//! [`Display`]: core::fmt::Display
14//! [with]: https://docs.rs/musli/latest/musli/_help/derives/index.html#musliwith--path
15//! [display]: https://docs.rs/musli/latest/musli/_help/derives/index.html#muslidisplay
16//!
17//! # Examples
18//!
19//! ```
20//! use std::net::Ipv4Addr;
21//!
22//! use musli::{Decode, Encode};
23//!
24//! #[derive(Debug, PartialEq, Encode, Decode)]
25//! struct Host {
26//!     #[musli(with = musli::display)]
27//!     address: Ipv4Addr,
28//!     #[musli(with = musli::display::option)]
29//!     gateway: Option<Ipv4Addr>,
30//! }
31//!
32//! let host = Host {
33//!     address: Ipv4Addr::new(10, 0, 0, 2),
34//!     gateway: None,
35//! };
36//!
37//! let json = musli::json::to_string(&host)?;
38//! assert_eq!(json, r#"{"address":"10.0.0.2","gateway":null}"#);
39//! assert_eq!(musli::json::from_str::<Host>(&json)?, host);
40//!
41//! let error = musli::json::from_str::<Host>(r#"{"address":"10.0.0","gateway":null}"#);
42//! assert!(error.is_err());
43//! # Ok::<_, musli::json::Error>(())
44//! ```
45
46use core::fmt;
47use core::str::FromStr;
48
49use crate::{Context, Decoder, Encoder};
50
51/// Encode `value` as a string through its [`Display`] implementation.
52///
53/// [`Display`]: core::fmt::Display
54///
55/// # Examples
56///
57/// ```
58/// use std::net::Ipv4Addr;
59///
60/// use musli::Encode;
61///
62/// #[derive(Encode)]
63/// struct Host {
64///     #[musli(encode_with = musli::display::encode)]
65///     address: Ipv4Addr,
66/// }
67///
68/// let host = Host { address: Ipv4Addr::LOCALHOST };
69/// assert_eq!(musli::json::to_string(&host)?, r#"{"address":"127.0.0.1"}"#);
70/// # Ok::<_, musli::json::Error>(())
71/// ```
72#[inline]
73pub fn encode<E, T>(value: &T, encoder: E) -> Result<(), E::Error>
74where
75    E: Encoder,
76    T: ?Sized + fmt::Display,
77{
78    encoder.collect_string(value)
79}
80
81/// Decode a string and parse it through [`FromStr`].
82///
83/// A parse error is reported through the decoder's context, formatted through
84/// its [`Display`] implementation.
85///
86/// [`Display`]: core::fmt::Display
87///
88/// # Examples
89///
90/// ```
91/// use std::net::Ipv4Addr;
92///
93/// use musli::Decode;
94///
95/// #[derive(Debug, PartialEq, Decode)]
96/// struct Host {
97///     #[musli(decode_with = musli::display::decode)]
98///     address: Ipv4Addr,
99/// }
100///
101/// let host: Host = musli::json::from_str(r#"{"address":"127.0.0.1"}"#)?;
102/// assert_eq!(host, Host { address: Ipv4Addr::LOCALHOST });
103/// # Ok::<_, musli::json::Error>(())
104/// ```
105#[inline]
106pub fn decode<'de, D, T>(decoder: D) -> Result<T, D::Error>
107where
108    D: Decoder<'de>,
109    T: FromStr<Err: fmt::Display>,
110{
111    let cx = decoder.cx();
112    decoder.decode_unsized(|string: &str| string.parse().map_err(|error| cx.message(error)))
113}
114
115/// Encode an `Option<T>` through [`Display`] and decode it through [`FromStr`].
116///
117/// The value is encoded as an optional string, so `None` is encoded the same
118/// way as it is for any other `Option<T>`.
119///
120/// [`Display`]: core::fmt::Display
121///
122/// # Examples
123///
124/// ```
125/// use std::net::Ipv4Addr;
126///
127/// use musli::{Decode, Encode};
128///
129/// #[derive(Debug, PartialEq, Encode, Decode)]
130/// struct Host {
131///     #[musli(with = musli::display::option)]
132///     gateway: Option<Ipv4Addr>,
133/// }
134///
135/// let host = Host { gateway: Some(Ipv4Addr::new(10, 0, 0, 1)) };
136/// let json = musli::json::to_string(&host)?;
137/// assert_eq!(json, r#"{"gateway":"10.0.0.1"}"#);
138/// assert_eq!(musli::json::from_str::<Host>(&json)?, host);
139/// # Ok::<_, musli::json::Error>(())
140/// ```
141pub mod option {
142    use core::fmt;
143    use core::str::FromStr;
144
145    use crate::{Decoder, Encoder};
146
147    /// Encode `value` as an optional string through [`Display`].
148    ///
149    /// [`Display`]: core::fmt::Display
150    #[inline]
151    pub fn encode<E, T>(value: &Option<T>, encoder: E) -> Result<(), E::Error>
152    where
153        E: Encoder,
154        T: fmt::Display,
155    {
156        match value {
157            Some(value) => super::encode(value, encoder.encode_some()?),
158            None => encoder.encode_none(),
159        }
160    }
161
162    /// Decode an optional string and parse it through [`FromStr`].
163    #[inline]
164    pub fn decode<'de, D, T>(decoder: D) -> Result<Option<T>, D::Error>
165    where
166        D: Decoder<'de>,
167        T: FromStr<Err: fmt::Display>,
168    {
169        match decoder.decode_option()? {
170            Some(decoder) => Ok(Some(super::decode(decoder)?)),
171            None => Ok(None),
172        }
173    }
174}