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}