smart_config/de/
units.rs

1//! Param deserializers based on units of measurement.
2
3use std::{fmt, marker::PhantomData, str::FromStr, time::Duration};
4
5use serde::{
6    Deserialize, Deserializer,
7    de::{self, EnumAccess, Error as DeError, VariantAccess},
8};
9
10use crate::{
11    ByteSize, EtherAmount,
12    de::{CustomKnownOption, DeserializeContext, DeserializeParam, Optional, WellKnown},
13    error::ErrorWithOrigin,
14    metadata::{BasicTypes, ParamMetadata, SizeUnit, TimeUnit, TypeDescription, TypeSuffixes},
15    utils::{Decimal, FromStrStart},
16    value::Value,
17};
18
19impl TimeUnit {
20    fn overflow_err(self, raw_val: Decimal) -> serde_json::Error {
21        let plural = self.plural();
22        DeError::custom(format!(
23            "{raw_val} {plural} does not fit into `u64` when converted to milliseconds"
24        ))
25    }
26
27    fn into_duration(self, raw_value: Decimal) -> Result<Duration, serde_json::Error> {
28        let millis_in_unit = match self {
29            Self::Millis => Decimal::from(1),
30            Self::Seconds => Decimal::new(1, 3),
31            Self::Minutes => Decimal::new(60, 3),
32            Self::Hours => Decimal::new(3_600, 3),
33            Self::Days => Decimal::new(86_400, 3),
34            Self::Weeks => Decimal::new(7 * 86_400, 3),
35        };
36        let millis = raw_value
37            .checked_mul(millis_in_unit)
38            .ok_or_else(|| self.overflow_err(raw_value))?;
39        let millis = millis
40            .to_int()
41            .ok_or_else(|| self.overflow_err(raw_value))?;
42
43        u64::try_from(millis)
44            .map(Duration::from_millis)
45            .or_else(|_| {
46                // Try converting to seconds to cover a wider range of values. Losing precision
47                // in subsecond millis is not a concern for such large values.
48                let secs =
49                    u64::try_from(millis / 1_000).map_err(|_| self.overflow_err(raw_value))?;
50                Ok(Duration::from_secs(secs))
51            })
52    }
53}
54
55/// Supports deserializing a [`Duration`] from a number, with `self` being the unit of measurement.
56///
57/// # Examples
58///
59/// ```
60/// # use std::time::Duration;
61/// # use smart_config::{metadata::TimeUnit, DescribeConfig, DeserializeConfig};
62/// use smart_config::testing;
63///
64/// #[derive(DescribeConfig, DeserializeConfig)]
65/// struct TestConfig {
66///     #[config(with = TimeUnit::Millis)]
67///     time_ms: Duration,
68/// }
69///
70/// let source = smart_config::config!("time_ms": 100);
71/// let config = testing::test::<TestConfig>(source)?;
72/// assert_eq!(config.time_ms, Duration::from_millis(100));
73/// # anyhow::Ok(())
74/// ```
75impl DeserializeParam<Duration> for TimeUnit {
76    const EXPECTING: BasicTypes = BasicTypes::INTEGER;
77
78    fn describe(&self, description: &mut TypeDescription) {
79        description
80            .set_details("time duration")
81            .set_unit((*self).into());
82    }
83
84    fn deserialize_param(
85        &self,
86        ctx: DeserializeContext<'_>,
87        param: &'static ParamMetadata,
88    ) -> Result<Duration, ErrorWithOrigin> {
89        let deserializer = ctx.current_value_deserializer(param.name)?;
90        let raw_value = Decimal::deserialize(deserializer)?;
91        self.into_duration(raw_value)
92            .map_err(|err| deserializer.enrich_err(err))
93    }
94
95    fn serialize_param(&self, param: &Duration) -> serde_json::Value {
96        match self {
97            Self::Millis => serde_json::to_value(param.as_millis()).unwrap(),
98            Self::Seconds => param.as_secs().into(),
99            Self::Minutes => (param.as_secs() / 60).into(),
100            Self::Hours => (param.as_secs() / 3_600).into(),
101            Self::Days => (param.as_secs() / 86_400).into(),
102            Self::Weeks => (param.as_secs() / 86_400 / 7).into(),
103        }
104    }
105}
106
107/// Supports deserializing a [`ByteSize`] from a number, with `self` being the unit of measurement.
108///
109/// # Examples
110///
111/// ```
112/// # use std::time::Duration;
113/// # use smart_config::{metadata::SizeUnit, DescribeConfig, DeserializeConfig, ByteSize};
114/// use smart_config::testing;
115///
116/// #[derive(DescribeConfig, DeserializeConfig)]
117/// struct TestConfig {
118///     #[config(with = SizeUnit::MiB)]
119///     size_mb: ByteSize,
120/// }
121///
122/// let source = smart_config::config!("size_mb": 4);
123/// let config = testing::test::<TestConfig>(source)?;
124/// assert_eq!(config.size_mb, ByteSize(4 << 20));
125/// # anyhow::Ok(())
126/// ```
127impl DeserializeParam<ByteSize> for SizeUnit {
128    const EXPECTING: BasicTypes = BasicTypes::INTEGER;
129
130    fn describe(&self, description: &mut TypeDescription) {
131        description
132            .set_details("byte size")
133            .set_unit((*self).into());
134    }
135
136    fn deserialize_param(
137        &self,
138        ctx: DeserializeContext<'_>,
139        param: &'static ParamMetadata,
140    ) -> Result<ByteSize, ErrorWithOrigin> {
141        let deserializer = ctx.current_value_deserializer(param.name)?;
142        let raw_value = u64::deserialize(deserializer)?;
143        ByteSize::checked(raw_value, *self).ok_or_else(|| {
144            let err = DeError::custom(format!(
145                "{raw_value} {unit} does not fit into `u64`",
146                unit = self.as_str()
147            ));
148            deserializer.enrich_err(err)
149        })
150    }
151
152    fn serialize_param(&self, param: &ByteSize) -> serde_json::Value {
153        match self {
154            Self::Bytes => param.0.into(),
155            Self::KiB => (param.0 >> 10).into(),
156            Self::MiB => (param.0 >> 20).into(),
157            Self::GiB => (param.0 >> 30).into(),
158        }
159    }
160}
161
162/// Default deserializer for [`Duration`]s, [`ByteSize`]s and [`EtherAmount`]s.
163///
164/// Values can be deserialized from 2 formats:
165///
166/// - String consisting of a number, optional whitespace and a unit, such as "30 secs" or "500ms" (for `Duration`) /
167///   "4 MiB" (for `ByteSize`). The unit must correspond to a [`TimeUnit`] / [`SizeUnit`] / [`EtherUnit`](crate::metadata::EtherUnit).
168///   `Duration`s and `EtherAmount`s support decimal numbers, such as `3.5 sec` or `1.5e-5 ether`; `ByteSize`s only support integers.
169/// - Object with a single key and a numeric value, such as `{ "hours": 3 }` (for `Duration`) / `{ "kb": 512 }` (for `SizeUnit`).
170///   To prevent precision loss, decimal values may be enclosed in a string (e.g., `{ "ether": "0.000123456" }`).
171///
172/// Thanks to nesting of object params, the second approach automatically means that a duration can be parsed
173/// from a param name suffixed with a unit. For example, a value `latency_ms: 500` for parameter `latency`
174/// will be recognized as 500 ms.
175///
176/// # Examples
177///
178/// ```
179/// # use std::time::Duration;
180/// # use smart_config::{testing, ByteSize, EtherAmount, Environment, DescribeConfig, DeserializeConfig};
181/// #[derive(DescribeConfig, DeserializeConfig)]
182/// struct TestConfig {
183///     latency: Duration,
184///     disk: ByteSize,
185///     #[config(default)]
186///     fee: EtherAmount,
187/// }
188///
189/// // Parsing from a string
190/// let source = smart_config::config!(
191///     "latency": "30 secs",
192///     "disk": "256 MiB",
193///     "fee": "100 gwei",
194/// );
195/// let config: TestConfig = testing::test(source)?;
196/// assert_eq!(config.latency, Duration::from_secs(30));
197/// assert_eq!(config.disk, ByteSize(256 << 20));
198/// assert_eq!(config.fee, EtherAmount(100_000_000_000));
199///
200/// // Parsing from an object
201/// let source = smart_config::config!(
202///     "latency": serde_json::json!({ "hours": 3.5 }),
203///     "disk": serde_json::json!({ "gigabytes": 2 }),
204///     "fee": serde_json::json!({ "ether": "0.000125" }),
205/// );
206/// let config: TestConfig = testing::test(source)?;
207/// assert_eq!(config.latency, Duration::from_secs(3_600 * 7 / 2));
208/// assert_eq!(config.disk, ByteSize(2 << 30));
209/// assert_eq!(config.fee, EtherAmount(125_000_000_000_000));
210///
211/// // Parsing from a suffixed parameter name
212/// let source = Environment::from_iter("", [
213///     ("LATENCY_SEC", "1.5"),
214///     ("DISK_GB", "10"),
215///     ("FEE_IN_ETHER", "1.5e-5"),
216/// ]);
217/// let config: TestConfig = testing::test(source)?;
218/// assert_eq!(config.latency, Duration::from_millis(1_500));
219/// assert_eq!(config.disk, ByteSize(10 << 30));
220/// assert_eq!(config.fee, EtherAmount(15_000_000_000_000));
221/// # anyhow::Ok(())
222/// ```
223#[derive(Debug, Clone, Copy)]
224pub struct WithUnit;
225
226impl WithUnit {
227    const EXPECTED_TYPES: BasicTypes = BasicTypes::STRING.or(BasicTypes::OBJECT);
228
229    fn deserialize<Raw, T>(
230        ctx: &DeserializeContext<'_>,
231        param: &'static ParamMetadata,
232    ) -> Result<T, ErrorWithOrigin>
233    where
234        Raw: EnumWithUnit + TryInto<T, Error = serde_json::Error>,
235    {
236        let deserializer = ctx.current_value_deserializer(param.name)?;
237        let raw = if let Value::String(s) = deserializer.value() {
238            s.expose()
239                .parse::<Raw>()
240                .map_err(|err| deserializer.enrich_err(err))?
241        } else {
242            deserializer.deserialize_enum("Raw", Raw::VARIANTS, EnumVisitor(PhantomData::<Raw>))?
243        };
244        raw.try_into().map_err(|err| deserializer.enrich_err(err))
245    }
246
247    // We need special handling for `{ "suffix": null }` values (incl. ones produced by suffixed param names like `param_ms: null`).
248    // Without it, we'd error when parsing `null` value as `u64`.
249    fn deserialize_opt<Raw, T>(
250        ctx: &DeserializeContext<'_>,
251        param: &'static ParamMetadata,
252    ) -> Result<Option<T>, ErrorWithOrigin>
253    where
254        Raw: EnumWithUnit + TryInto<T, Error = serde_json::Error>,
255    {
256        let deserializer = ctx.current_value_deserializer(param.name)?;
257        let raw = if let Value::String(s) = deserializer.value() {
258            Some(
259                s.expose()
260                    .parse::<Raw>()
261                    .map_err(|err| deserializer.enrich_err(err))?,
262            )
263        } else {
264            deserializer.deserialize_enum(
265                "Raw",
266                Raw::VARIANTS,
267                EnumVisitor(PhantomData::<Option<Raw>>),
268            )?
269        };
270        let Some(raw) = raw else {
271            return Ok(None);
272        };
273        raw.try_into()
274            .map(Some)
275            .map_err(|err| deserializer.enrich_err(err))
276    }
277}
278
279/// Helper trait allowing to unify enum parsing for durations and byte sizes.
280trait EnumWithUnit: FromStr<Err = serde_json::Error> {
281    type Value: FromStrStart + de::DeserializeOwned;
282
283    const EXPECTING: &'static str;
284    const VARIANTS: &'static [&'static str];
285
286    fn extract_variant(unit: &str) -> Option<fn(Self::Value) -> Self>;
287
288    fn parse<E: de::Error>(unit: &str, value: Self::Value) -> Result<Self, E> {
289        let variant_mapper = Self::extract_variant(unit)
290            .ok_or_else(|| DeError::unknown_variant(unit, Self::VARIANTS))?;
291        Ok(variant_mapper(value))
292    }
293
294    fn parse_opt<E: de::Error>(unit: &str, value: Option<Self::Value>) -> Result<Option<Self>, E> {
295        let variant_mapper = Self::extract_variant(unit)
296            .ok_or_else(|| DeError::unknown_variant(unit, Self::VARIANTS))?;
297        // We want to check the variant first, and only then return `Ok(None)`.
298        Ok(value.map(variant_mapper))
299    }
300
301    fn from_unit_str(s: &str, lowercase_unit: bool) -> Result<Self, serde_json::Error> {
302        let (value, rem) = <Self::Value as FromStrStart>::from_str_start(s)?;
303        let value =
304            value.ok_or_else(|| DeError::invalid_type(de::Unexpected::Str(s), &Self::EXPECTING))?;
305
306        let mut unit = rem.trim();
307        if unit.is_empty() {
308            return Err(DeError::invalid_type(
309                de::Unexpected::Str(s),
310                &Self::EXPECTING,
311            ));
312        }
313
314        let lowercase_unit_string;
315        if lowercase_unit {
316            lowercase_unit_string = unit.to_lowercase();
317            unit = &lowercase_unit_string;
318        }
319        Self::parse(unit, value)
320    }
321}
322
323#[derive(Debug)]
324struct EnumVisitor<T>(PhantomData<T>);
325
326impl<'v, T> de::Visitor<'v> for EnumVisitor<T>
327where
328    T: EnumWithUnit<Value: de::DeserializeOwned>,
329{
330    type Value = T;
331
332    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
333        write!(formatter, "enum with one of {:?} variants", T::VARIANTS)
334    }
335
336    fn visit_enum<A: EnumAccess<'v>>(self, data: A) -> Result<Self::Value, A::Error> {
337        let (tag, payload) = data.variant::<String>()?;
338        let value = payload.newtype_variant()?;
339        let unit = tag.strip_prefix("in_").unwrap_or(&tag);
340        T::parse(unit, value)
341    }
342}
343
344impl<'v, T: EnumWithUnit> de::Visitor<'v> for EnumVisitor<Option<T>> {
345    type Value = Option<T>;
346
347    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
348        write!(formatter, "enum with one of {:?} variants", T::VARIANTS)
349    }
350
351    fn visit_enum<A: EnumAccess<'v>>(self, data: A) -> Result<Self::Value, A::Error> {
352        let (tag, payload) = data.variant::<String>()?;
353        let value = payload.newtype_variant()?;
354        let unit = tag.strip_prefix("in_").unwrap_or(&tag);
355        T::parse_opt(unit, value)
356    }
357}
358
359/// Raw `Duration` representation used by the `WithUnit` deserializer.
360#[derive(Debug)]
361#[cfg_attr(test, derive(PartialEq))]
362enum RawDuration {
363    Millis(Decimal),
364    Seconds(Decimal),
365    Minutes(Decimal),
366    Hours(Decimal),
367    Days(Decimal),
368    Weeks(Decimal),
369}
370
371macro_rules! impl_enum_with_unit {
372    ($($($name:tt)|+ => $func:expr,)+) => {
373        const VARIANTS: &'static [&'static str] = &[$($($name,)+)+];
374
375        fn extract_variant(unit: &str) -> Option<fn(Self::Value) -> Self> {
376            Some(match unit {
377                $($($name )|+ => $func,)+
378                _ => return None,
379            })
380        }
381    };
382}
383
384impl EnumWithUnit for RawDuration {
385    type Value = Decimal;
386
387    const EXPECTING: &'static str = "value with unit, like '10 ms'";
388
389    impl_enum_with_unit!(
390        "milliseconds" | "millis" | "ms" => Self::Millis,
391        "seconds" | "second" | "secs" | "sec" | "s" => Self::Seconds,
392        "minutes" | "minute" | "mins" | "min" | "m" => Self::Minutes,
393        "hours" | "hour" | "hr" | "h" => Self::Hours,
394        "days" | "day" | "d" => Self::Days,
395        "weeks" | "week" | "w" => Self::Weeks,
396    );
397}
398
399impl FromStr for RawDuration {
400    type Err = serde_json::Error;
401
402    fn from_str(s: &str) -> Result<Self, Self::Err> {
403        Self::from_unit_str(s, false)
404    }
405}
406
407impl TryFrom<RawDuration> for Duration {
408    type Error = serde_json::Error;
409
410    fn try_from(value: RawDuration) -> Result<Self, Self::Error> {
411        let (unit, raw_value) = match value {
412            RawDuration::Millis(val) => (TimeUnit::Millis, val),
413            RawDuration::Seconds(val) => (TimeUnit::Seconds, val),
414            RawDuration::Minutes(val) => (TimeUnit::Minutes, val),
415            RawDuration::Hours(val) => (TimeUnit::Hours, val),
416            RawDuration::Days(val) => (TimeUnit::Days, val),
417            RawDuration::Weeks(val) => (TimeUnit::Weeks, val),
418        };
419        unit.into_duration(raw_value)
420    }
421}
422
423impl DeserializeParam<Duration> for WithUnit {
424    const EXPECTING: BasicTypes = Self::EXPECTED_TYPES;
425
426    fn describe(&self, description: &mut TypeDescription) {
427        description.set_details("duration with unit, or object with single unit key");
428        description.set_suffixes(TypeSuffixes::DurationUnits);
429    }
430
431    fn deserialize_param(
432        &self,
433        ctx: DeserializeContext<'_>,
434        param: &'static ParamMetadata,
435    ) -> Result<Duration, ErrorWithOrigin> {
436        Self::deserialize::<RawDuration, _>(&ctx, param)
437    }
438
439    fn serialize_param(&self, param: &Duration) -> serde_json::Value {
440        if param.is_zero() {
441            // Special case to produce a more "expected" string.
442            return "0s".into();
443        }
444
445        let duration_string = if param.subsec_millis() != 0 {
446            format!("{}ms", param.as_millis())
447        } else {
448            let seconds = param.as_secs();
449            if seconds % 60 != 0 {
450                format!("{seconds}s")
451            } else if seconds % 3_600 != 0 {
452                format!("{}min", seconds / 60)
453            } else if seconds % 86_400 != 0 {
454                format!("{}h", seconds / 3_600)
455            } else if seconds % (86_400 * 7) != 0 {
456                format!("{}d", seconds / 86_400)
457            } else {
458                format!("{}w", seconds / (86_400 * 7))
459            }
460        };
461        duration_string.into()
462    }
463}
464
465macro_rules! impl_deserialize_opt_param {
466    ($ty:ty => $raw:ty) => {
467        impl DeserializeParam<Option<$ty>> for WithUnit {
468            const EXPECTING: BasicTypes = Self::EXPECTED_TYPES;
469
470            fn describe(&self, description: &mut TypeDescription) {
471                <Self as DeserializeParam<$ty>>::describe(self, description);
472            }
473
474            fn deserialize_param(
475                &self,
476                ctx: DeserializeContext<'_>,
477                param: &'static ParamMetadata,
478            ) -> Result<Option<$ty>, ErrorWithOrigin> {
479                Self::deserialize_opt::<$raw, _>(&ctx, param)
480            }
481
482            fn serialize_param(&self, param: &Option<$ty>) -> serde_json::Value {
483                match param {
484                    Some(val) => self.serialize_param(val),
485                    None => serde_json::Value::Null,
486                }
487            }
488        }
489    };
490}
491
492impl_deserialize_opt_param!(Duration => RawDuration);
493
494macro_rules! impl_well_known_with_unit {
495    ($ty:ty) => {
496        impl WellKnown for $ty {
497            type Deserializer = WithUnit;
498            const DE: Self::Deserializer = WithUnit;
499        }
500
501        impl CustomKnownOption for $ty {
502            type OptDeserializer = Optional<WithUnit, true>;
503            const OPT_DE: Self::OptDeserializer = Optional(WithUnit);
504        }
505    };
506}
507
508impl_well_known_with_unit!(Duration);
509
510#[derive(Debug)]
511#[cfg_attr(test, derive(PartialEq))]
512enum RawByteSize {
513    Bytes(u64),
514    Kilobytes(u64),
515    Megabytes(u64),
516    Gigabytes(u64),
517}
518
519impl EnumWithUnit for RawByteSize {
520    type Value = u64;
521
522    const EXPECTING: &'static str = "value with unit, like '32 MB'";
523
524    impl_enum_with_unit!(
525        "bytes" | "b" => Self::Bytes,
526        "kilobytes" | "kb" | "kib" => Self::Kilobytes,
527        "megabytes" | "mb" | "mib" => Self::Megabytes,
528        "gigabytes" | "gb" | "gib" => Self::Gigabytes,
529    );
530}
531
532impl TryFrom<RawByteSize> for ByteSize {
533    type Error = serde_json::Error;
534
535    fn try_from(value: RawByteSize) -> Result<Self, Self::Error> {
536        let (unit, raw_value) = match value {
537            RawByteSize::Bytes(val) => (SizeUnit::Bytes, val),
538            RawByteSize::Kilobytes(val) => (SizeUnit::KiB, val),
539            RawByteSize::Megabytes(val) => (SizeUnit::MiB, val),
540            RawByteSize::Gigabytes(val) => (SizeUnit::GiB, val),
541        };
542        ByteSize::checked(raw_value, unit).ok_or_else(|| {
543            DeError::custom(format!(
544                "{raw_value} {unit} does not fit into `u64`",
545                unit = unit.as_str()
546            ))
547        })
548    }
549}
550
551// Inapplicable to `Duration` because it's not a local type.
552macro_rules! impl_deserialize_param {
553    ($ty:ty, raw: $raw:ty, name: $name:tt, units: $units:ident) => {
554        impl<'de> Deserialize<'de> for $raw {
555            fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
556                deserializer.deserialize_enum(
557                    stringify!($raw),
558                    Self::VARIANTS,
559                    EnumVisitor(PhantomData::<Self>),
560                )
561            }
562        }
563
564        impl FromStr for $raw {
565            type Err = serde_json::Error;
566
567            fn from_str(s: &str) -> Result<Self, Self::Err> {
568                Self::from_unit_str(s, true)
569            }
570        }
571
572        impl FromStr for $ty {
573            type Err = serde_json::Error;
574
575            fn from_str(s: &str) -> Result<Self, Self::Err> {
576                <$raw>::from_unit_str(s, true)?.try_into()
577            }
578        }
579
580        impl DeserializeParam<$ty> for WithUnit {
581            const EXPECTING: BasicTypes = Self::EXPECTED_TYPES;
582
583            fn describe(&self, description: &mut TypeDescription) {
584                description
585                    .set_details(concat!($name, " with unit, or object with single unit key"));
586                description.set_suffixes(TypeSuffixes::$units);
587            }
588
589            fn deserialize_param(
590                &self,
591                ctx: DeserializeContext<'_>,
592                param: &'static ParamMetadata,
593            ) -> Result<$ty, ErrorWithOrigin> {
594                Self::deserialize::<$raw, _>(&ctx, param)
595            }
596
597            fn serialize_param(&self, param: &$ty) -> serde_json::Value {
598                param.to_string().into()
599            }
600        }
601    };
602}
603
604impl_deserialize_param!(ByteSize, raw: RawByteSize, name: "size", units: SizeUnits);
605impl_deserialize_opt_param!(ByteSize => RawByteSize);
606impl_well_known_with_unit!(ByteSize);
607
608impl TypeSuffixes {
609    pub(crate) fn contains(self, suffix: &str) -> bool {
610        match self {
611            Self::All => true,
612            Self::DurationUnits => {
613                let suffix = suffix.strip_prefix("in_").unwrap_or(suffix);
614                RawDuration::VARIANTS.contains(&suffix)
615            }
616            Self::SizeUnits => {
617                let suffix = suffix.strip_prefix("in_").unwrap_or(suffix);
618                RawByteSize::VARIANTS.contains(&suffix)
619            }
620            Self::EtherUnits => {
621                let suffix = suffix.strip_prefix("in_").unwrap_or(suffix);
622                RawEtherAmount::VARIANTS.contains(&suffix)
623            }
624        }
625    }
626}
627
628#[derive(Debug)]
629#[cfg_attr(test, derive(PartialEq))]
630enum RawEtherAmount {
631    Wei(Decimal),
632    Gwei(Decimal),
633    Ether(Decimal),
634}
635
636impl EnumWithUnit for RawEtherAmount {
637    type Value = Decimal;
638
639    const EXPECTING: &'static str = "value with unit, like '100 gwei'";
640
641    impl_enum_with_unit!(
642        "wei" => Self::Wei,
643        "gwei" => Self::Gwei,
644        "ether" => Self::Ether,
645    );
646}
647
648impl TryFrom<RawEtherAmount> for EtherAmount {
649    type Error = serde_json::Error;
650
651    fn try_from(value: RawEtherAmount) -> Result<Self, Self::Error> {
652        let (scale, raw_value) = match value {
653            RawEtherAmount::Wei(val) => (0, val),
654            RawEtherAmount::Gwei(val) => (9, val),
655            RawEtherAmount::Ether(val) => (18, val),
656        };
657        let value = raw_value.scale(scale)?;
658        Ok(Self(value))
659    }
660}
661
662impl_deserialize_param!(EtherAmount, raw: RawEtherAmount, name: "amount", units: EtherUnits);
663impl_deserialize_opt_param!(EtherAmount => RawEtherAmount);
664impl_well_known_with_unit!(EtherAmount);
665
666#[cfg(test)]
667mod tests {
668    use super::*;
669
670    #[test]
671    fn parsing_time_string() {
672        let duration: RawDuration = "10ms".parse().unwrap();
673        assert_eq!(duration, RawDuration::Millis(10.into()));
674        let duration: RawDuration = "50    seconds".parse().unwrap();
675        assert_eq!(duration, RawDuration::Seconds(50.into()));
676        let duration: RawDuration = "40s".parse().unwrap();
677        assert_eq!(duration, RawDuration::Seconds(40.into()));
678        let duration: RawDuration = "10 min".parse().unwrap();
679        assert_eq!(duration, RawDuration::Minutes(10.into()));
680        let duration: RawDuration = "10m".parse().unwrap();
681        assert_eq!(duration, RawDuration::Minutes(10.into()));
682        let duration: RawDuration = "12 hours".parse().unwrap();
683        assert_eq!(duration, RawDuration::Hours(12.into()));
684        let duration: RawDuration = "12h".parse().unwrap();
685        assert_eq!(duration, RawDuration::Hours(12.into()));
686        let duration: RawDuration = "30d".parse().unwrap();
687        assert_eq!(duration, RawDuration::Days(30.into()));
688        let duration: RawDuration = "1 day".parse().unwrap();
689        assert_eq!(duration, RawDuration::Days(1.into()));
690        let duration: RawDuration = "2 weeks".parse().unwrap();
691        assert_eq!(duration, RawDuration::Weeks(2.into()));
692        let duration: RawDuration = "3w".parse().unwrap();
693        assert_eq!(duration, RawDuration::Weeks(3.into()));
694    }
695
696    #[test]
697    fn parsing_fractional_time_string() {
698        let duration: RawDuration = "10.0ms".parse().unwrap();
699        assert_eq!(duration, RawDuration::Millis(10.into()));
700        let duration: RawDuration = "0.2s".parse().unwrap();
701        assert_eq!(duration, RawDuration::Seconds(Decimal::new(2, -1)));
702        let duration: RawDuration = "0.33 days".parse().unwrap();
703        assert_eq!(duration, RawDuration::Days(Decimal::new(33, -2)));
704        let duration: RawDuration = "1.7e+3 hours".parse().unwrap();
705        assert_eq!(duration, RawDuration::Hours(Decimal::new(17, 2)));
706    }
707
708    #[test]
709    fn parsing_time_string_errors() {
710        let err = "".parse::<RawDuration>().unwrap_err().to_string();
711        assert!(err.starts_with("invalid type"), "{err}");
712        let err = "???".parse::<RawDuration>().unwrap_err().to_string();
713        assert!(err.starts_with("invalid type"), "{err}");
714        let err = "10".parse::<RawDuration>().unwrap_err().to_string();
715        assert!(err.starts_with("invalid type"), "{err}");
716        let err = "hours".parse::<RawDuration>().unwrap_err().to_string();
717        assert!(err.starts_with("invalid type"), "{err}");
718
719        let err = "111111111111111111111111111111111111111111s"
720            .parse::<RawDuration>()
721            .unwrap_err()
722            .to_string();
723        assert!(err.contains("too many digits"), "{err}");
724
725        let err = "10 months".parse::<RawDuration>().unwrap_err().to_string();
726        assert!(err.starts_with("unknown variant"), "{err}");
727    }
728
729    #[test]
730    fn parsing_byte_size_string() {
731        let size: RawByteSize = "16bytes".parse().unwrap();
732        assert_eq!(size, RawByteSize::Bytes(16));
733        let size: RawByteSize = "128    KiB".parse().unwrap();
734        assert_eq!(size, RawByteSize::Kilobytes(128));
735        let size: RawByteSize = "16 kb".parse().unwrap();
736        assert_eq!(size, RawByteSize::Kilobytes(16));
737        let size: RawByteSize = "4MB".parse().unwrap();
738        assert_eq!(size, RawByteSize::Megabytes(4));
739        let size: RawByteSize = "1 GB".parse().unwrap();
740        assert_eq!(size, RawByteSize::Gigabytes(1));
741    }
742
743    #[test]
744    fn parsing_ether_amount_string() {
745        let amount: RawEtherAmount = "1wei".parse().unwrap();
746        assert_eq!(amount, RawEtherAmount::Wei(1.into()));
747        let amount: EtherAmount = amount.try_into().unwrap();
748        assert_eq!(amount, EtherAmount(1));
749
750        let amount: RawEtherAmount = "123 wei".parse().unwrap();
751        assert_eq!(amount, RawEtherAmount::Wei(123.into()));
752        let amount: EtherAmount = amount.try_into().unwrap();
753        assert_eq!(amount, EtherAmount(123));
754
755        let amount: RawEtherAmount = "1.5 gwei".parse().unwrap();
756        assert_eq!(amount, RawEtherAmount::Gwei(Decimal::new(15, -1)));
757        let amount: EtherAmount = amount.try_into().unwrap();
758        assert_eq!(amount, EtherAmount(1_500_000_000));
759
760        for input in [
761            "0.0015 ether",
762            "0.001_5ether",
763            "0.0015ether",
764            "1.5e-3 ether",
765            "15e-4ether",
766            ".015e-1 ether",
767        ] {
768            let amount: RawEtherAmount = input.parse().unwrap();
769            assert_eq!(amount, RawEtherAmount::Ether(Decimal::new(15, -4)));
770            let amount: EtherAmount = amount.try_into().unwrap();
771            assert_eq!(amount, EtherAmount(1_500_000_000_000_000));
772        }
773    }
774
775    #[test]
776    fn serializing_with_time_unit() {
777        let val = TimeUnit::Millis.serialize_param(&Duration::from_millis(10));
778        assert_eq!(val, 10_u32);
779        let val = TimeUnit::Millis.serialize_param(&Duration::from_secs(10));
780        assert_eq!(val, 10_000_u32);
781        let val = TimeUnit::Seconds.serialize_param(&Duration::from_secs(10));
782        assert_eq!(val, 10_u32);
783        let val = TimeUnit::Minutes.serialize_param(&Duration::from_secs(10));
784        assert_eq!(val, 0_u32);
785        let val = TimeUnit::Minutes.serialize_param(&Duration::from_secs(120));
786        assert_eq!(val, 2_u32);
787    }
788
789    #[test]
790    fn serializing_with_size_unit() {
791        let val = SizeUnit::Bytes.serialize_param(&ByteSize(128));
792        assert_eq!(val, 128_u32);
793        let val = SizeUnit::Bytes.serialize_param(&ByteSize(1 << 16));
794        assert_eq!(val, 1_u32 << 16);
795        let val = SizeUnit::KiB.serialize_param(&ByteSize(1 << 16));
796        assert_eq!(val, 1_u32 << 6);
797        let val = SizeUnit::MiB.serialize_param(&ByteSize(1 << 16));
798        assert_eq!(val, 0_u32);
799        let val = SizeUnit::MiB.serialize_param(&ByteSize::new(3, SizeUnit::MiB));
800        assert_eq!(val, 3_u32);
801    }
802
803    #[test]
804    fn serializing_with_duration() {
805        let val = WithUnit.serialize_param(&Duration::ZERO);
806        assert_eq!(val, "0s");
807        let val = WithUnit.serialize_param(&Duration::from_millis(10));
808        assert_eq!(val, "10ms");
809        let val = WithUnit.serialize_param(&Duration::from_secs(5));
810        assert_eq!(val, "5s");
811        let val = WithUnit.serialize_param(&Duration::from_millis(5_050));
812        assert_eq!(val, "5050ms");
813        let val = WithUnit.serialize_param(&Duration::from_secs(300));
814        assert_eq!(val, "5min");
815        let val = WithUnit.serialize_param(&Duration::from_secs(7_200));
816        assert_eq!(val, "2h");
817        let val = WithUnit.serialize_param(&Duration::from_secs(86_400));
818        assert_eq!(val, "1d");
819    }
820
821    #[test]
822    fn serializing_with_byte_size() {
823        let val = WithUnit.serialize_param(&ByteSize(0));
824        assert_eq!(val, "0 B");
825        let val = WithUnit.serialize_param(&ByteSize(128));
826        assert_eq!(val, "128 B");
827        let val = WithUnit.serialize_param(&ByteSize(32 << 10));
828        assert_eq!(val, "32 KiB");
829        let val = WithUnit.serialize_param(&ByteSize(3 << 20));
830        assert_eq!(val, "3 MiB");
831    }
832}