datetime_string/rfc3339/partial_time/
owned.rs

1//! RFC 3339 [`partial-time`] owned string type.
2//!
3//! [`partial-time`]: https://tools.ietf.org/html/rfc3339#section-5.6
4#![cfg(feature = "alloc")]
5
6use core::{convert::TryFrom, fmt, ops, str};
7
8use alloc::{string::String, vec::Vec};
9
10use crate::{ConversionError, Error};
11
12use super::{validate_bytes, PartialTimeStr};
13
14/// Owned string for a time in RFC 3339 [`partial-time`] format, such as `12:34:56.7890`.
15///
16/// Available when `alloc` feature is enabled.
17///
18/// This is "partial", because it is not associated to a time offset.
19///
20/// To create a value of this type, use [`str::parse`] method or
21/// [`std::convert::TryFrom`] trait, or convert from `&PartialTimeStr`.
22///
23/// # Examples
24///
25/// ```
26/// # use datetime_string::rfc3339::PartialTimeString;
27/// use datetime_string::rfc3339::PartialTimeStr;
28/// use std::convert::TryFrom;
29///
30/// let try_from = PartialTimeString::try_from("12:34:56.7890")?;
31///
32/// let parse = "12:34:56.7890".parse::<PartialTimeString>()?;
33/// let parse2: PartialTimeString = "12:34:56.7890".parse()?;
34///
35/// let to_owned = PartialTimeStr::from_str("12:34:56.7890")?.to_owned();
36/// let into: PartialTimeString = PartialTimeStr::from_str("12:34:56.7890")?.into();
37/// # Ok::<_, datetime_string::Error>(())
38/// ```
39///
40/// [`partial-time`]: https://tools.ietf.org/html/rfc3339#section-5.6
41#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
42#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
43#[repr(transparent)]
44// Note that `derive(Serialize)` cannot used here, because it encodes this as
45// `[u8; 8]` rather than as a string.
46//
47// Comparisons implemented for the type are consistent (at least it is intended to be so).
48// See <https://github.com/rust-lang/rust-clippy/issues/2025>.
49// Note that `clippy::derive_ord_xor_partial_ord` would be introduced since Rust 1.47.0.
50#[allow(clippy::derive_hash_xor_eq)]
51#[allow(unknown_lints, clippy::derive_ord_xor_partial_ord)]
52pub struct PartialTimeString(Vec<u8>);
53
54impl PartialTimeString {
55    /// Creates a `PartialTimeString` from the given bytes.
56    ///
57    /// This performs assertion in debug build, but not in release build.
58    ///
59    /// # Safety
60    ///
61    /// `validate_bytes(&s)` should return `Ok(())`.
62    #[inline]
63    #[must_use]
64    unsafe fn from_bytes_maybe_unchecked(s: Vec<u8>) -> Self {
65        debug_assert_ok!(validate_bytes(&s));
66        Self(s)
67    }
68
69    /// Returns a `&PartialTimeStr` for the string.
70    ///
71    /// # Examples
72    ///
73    /// ```
74    /// # use datetime_string::rfc3339::PartialTimeString;
75    /// use datetime_string::rfc3339::PartialTimeStr;
76    ///
77    /// let time = "12:34:56.7890".parse::<PartialTimeString>()?;
78    ///
79    /// // Usually you don't need to call `as_deref()` explicitly, because
80    /// // `Deref<Target = PartialTimeStr>` trait is implemented.
81    /// let _: &PartialTimeStr = time.as_deref();
82    /// # Ok::<_, datetime_string::Error>(())
83    /// ```
84    #[inline]
85    #[must_use]
86    pub fn as_deref(&self) -> &PartialTimeStr {
87        unsafe {
88            // This is safe because `self.0` is valid partial-time string.
89            debug_assert_safe_version_ok!(PartialTimeStr::from_bytes(&self.0));
90            PartialTimeStr::from_bytes_maybe_unchecked(&self.0)
91        }
92    }
93
94    /// Returns a `&mut PartialTimeStr` for the string.
95    ///
96    /// # Examples
97    ///
98    /// ```
99    /// # use datetime_string::rfc3339::PartialTimeString;
100    /// use datetime_string::rfc3339::PartialTimeStr;
101    ///
102    /// let mut time = "12:34:56.7890".parse::<PartialTimeString>()?;
103    ///
104    /// // Usually you don't need to call `as_deref_mut()` explicitly, because
105    /// // `DerefMut` trait is implemented.
106    /// let _: &mut PartialTimeStr = time.as_deref_mut();
107    /// # Ok::<_, datetime_string::Error>(())
108    /// ```
109    #[inline]
110    #[must_use]
111    pub fn as_deref_mut(&mut self) -> &mut PartialTimeStr {
112        unsafe {
113            // This is safe because `self.0` is valid partial-time string.
114            debug_assert_ok!(PartialTimeStr::from_bytes(&self.0));
115            PartialTimeStr::from_bytes_maybe_unchecked_mut(&mut self.0)
116        }
117    }
118}
119
120impl core::borrow::Borrow<PartialTimeStr> for PartialTimeString {
121    #[inline]
122    fn borrow(&self) -> &PartialTimeStr {
123        self.as_deref()
124    }
125}
126
127impl core::borrow::BorrowMut<PartialTimeStr> for PartialTimeString {
128    #[inline]
129    fn borrow_mut(&mut self) -> &mut PartialTimeStr {
130        self.as_deref_mut()
131    }
132}
133
134impl alloc::borrow::ToOwned for PartialTimeStr {
135    type Owned = PartialTimeString;
136
137    #[inline]
138    fn to_owned(&self) -> Self::Owned {
139        self.into()
140    }
141}
142
143impl AsRef<[u8]> for PartialTimeString {
144    #[inline]
145    fn as_ref(&self) -> &[u8] {
146        self.as_bytes()
147    }
148}
149
150impl AsRef<str> for PartialTimeString {
151    #[inline]
152    fn as_ref(&self) -> &str {
153        self.as_str()
154    }
155}
156
157impl AsRef<PartialTimeStr> for PartialTimeString {
158    #[inline]
159    fn as_ref(&self) -> &PartialTimeStr {
160        self
161    }
162}
163
164impl AsMut<PartialTimeStr> for PartialTimeString {
165    #[inline]
166    fn as_mut(&mut self) -> &mut PartialTimeStr {
167        self
168    }
169}
170
171impl From<PartialTimeString> for Vec<u8> {
172    #[inline]
173    fn from(v: PartialTimeString) -> Vec<u8> {
174        v.0
175    }
176}
177
178impl From<PartialTimeString> for String {
179    #[inline]
180    fn from(v: PartialTimeString) -> String {
181        unsafe {
182            // This is safe because a valid `PartialTimeString` is an ASCII string.
183            debug_assert_ok!(str::from_utf8(&v.0));
184            String::from_utf8_unchecked(v.0)
185        }
186    }
187}
188
189impl From<&PartialTimeStr> for PartialTimeString {
190    fn from(v: &PartialTimeStr) -> Self {
191        unsafe {
192            // This is safe because the value is already validated.
193            debug_assert_ok!(validate_bytes(&v.0));
194            Self::from_bytes_maybe_unchecked(v.0.into())
195        }
196    }
197}
198
199impl TryFrom<&[u8]> for PartialTimeString {
200    type Error = Error;
201
202    #[inline]
203    fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
204        PartialTimeStr::from_bytes(v).map(Into::into)
205    }
206}
207
208impl TryFrom<&str> for PartialTimeString {
209    type Error = Error;
210
211    #[inline]
212    fn try_from(v: &str) -> Result<Self, Self::Error> {
213        PartialTimeStr::from_str(v).map(Into::into)
214    }
215}
216
217impl TryFrom<Vec<u8>> for PartialTimeString {
218    type Error = ConversionError<Vec<u8>>;
219
220    #[inline]
221    fn try_from(v: Vec<u8>) -> Result<Self, Self::Error> {
222        match validate_bytes(&v) {
223            Ok(_) => Ok(unsafe {
224                // This is safe because the value is successfully validated.
225                Self::from_bytes_maybe_unchecked(v)
226            }),
227            Err(e) => Err(ConversionError::new(v, e)),
228        }
229    }
230}
231
232impl TryFrom<String> for PartialTimeString {
233    type Error = ConversionError<String>;
234
235    #[inline]
236    fn try_from(v: String) -> Result<Self, Self::Error> {
237        match validate_bytes(v.as_bytes()) {
238            Ok(_) => Ok(unsafe {
239                // This is safe because the value is successfully validated.
240                Self::from_bytes_maybe_unchecked(v.into_bytes())
241            }),
242            Err(e) => Err(ConversionError::new(v, e)),
243        }
244    }
245}
246
247impl fmt::Display for PartialTimeString {
248    #[inline]
249    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250        self.as_deref().fmt(f)
251    }
252}
253
254impl ops::Deref for PartialTimeString {
255    type Target = PartialTimeStr;
256
257    #[inline]
258    fn deref(&self) -> &Self::Target {
259        self.as_deref()
260    }
261}
262
263impl ops::DerefMut for PartialTimeString {
264    #[inline]
265    fn deref_mut(&mut self) -> &mut Self::Target {
266        self.as_deref_mut()
267    }
268}
269
270impl str::FromStr for PartialTimeString {
271    type Err = Error;
272
273    #[inline]
274    fn from_str(s: &str) -> Result<Self, Self::Err> {
275        Self::try_from(s)
276    }
277}
278
279impl_cmp_symmetric!(PartialTimeStr, PartialTimeString, &PartialTimeString);
280impl_cmp_symmetric!(PartialTimeStr, PartialTimeString, PartialTimeStr);
281impl_cmp_symmetric!(PartialTimeStr, PartialTimeString, &PartialTimeStr);
282impl_cmp_symmetric!(str, PartialTimeString, str);
283impl_cmp_symmetric!(str, PartialTimeString, &str);
284impl_cmp_symmetric!(str, &PartialTimeString, str);
285impl_cmp_symmetric!([u8], PartialTimeString, [u8]);
286impl_cmp_symmetric!([u8], PartialTimeString, &[u8]);
287impl_cmp_symmetric!([u8], &PartialTimeString, [u8]);
288
289#[cfg(feature = "serde")]
290#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
291impl serde::Serialize for PartialTimeString {
292    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
293    where
294        S: serde::Serializer,
295    {
296        serializer.serialize_str(self.as_str())
297    }
298}
299
300/// Items for serde support.
301#[cfg(feature = "serde")]
302mod serde_ {
303    use super::*;
304
305    use serde::de::{Deserialize, Deserializer, Visitor};
306
307    /// Visitor for `PartialTimeString`.
308    struct StringVisitor;
309
310    impl<'de> Visitor<'de> for StringVisitor {
311        type Value = PartialTimeString;
312
313        #[inline]
314        fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315            f.write_str("RFC 3339 partial-time string")
316        }
317
318        #[inline]
319        fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
320        where
321            E: serde::de::Error,
322        {
323            Self::Value::try_from(v).map_err(E::custom)
324        }
325
326        #[inline]
327        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
328        where
329            E: serde::de::Error,
330        {
331            Self::Value::try_from(v).map_err(E::custom)
332        }
333    }
334
335    impl<'de> Deserialize<'de> for PartialTimeString {
336        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
337        where
338            D: Deserializer<'de>,
339        {
340            deserializer.deserialize_any(StringVisitor)
341        }
342    }
343}
344
345#[cfg(feature = "serde")]
346#[cfg(test)]
347mod tests {
348    use super::*;
349
350    use serde_test::{assert_de_tokens, assert_tokens, Token};
351
352    #[test]
353    fn ser_de_string() {
354        let raw: &'static str = "12:34:56.7890";
355        assert_tokens(
356            &PartialTimeString::try_from(raw).unwrap(),
357            &[Token::Str(raw)],
358        );
359    }
360
361    #[test]
362    fn de_bytes() {
363        let raw: &'static [u8; 13] = b"12:34:56.7890";
364        assert_de_tokens(
365            &PartialTimeString::try_from(&raw[..]).unwrap(),
366            &[Token::Bytes(raw)],
367        );
368    }
369}