| // Licensed to the Apache Software Foundation (ASF) under one | |
| // or more contributor license agreements. See the NOTICE file | |
| // distributed with this work for additional information | |
| // regarding copyright ownership. The ASF licenses this file | |
| // to you under the Apache License, Version 2.0 (the | |
| // "License"); you may not use this file except in compliance | |
| // with the License. You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, | |
| // software distributed under the License is distributed on an | |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| // KIND, either express or implied. See the License for the | |
| // specific language governing permissions and limitations | |
| // under the License. | |
| use humantime::Duration as HumanDuration; | |
| use humantime::format_duration; | |
| use serde::de::{Error as DeError, Visitor}; | |
| use serde::ser::Error as SerError; | |
| use serde::{Deserialize, Deserializer, Serialize, Serializer}; | |
| use std::{ | |
| error::Error, | |
| fmt::{Display, Formatter}, | |
| ops::Add, | |
| str::FromStr, | |
| time::Duration, | |
| }; | |
| pub const SEC_IN_MICRO: u64 = 1_000_000; | |
| /// A struct for representing time durations with various utility functions. | |
| /// | |
| /// This struct wraps `std::time::Duration` and uses the `humantime` crate for parsing and formatting | |
| /// human-readable duration strings. It also implements serialization and deserialization via the `serde` crate. | |
| /// | |
| /// # Example | |
| /// | |
| /// ``` | |
| /// use iggy_common::IggyDuration; | |
| /// use std::str::FromStr; | |
| /// | |
| /// let duration = IggyDuration::from(3661_000_000_u64); // 3661 seconds in microseconds | |
| /// assert_eq!(3661, duration.as_secs()); | |
| /// assert_eq!("1h 1m 1s", duration.as_human_time_string()); | |
| /// assert_eq!("1h 1m 1s", format!("{}", duration)); | |
| /// | |
| /// let duration = IggyDuration::from(0_u64); | |
| /// assert_eq!(0, duration.as_secs()); | |
| /// assert_eq!("0s", duration.as_human_time_string()); | |
| /// assert_eq!("0s", format!("{}", duration)); | |
| /// | |
| /// let duration = IggyDuration::from_str("1h 1m 1s").unwrap(); | |
| /// assert_eq!(3661, duration.as_secs()); | |
| /// assert_eq!("1h 1m 1s", duration.as_human_time_string()); | |
| /// assert_eq!("1h 1m 1s", format!("{}", duration)); | |
| /// | |
| /// let duration = IggyDuration::from_str("unlimited").unwrap(); | |
| /// assert_eq!(0, duration.as_secs()); | |
| /// ``` | |
| #[derive(Debug, Clone, Copy, Eq, PartialEq)] | |
| pub struct IggyDuration { | |
| duration: Duration, | |
| } | |
| impl IggyDuration { | |
| pub const ONE_SECOND: IggyDuration = IggyDuration { | |
| duration: Duration::from_secs(1), | |
| }; | |
| } | |
| impl IggyDuration { | |
| pub fn new(duration: Duration) -> IggyDuration { | |
| IggyDuration { duration } | |
| } | |
| pub fn new_from_secs(secs: u64) -> IggyDuration { | |
| IggyDuration { | |
| duration: Duration::from_secs(secs), | |
| } | |
| } | |
| pub fn as_human_time_string(&self) -> String { | |
| format!("{}", format_duration(self.duration)) | |
| } | |
| pub fn as_secs(&self) -> u32 { | |
| self.duration.as_secs() as u32 | |
| } | |
| pub fn as_secs_f64(&self) -> f64 { | |
| self.duration.as_secs_f64() | |
| } | |
| pub fn as_micros(&self) -> u64 { | |
| self.duration.as_micros() as u64 | |
| } | |
| pub fn get_duration(&self) -> Duration { | |
| self.duration | |
| } | |
| pub fn is_zero(&self) -> bool { | |
| self.duration.is_zero() | |
| } | |
| pub fn abs_diff(&self, other: IggyDuration) -> IggyDuration { | |
| let diff = self.duration.abs_diff(other.duration); | |
| IggyDuration { duration: diff } | |
| } | |
| } | |
| impl FromStr for IggyDuration { | |
| type Err = humantime::DurationError; | |
| fn from_str(s: &str) -> Result<Self, Self::Err> { | |
| let s = &s.to_lowercase(); | |
| if s == "0" || s == "unlimited" || s == "disabled" || s == "none" { | |
| Ok(IggyDuration { | |
| duration: Duration::new(0, 0), | |
| }) | |
| } else { | |
| Ok(IggyDuration { | |
| duration: humantime::parse_duration(s)?, | |
| }) | |
| } | |
| } | |
| } | |
| impl From<Option<u64>> for IggyDuration { | |
| fn from(duration_us: Option<u64>) -> Self { | |
| match duration_us { | |
| Some(value) => IggyDuration { | |
| duration: Duration::from_micros(value), | |
| }, | |
| None => IggyDuration { | |
| duration: Duration::new(0, 0), | |
| }, | |
| } | |
| } | |
| } | |
| impl From<u64> for IggyDuration { | |
| fn from(value: u64) -> Self { | |
| IggyDuration { | |
| duration: Duration::from_micros(value), | |
| } | |
| } | |
| } | |
| impl From<Duration> for IggyDuration { | |
| fn from(duration: Duration) -> Self { | |
| IggyDuration { duration } | |
| } | |
| } | |
| impl From<HumanDuration> for IggyDuration { | |
| fn from(human_duration: HumanDuration) -> Self { | |
| Self { | |
| duration: human_duration.into(), | |
| } | |
| } | |
| } | |
| impl From<IggyDuration> for u64 { | |
| fn from(iggy_duration: IggyDuration) -> u64 { | |
| iggy_duration.duration.as_micros() as u64 | |
| } | |
| } | |
| impl Default for IggyDuration { | |
| fn default() -> Self { | |
| IggyDuration { | |
| duration: Duration::new(0, 0), | |
| } | |
| } | |
| } | |
| impl Display for IggyDuration { | |
| fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | |
| write!(f, "{}", self.as_human_time_string()) | |
| } | |
| } | |
| impl Add for IggyDuration { | |
| type Output = IggyDuration; | |
| fn add(self, rhs: Self) -> Self::Output { | |
| IggyDuration { | |
| duration: self.duration + rhs.duration, | |
| } | |
| } | |
| } | |
| impl Serialize for IggyDuration { | |
| fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | |
| where | |
| S: Serializer, | |
| { | |
| let micros = self.duration.as_micros(); | |
| let micros = u64::try_from(micros).map_err(|_| { | |
| S::Error::custom(format!( | |
| "duration of {micros} microseconds exceeds the {} microseconds the wire format carries", | |
| u64::MAX | |
| )) | |
| })?; | |
| serializer.serialize_u64(micros) | |
| } | |
| } | |
| struct IggyDurationVisitor; | |
| impl<'de> Deserialize<'de> for IggyDuration { | |
| fn deserialize<D>(deserializer: D) -> Result<IggyDuration, D::Error> | |
| where | |
| D: Deserializer<'de>, | |
| { | |
| deserializer.deserialize_u64(IggyDurationVisitor) | |
| } | |
| } | |
| impl Visitor<'_> for IggyDurationVisitor { | |
| type Value = IggyDuration; | |
| fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { | |
| formatter.write_str("a duration in seconds") | |
| } | |
| fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E> | |
| where | |
| E: serde::de::Error, | |
| { | |
| Ok(IggyDuration::new(Duration::from_micros(value))) | |
| } | |
| } | |
| /// A duration that is guaranteed to be at least one microsecond. | |
| /// | |
| /// `IggyDuration::from_str` maps `0`, `none`, `disabled` and `unlimited` to the same | |
| /// zero, so all four are rejected here. Serialization emits whole microseconds, so a | |
| /// shorter duration such as `1ns` is rejected as well. | |
| /// | |
| /// # Example | |
| /// | |
| /// ``` | |
| /// use iggy_common::{IggyDuration, NonZeroIggyDuration, NonZeroDurationError}; | |
| /// use std::str::FromStr; | |
| /// | |
| /// let interval = NonZeroIggyDuration::from_str("1s").unwrap(); | |
| /// assert_eq!(1, interval.as_secs()); | |
| /// assert_eq!("1s", format!("{}", interval)); | |
| /// | |
| /// assert_eq!(Err(NonZeroDurationError::Zero), NonZeroIggyDuration::from_str("none")); | |
| /// assert_eq!( | |
| /// Err(NonZeroDurationError::Zero), | |
| /// NonZeroIggyDuration::try_from(IggyDuration::from(0_u64)), | |
| /// ); | |
| /// assert_eq!( | |
| /// Err(NonZeroDurationError::SubMicrosecond), | |
| /// NonZeroIggyDuration::from_str("1ns"), | |
| /// ); | |
| /// ``` | |
| #[derive(Debug, Clone, Copy, Eq, PartialEq)] | |
| pub struct NonZeroIggyDuration { | |
| duration: IggyDuration, | |
| } | |
| /// The reason a value could not become a `NonZeroIggyDuration`. | |
| #[derive(Debug, Clone, PartialEq)] | |
| pub enum NonZeroDurationError { | |
| /// The value parsed or converted to zero. | |
| Zero, | |
| /// The value is shorter than the one microsecond resolution of the wire format. | |
| SubMicrosecond, | |
| /// The text is not a duration `humantime` understands. | |
| InvalidFormat(humantime::DurationError), | |
| } | |
| impl NonZeroIggyDuration { | |
| pub const ONE_SECOND: NonZeroIggyDuration = NonZeroIggyDuration { | |
| duration: IggyDuration::ONE_SECOND, | |
| }; | |
| pub fn new(duration: Duration) -> Result<Self, NonZeroDurationError> { | |
| IggyDuration::new(duration).try_into() | |
| } | |
| pub fn get(&self) -> IggyDuration { | |
| self.duration | |
| } | |
| pub fn get_duration(&self) -> Duration { | |
| self.duration.get_duration() | |
| } | |
| pub fn as_human_time_string(&self) -> String { | |
| self.duration.as_human_time_string() | |
| } | |
| pub fn as_secs(&self) -> u32 { | |
| self.duration.as_secs() | |
| } | |
| pub fn as_secs_f64(&self) -> f64 { | |
| self.duration.as_secs_f64() | |
| } | |
| pub fn as_micros(&self) -> u64 { | |
| self.duration.as_micros() | |
| } | |
| /// The gap between two non-zero durations is zero when they are equal, so the | |
| /// result is an `IggyDuration`. | |
| pub fn abs_diff(&self, other: NonZeroIggyDuration) -> IggyDuration { | |
| self.duration.abs_diff(other.duration) | |
| } | |
| } | |
| impl Display for NonZeroDurationError { | |
| fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | |
| match self { | |
| NonZeroDurationError::Zero => write!(f, "duration must be greater than zero"), | |
| NonZeroDurationError::SubMicrosecond => { | |
| write!(f, "duration must be at least one microsecond") | |
| } | |
| NonZeroDurationError::InvalidFormat(error) => write!(f, "invalid duration: {error}"), | |
| } | |
| } | |
| } | |
| impl Error for NonZeroDurationError { | |
| fn source(&self) -> Option<&(dyn Error + 'static)> { | |
| match self { | |
| NonZeroDurationError::Zero | NonZeroDurationError::SubMicrosecond => None, | |
| NonZeroDurationError::InvalidFormat(error) => Some(error), | |
| } | |
| } | |
| } | |
| impl From<humantime::DurationError> for NonZeroDurationError { | |
| fn from(error: humantime::DurationError) -> Self { | |
| NonZeroDurationError::InvalidFormat(error) | |
| } | |
| } | |
| impl TryFrom<IggyDuration> for NonZeroIggyDuration { | |
| type Error = NonZeroDurationError; | |
| fn try_from(duration: IggyDuration) -> Result<Self, Self::Error> { | |
| if duration.is_zero() { | |
| return Err(NonZeroDurationError::Zero); | |
| } | |
| // Serialization emits whole microseconds, so a shorter duration would come back as | |
| // zero. `as_micros` truncates to `u64`, so compare the underlying `Duration` instead. | |
| if duration.get_duration() < Duration::from_micros(1) { | |
| return Err(NonZeroDurationError::SubMicrosecond); | |
| } | |
| Ok(NonZeroIggyDuration { duration }) | |
| } | |
| } | |
| impl TryFrom<u64> for NonZeroIggyDuration { | |
| type Error = NonZeroDurationError; | |
| fn try_from(duration_us: u64) -> Result<Self, Self::Error> { | |
| IggyDuration::from(duration_us).try_into() | |
| } | |
| } | |
| impl TryFrom<Duration> for NonZeroIggyDuration { | |
| type Error = NonZeroDurationError; | |
| fn try_from(duration: Duration) -> Result<Self, Self::Error> { | |
| IggyDuration::from(duration).try_into() | |
| } | |
| } | |
| impl TryFrom<HumanDuration> for NonZeroIggyDuration { | |
| type Error = NonZeroDurationError; | |
| fn try_from(duration: HumanDuration) -> Result<Self, Self::Error> { | |
| IggyDuration::from(duration).try_into() | |
| } | |
| } | |
| impl From<NonZeroIggyDuration> for IggyDuration { | |
| fn from(duration: NonZeroIggyDuration) -> Self { | |
| duration.duration | |
| } | |
| } | |
| impl FromStr for NonZeroIggyDuration { | |
| type Err = NonZeroDurationError; | |
| fn from_str(s: &str) -> Result<Self, Self::Err> { | |
| IggyDuration::from_str(s)?.try_into() | |
| } | |
| } | |
| impl Display for NonZeroIggyDuration { | |
| fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | |
| write!(f, "{}", self.duration) | |
| } | |
| } | |
| impl Add for NonZeroIggyDuration { | |
| type Output = NonZeroIggyDuration; | |
| fn add(self, rhs: Self) -> Self::Output { | |
| NonZeroIggyDuration { | |
| duration: self.duration + rhs.duration, | |
| } | |
| } | |
| } | |
| impl Serialize for NonZeroIggyDuration { | |
| fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | |
| where | |
| S: Serializer, | |
| { | |
| self.duration.serialize(serializer) | |
| } | |
| } | |
| struct NonZeroIggyDurationVisitor; | |
| impl<'de> Deserialize<'de> for NonZeroIggyDuration { | |
| fn deserialize<D>(deserializer: D) -> Result<NonZeroIggyDuration, D::Error> | |
| where | |
| D: Deserializer<'de>, | |
| { | |
| deserializer.deserialize_u64(NonZeroIggyDurationVisitor) | |
| } | |
| } | |
| impl Visitor<'_> for NonZeroIggyDurationVisitor { | |
| type Value = NonZeroIggyDuration; | |
| fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { | |
| formatter.write_str("a duration in microseconds greater than zero") | |
| } | |
| fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E> | |
| where | |
| E: DeError, | |
| { | |
| NonZeroIggyDuration::try_from(value).map_err(E::custom) | |
| } | |
| } | |
| #[cfg(test)] | |
| mod tests { | |
| use super::*; | |
| use std::time::Duration; | |
| #[test] | |
| fn test_new() { | |
| let duration = Duration::new(60, 0); // 60 seconds | |
| let iggy_duration = IggyDuration::new(duration); | |
| assert_eq!(iggy_duration.as_secs(), 60); | |
| } | |
| #[test] | |
| fn test_as_human_time_string() { | |
| let duration = Duration::new(3661, 0); // 1 hour, 1 minute and 1 second | |
| let iggy_duration = IggyDuration::new(duration); | |
| assert_eq!(iggy_duration.as_human_time_string(), "1h 1m 1s"); | |
| } | |
| #[test] | |
| fn test_long_duration_as_human_time_string() { | |
| let duration = Duration::new(36611233, 0); // 1year 1month 28days 1hour 13minutes 37seconds | |
| let iggy_duration = IggyDuration::new(duration); | |
| assert_eq!( | |
| iggy_duration.as_human_time_string(), | |
| "1year 1month 28days 1h 13m 37s" | |
| ); | |
| } | |
| #[test] | |
| fn test_from_str() { | |
| let iggy_duration: IggyDuration = "1h 1m 1s".parse().unwrap(); | |
| assert_eq!(iggy_duration.as_secs(), 3661); | |
| } | |
| #[test] | |
| fn test_display() { | |
| let duration = Duration::new(3661, 0); | |
| let iggy_duration = IggyDuration::new(duration); | |
| let duration_string = format!("{iggy_duration}"); | |
| assert_eq!(duration_string, "1h 1m 1s"); | |
| } | |
| #[test] | |
| fn test_invalid_duration() { | |
| let result: Result<IggyDuration, _> = "1 hour and 30 minutes".parse(); | |
| assert!(result.is_err()); | |
| } | |
| #[test] | |
| fn test_zero_seconds_duration() { | |
| let iggy_duration: IggyDuration = "0s".parse().unwrap(); | |
| assert_eq!(iggy_duration.as_secs(), 0); | |
| } | |
| #[test] | |
| fn test_zero_duration() { | |
| let iggy_duration: IggyDuration = "0".parse().unwrap(); | |
| assert_eq!(iggy_duration.as_secs(), 0); | |
| } | |
| #[test] | |
| fn test_unlimited() { | |
| let iggy_duration: IggyDuration = "unlimited".parse().unwrap(); | |
| assert_eq!(iggy_duration.as_secs(), 0); | |
| } | |
| #[test] | |
| fn test_disabled() { | |
| let iggy_duration: IggyDuration = "disabled".parse().unwrap(); | |
| assert_eq!(iggy_duration.as_secs(), 0); | |
| } | |
| #[test] | |
| fn test_add_duration() { | |
| let iggy_duration1: IggyDuration = "6s".parse().unwrap(); | |
| let iggy_duration2: IggyDuration = "1m".parse().unwrap(); | |
| let result: IggyDuration = iggy_duration1 + iggy_duration2; | |
| assert_eq!(result.as_secs(), 66); | |
| } | |
| #[test] | |
| fn given_a_positive_duration_should_convert() { | |
| let duration = NonZeroIggyDuration::try_from(IggyDuration::ONE_SECOND).unwrap(); | |
| assert_eq!(IggyDuration::ONE_SECOND, duration.get()); | |
| assert_eq!(Duration::from_secs(1), duration.get_duration()); | |
| } | |
| #[test] | |
| fn given_a_zero_duration_should_fail_to_convert() { | |
| let error = NonZeroIggyDuration::try_from(IggyDuration::default()).unwrap_err(); | |
| assert_eq!(NonZeroDurationError::Zero, error); | |
| } | |
| #[test] | |
| fn given_a_positive_std_duration_should_build() { | |
| let duration = NonZeroIggyDuration::new(Duration::from_millis(1500)).unwrap(); | |
| assert_eq!(1.5, duration.as_secs_f64()); | |
| } | |
| #[test] | |
| fn given_a_zero_std_duration_should_fail_to_build() { | |
| assert_eq!( | |
| Err(NonZeroDurationError::Zero), | |
| NonZeroIggyDuration::new(Duration::ZERO) | |
| ); | |
| assert_eq!( | |
| Err(NonZeroDurationError::Zero), | |
| NonZeroIggyDuration::try_from(Duration::ZERO) | |
| ); | |
| } | |
| #[test] | |
| fn given_a_human_duration_should_convert() { | |
| let human_duration = HumanDuration::from_str("1m").unwrap(); | |
| let duration = NonZeroIggyDuration::try_from(human_duration).unwrap(); | |
| assert_eq!(60, duration.as_secs()); | |
| } | |
| #[test] | |
| fn given_two_durations_should_report_their_gap() { | |
| let one_minute = NonZeroIggyDuration::from_str("1m").unwrap(); | |
| let six_seconds = NonZeroIggyDuration::from_str("6s").unwrap(); | |
| assert_eq!( | |
| IggyDuration::new_from_secs(54), | |
| one_minute.abs_diff(six_seconds) | |
| ); | |
| assert_eq!(IggyDuration::default(), one_minute.abs_diff(one_minute)); | |
| } | |
| #[test] | |
| fn given_two_durations_should_add_up() { | |
| let sum = NonZeroIggyDuration::from_str("6s").unwrap() | |
| + NonZeroIggyDuration::from_str("1m").unwrap(); | |
| assert_eq!(66, sum.as_secs()); | |
| } | |
| #[test] | |
| fn given_a_zero_alias_should_fail_to_parse() { | |
| for value in ["0", "0s", "none", "disabled", "unlimited"] { | |
| assert_eq!( | |
| Err(NonZeroDurationError::Zero), | |
| NonZeroIggyDuration::from_str(value), | |
| "expected {value} to be rejected" | |
| ); | |
| } | |
| } | |
| #[test] | |
| fn given_a_malformed_value_should_report_the_format_error() { | |
| let error = NonZeroIggyDuration::from_str("1 hour and 30 minutes").unwrap_err(); | |
| assert!(matches!(error, NonZeroDurationError::InvalidFormat(_))); | |
| } | |
| #[test] | |
| fn given_a_human_time_string_should_parse() { | |
| let duration = NonZeroIggyDuration::from_str("1h 1m 1s").unwrap(); | |
| assert_eq!(3661, duration.as_secs()); | |
| assert_eq!("1h 1m 1s", duration.as_human_time_string()); | |
| assert_eq!("1h 1m 1s", format!("{duration}")); | |
| } | |
| #[test] | |
| fn given_microseconds_should_round_trip_through_serde() { | |
| let duration = NonZeroIggyDuration::from_str("500ms").unwrap(); | |
| let serialized = serde_json::to_string(&duration).unwrap(); | |
| assert_eq!("500000", serialized); | |
| assert_eq!( | |
| duration, | |
| serde_json::from_str::<NonZeroIggyDuration>(&serialized).unwrap() | |
| ); | |
| } | |
| #[test] | |
| fn given_a_zero_microsecond_value_should_fail_to_deserialize() { | |
| assert!(serde_json::from_str::<NonZeroIggyDuration>("0").is_err()); | |
| } | |
| #[test] | |
| fn given_a_sub_microsecond_value_should_fail_to_build() { | |
| assert_eq!( | |
| Err(NonZeroDurationError::SubMicrosecond), | |
| NonZeroIggyDuration::from_str("1ns") | |
| ); | |
| assert_eq!( | |
| Err(NonZeroDurationError::SubMicrosecond), | |
| NonZeroIggyDuration::new(Duration::from_nanos(999)) | |
| ); | |
| assert_eq!( | |
| Err(NonZeroDurationError::SubMicrosecond), | |
| NonZeroIggyDuration::try_from(IggyDuration::new(Duration::from_nanos(1))) | |
| ); | |
| } | |
| #[test] | |
| fn given_one_microsecond_should_round_trip_through_serde() { | |
| let duration = NonZeroIggyDuration::from_str("1us").unwrap(); | |
| let serialized = serde_json::to_string(&duration).unwrap(); | |
| assert_eq!("1", serialized); | |
| assert_eq!( | |
| duration, | |
| serde_json::from_str::<NonZeroIggyDuration>(&serialized).unwrap() | |
| ); | |
| } | |
| #[test] | |
| fn given_the_largest_serializable_duration_should_round_trip_through_serde() { | |
| let duration = NonZeroIggyDuration::new(Duration::from_micros(u64::MAX)).unwrap(); | |
| let serialized = serde_json::to_string(&duration).unwrap(); | |
| assert_eq!(u64::MAX.to_string(), serialized); | |
| assert_eq!( | |
| duration, | |
| serde_json::from_str::<NonZeroIggyDuration>(&serialized).unwrap() | |
| ); | |
| } | |
| #[test] | |
| fn given_a_duration_beyond_the_serializable_range_should_build() { | |
| let beyond_range = Duration::from_micros(u64::MAX) + Duration::from_micros(1); | |
| assert!(NonZeroIggyDuration::new(beyond_range).is_ok()); | |
| assert!(NonZeroIggyDuration::new(beyond_range + Duration::from_micros(1)).is_ok()); | |
| } | |
| #[test] | |
| fn given_a_duration_beyond_the_serializable_range_should_fail_to_serialize() { | |
| let beyond_range = Duration::from_micros(u64::MAX) + Duration::from_micros(1); | |
| assert!(serde_json::to_string(&IggyDuration::new(beyond_range)).is_err()); | |
| assert!(serde_json::to_string(&NonZeroIggyDuration::new(beyond_range).unwrap()).is_err()); | |
| } | |
| } |