smart_config/metadata/
mod.rs

1//! Configuration metadata.
2
3use std::{any, borrow::Cow, fmt, ops, time::Duration};
4
5use self::_private::{BoxedDeserializer, BoxedVisitor};
6use crate::{
7    de::{_private::ErasedDeserializer, DeserializeParam},
8    fallback::FallbackSource,
9    pat::PatternDisplay,
10    validation::Validate,
11};
12
13#[doc(hidden)] // used in the derive macros
14pub mod _private;
15#[cfg(test)]
16mod tests;
17
18/// Options for a param or config alias.
19#[derive(Debug, Clone, Copy)]
20#[cfg_attr(test, derive(PartialEq))]
21#[non_exhaustive]
22pub struct AliasOptions {
23    /// Is this alias deprecated?
24    pub is_deprecated: bool,
25}
26
27impl Default for AliasOptions {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33impl AliasOptions {
34    /// Creates default options.
35    pub const fn new() -> Self {
36        AliasOptions {
37            is_deprecated: false,
38        }
39    }
40
41    /// Marks the alias as deprecated.
42    #[must_use]
43    pub const fn deprecated(mut self) -> Self {
44        self.is_deprecated = true;
45        self
46    }
47
48    #[doc(hidden)] // not stable yet
49    #[must_use]
50    pub fn combine(self, other: Self) -> Self {
51        Self {
52            is_deprecated: self.is_deprecated || other.is_deprecated,
53        }
54    }
55}
56
57/// Metadata for a configuration (i.e., a group of related parameters).
58#[derive(Debug, Clone)]
59pub struct ConfigMetadata {
60    /// Type of this configuration.
61    pub ty: RustType,
62    /// Help regarding the config itself.
63    pub help: &'static str,
64    /// Parameters included in the config.
65    pub params: &'static [ParamMetadata],
66    /// Tag for enumeration configs.
67    pub tag: Option<ConfigTag>,
68    /// Nested configs included in the config.
69    pub nested_configs: &'static [NestedConfigMetadata],
70    #[doc(hidden)] // implementation detail
71    pub deserializer: BoxedDeserializer,
72    #[doc(hidden)] // implementation detail
73    pub visitor: BoxedVisitor,
74    #[doc(hidden)] // implementation detail
75    pub validations: &'static [&'static dyn Validate<dyn any::Any>],
76}
77
78/// Information about a config tag.
79#[derive(Debug, Clone, Copy)]
80pub struct ConfigTag {
81    /// Parameter of the enclosing config corresponding to the tag.
82    pub param: &'static ParamMetadata,
83    /// Variants for the tag.
84    pub variants: &'static [ConfigVariant],
85    /// Default variant, if any.
86    pub default_variant: Option<&'static ConfigVariant>,
87}
88
89/// Variant of a [`ConfigTag`].
90#[derive(Debug, Clone, Copy)]
91pub struct ConfigVariant {
92    /// Canonical param name in the config sources. Not necessarily the Rust name!
93    pub name: &'static str,
94    /// Param aliases.
95    pub aliases: &'static [&'static str],
96    /// Name of the corresponding enum variant in Rust code.
97    pub rust_name: &'static str,
98    /// Human-readable param help parsed from the doc comment.
99    pub help: &'static str,
100}
101
102/// Metadata for a specific configuration parameter.
103#[derive(Debug, Clone, Copy)]
104pub struct ParamMetadata {
105    /// Canonical param name in the config sources. Not necessarily the Rust field name!
106    pub name: &'static str,
107    /// Param aliases.
108    pub aliases: &'static [(&'static str, AliasOptions)],
109    /// Human-readable param help parsed from the doc comment.
110    pub help: &'static str,
111    /// Name of the param field in Rust code.
112    pub rust_field_name: &'static str,
113    /// Rust type of the parameter.
114    pub rust_type: RustType,
115    /// Basic type(s) expected by the param deserializer.
116    pub expecting: BasicTypes,
117    /// Tag variant in the enclosing [`ConfigMetadata`] that enables this parameter. `None` means that the parameter is unconditionally enabled.
118    pub tag_variant: Option<&'static ConfigVariant>,
119    #[doc(hidden)] // implementation detail
120    pub deserializer: &'static dyn ErasedDeserializer,
121    #[doc(hidden)] // implementation detail
122    pub default_value: Option<fn() -> Box<dyn any::Any>>,
123    #[doc(hidden)] // implementation detail
124    pub example_value: Option<fn() -> Box<dyn any::Any>>,
125    #[doc(hidden)]
126    pub fallback: Option<&'static dyn FallbackSource>,
127}
128
129impl ParamMetadata {
130    /// Returns the default value for the param.
131    pub fn default_value(&self) -> Option<Box<dyn any::Any>> {
132        self.default_value.map(|value_fn| value_fn())
133    }
134
135    /// Returns the default value for the param serialized into JSON.
136    pub fn default_value_json(&self) -> Option<serde_json::Value> {
137        self.default_value()
138            .map(|val| self.deserializer.serialize_param(val.as_ref()))
139    }
140
141    /// Returns the example value for the param serialized into JSON.
142    pub fn example_value_json(&self) -> Option<serde_json::Value> {
143        let example = self.example_value?();
144        Some(self.deserializer.serialize_param(example.as_ref()))
145    }
146
147    /// Returns the type description for this param as provided by its deserializer.
148    // TODO: can be cached if necessary
149    pub fn type_description(&self) -> TypeDescription {
150        let mut description = TypeDescription::default();
151        self.deserializer.describe(&mut description);
152        description.rust_type = self.rust_type.name_in_code;
153        description
154    }
155}
156
157/// Representation of a Rust type.
158#[derive(Clone, Copy)]
159pub struct RustType {
160    id: fn() -> any::TypeId,
161    name_in_code: &'static str,
162}
163
164impl fmt::Debug for RustType {
165    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
166        formatter.write_str(self.name_in_code)
167    }
168}
169
170impl PartialEq for RustType {
171    fn eq(&self, other: &Self) -> bool {
172        (self.id)() == (other.id)()
173    }
174}
175
176impl RustType {
177    /// Creates a new type.
178    #[allow(clippy::incompatible_msrv)] // false positive; `TypeId::of` is referenced, not invoked
179    pub const fn of<T: 'static>(name_in_code: &'static str) -> Self {
180        Self {
181            id: any::TypeId::of::<T>,
182            name_in_code,
183        }
184    }
185
186    /// Returns the unique ID of this type.
187    pub fn id(&self) -> any::TypeId {
188        (self.id)()
189    }
190
191    /// Returns the name of this type as specified in code.
192    pub const fn name_in_code(&self) -> &'static str {
193        self.name_in_code
194    }
195}
196
197/// Set of one or more basic types in the JSON object model.
198#[derive(Clone, Copy, PartialEq, Eq, Hash)]
199pub struct BasicTypes(u8);
200
201impl BasicTypes {
202    /// Boolean value.
203    pub const BOOL: Self = Self(1);
204    /// Integer value.
205    pub const INTEGER: Self = Self(2);
206    /// Floating-point value.
207    pub const FLOAT: Self = Self(4 | 2);
208    /// String.
209    pub const STRING: Self = Self(8);
210    /// Array of values.
211    pub const ARRAY: Self = Self(16);
212    /// Object / map of values.
213    pub const OBJECT: Self = Self(32);
214    /// Any value.
215    pub const ANY: Self = Self(63);
216
217    const COMPONENTS: &'static [(Self, &'static str)] = &[
218        (Self::BOOL, "Boolean"),
219        (Self::INTEGER, "integer"),
220        (Self::FLOAT, "float"),
221        (Self::STRING, "string"),
222        (Self::ARRAY, "array"),
223        (Self::OBJECT, "object"),
224    ];
225
226    pub(crate) const fn from_raw(raw: u8) -> Self {
227        assert!(raw != 0, "Raw `BasicTypes` cannot be 0");
228        assert!(
229            raw <= Self::ANY.0,
230            "Unused set bits in `BasicTypes` raw value"
231        );
232        Self(raw)
233    }
234
235    #[doc(hidden)] // should only be used via macros
236    pub const fn raw(self) -> u8 {
237        self.0
238    }
239
240    /// Returns a union of two sets of basic types.
241    #[must_use]
242    pub const fn or(self, rhs: Self) -> Self {
243        Self(self.0 | rhs.0)
244    }
245
246    /// Checks whether the `needle` is fully contained in this set.
247    pub const fn contains(self, needle: Self) -> bool {
248        self.0 & needle.0 == needle.0
249    }
250}
251
252impl fmt::Display for BasicTypes {
253    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
254        if *self == Self::ANY {
255            formatter.write_str("any")
256        } else {
257            let mut is_empty = true;
258            for &(component, name) in Self::COMPONENTS {
259                if self.contains(component) {
260                    if !is_empty {
261                        formatter.write_str(" | ")?;
262                    }
263                    formatter.write_str(name)?;
264                    is_empty = false;
265                }
266            }
267            Ok(())
268        }
269    }
270}
271
272impl fmt::Debug for BasicTypes {
273    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
274        fmt::Display::fmt(self, formatter)
275    }
276}
277
278#[derive(Debug, Clone)]
279struct ChildDescription {
280    expecting: BasicTypes,
281    description: Box<TypeDescription>,
282}
283
284impl ChildDescription {
285    fn new<T: 'static, De: DeserializeParam<T>>(deserializer: &De, set_type: bool) -> Self {
286        let mut description = Box::default();
287        deserializer.describe(&mut description);
288        if set_type {
289            description.rust_type = any::type_name::<T>();
290        }
291        Self {
292            expecting: De::EXPECTING,
293            description,
294        }
295    }
296}
297
298/// Recognized suffixes for a param type used during object nesting when preprocessing config sources.
299/// Only these suffixes will be recognized as belonging to the param and activate its object nesting.
300#[derive(Debug, Clone, Copy)]
301#[non_exhaustive]
302#[doc(hidden)] // not stable yet
303pub enum TypeSuffixes {
304    /// All possible suffixes.
305    All,
306    /// Duration units like `_sec` or `_millis`. May be prepended with `_in`, e.g. `_in_secs`.
307    DurationUnits,
308    /// Byte size units like `_mb` or `_bytes`. May be prepended with `_in`, e.g. `_in_mb`.
309    SizeUnits,
310    /// Ether units like `_wei` or `_ether`. May be prepended with `_in`, e.g. `_in_wei`.
311    EtherUnits,
312}
313
314#[derive(Debug, Clone)]
315struct Items {
316    description: ChildDescription,
317    sep: Option<PatternDisplay>,
318}
319
320#[derive(Debug, Clone)]
321struct Entries {
322    keys: ChildDescription,
323    values: ChildDescription,
324    sep: Option<(PatternDisplay, PatternDisplay)>,
325}
326
327/// Human-readable description for a Rust type used in configuration parameter (Boolean value, integer, string etc.).
328///
329/// If a configuration parameter supports complex inputs (objects and/or arrays), this information *may* contain
330/// info on child types (array items; map keys / values).
331#[derive(Debug, Clone, Default)]
332pub struct TypeDescription {
333    rust_type: &'static str,
334    details: Option<Cow<'static, str>>,
335    unit: Option<UnitOfMeasurement>,
336    suffixes: Option<TypeSuffixes>,
337    pub(crate) is_secret: bool,
338    validations: Vec<String>,
339    deserialize_if: Option<String>,
340    items: Option<Items>,
341    entries: Option<Entries>,
342    fallback: Option<ChildDescription>,
343}
344
345impl TypeDescription {
346    #[doc(hidden)]
347    pub fn rust_type(&self) -> &str {
348        self.rust_type
349    }
350
351    /// Gets the type details.
352    pub fn details(&self) -> Option<&str> {
353        self.details.as_deref()
354    }
355
356    /// Gets the unit of measurement.
357    pub fn unit(&self) -> Option<UnitOfMeasurement> {
358        self.unit
359    }
360
361    #[doc(hidden)] // not stable yet
362    pub fn suffixes(&self) -> Option<TypeSuffixes> {
363        self.suffixes
364    }
365
366    #[doc(hidden)] // exposes implementation details
367    pub fn validations(&self) -> &[String] {
368        &self.validations
369    }
370
371    #[doc(hidden)] // exposes implementation details
372    pub fn deserialize_if(&self) -> Option<&str> {
373        self.deserialize_if.as_deref()
374    }
375
376    /// Returns the description of array items, if one was provided.
377    pub fn items(&self) -> Option<(BasicTypes, &Self)> {
378        self.items
379            .as_ref()
380            .map(|child| (child.description.expecting, &*child.description.description))
381    }
382
383    /// Returns a separator for array items. This can be `Some(_)` only if `items()` returns `Some(_)`.
384    #[doc(hidden)] // not stable yet
385    pub fn item_separator(&self) -> Option<&PatternDisplay> {
386        self.items.as_ref()?.sep.as_ref()
387    }
388
389    /// Returns the description of map keys, if one was provided.
390    pub fn keys(&self) -> Option<(BasicTypes, &Self)> {
391        let keys = &self.entries.as_ref()?.keys;
392        Some((keys.expecting, &*keys.description))
393    }
394
395    /// Returns the description of map values, if one was provided.
396    pub fn values(&self) -> Option<(BasicTypes, &Self)> {
397        let keys = &self.entries.as_ref()?.values;
398        Some((keys.expecting, &*keys.description))
399    }
400
401    /// Returns separator for entries (first, the separator between entries and then the separator
402    /// between key and value in an entry). This can be `Some(_)` only if `keys()` and `values()` return `Some(_)`.
403    #[doc(hidden)] // not stable yet
404    pub fn entry_separators(&self) -> Option<(&PatternDisplay, &PatternDisplay)> {
405        let entries = self.entries.as_ref()?;
406        entries.sep.as_ref().map(|(entries, kv)| (entries, kv))
407    }
408
409    /// Returns the fallback description, if any.
410    pub fn fallback(&self) -> Option<(BasicTypes, &Self)> {
411        let fallback = self.fallback.as_ref()?;
412        Some((fallback.expecting, &*fallback.description))
413    }
414
415    /// Checks whether this type or any child types (e.g., array items or map keys / values) are marked
416    /// as secret.
417    pub fn contains_secrets(&self) -> bool {
418        if self.is_secret {
419            return true;
420        }
421        if let Some(item) = &self.items
422            && item.description.description.contains_secrets()
423        {
424            return true;
425        }
426        if let Some(Entries { keys, values, .. }) = &self.entries {
427            if keys.description.contains_secrets() {
428                return true;
429            }
430            if values.description.contains_secrets() {
431                return true;
432            }
433        }
434        false
435    }
436
437    /// Sets human-readable type details.
438    pub fn set_details(&mut self, details: impl Into<Cow<'static, str>>) -> &mut Self {
439        self.details = Some(details.into());
440        self
441    }
442
443    /// Adds a unit of measurement.
444    pub fn set_unit(&mut self, unit: UnitOfMeasurement) -> &mut Self {
445        self.unit = Some(unit);
446        self
447    }
448
449    pub(crate) fn set_suffixes(&mut self, suffixes: TypeSuffixes) -> &mut Self {
450        self.suffixes = Some(suffixes);
451        self
452    }
453
454    /// Sets validation for the type.
455    pub fn set_validations<T>(&mut self, validations: &[&'static dyn Validate<T>]) -> &mut Self {
456        self.validations = validations.iter().map(ToString::to_string).collect();
457        self
458    }
459
460    /// Sets a "deserialize if" condition for the type.
461    pub fn set_deserialize_if<T>(&mut self, condition: &'static dyn Validate<T>) -> &mut Self {
462        self.deserialize_if = Some(condition.to_string());
463        self
464    }
465
466    /// Marks the value as secret.
467    pub fn set_secret(&mut self) -> &mut Self {
468        self.is_secret = true;
469        self
470    }
471
472    /// Adds a description of array items. This only makes sense for params accepting array input.
473    pub fn set_items<T: 'static>(&mut self, items: &impl DeserializeParam<T>) -> &mut Self {
474        self.items = Some(Items {
475            description: ChildDescription::new(items, true),
476            sep: None,
477        });
478        self
479    }
480
481    pub(crate) fn set_items_sep(&mut self, separator: PatternDisplay) {
482        if let Some(items) = &mut self.items {
483            items.sep = Some(separator);
484        }
485    }
486
487    /// Adds a description of keys and values. This only makes sense for params accepting object input.
488    pub fn set_entries<K: 'static, V: 'static>(
489        &mut self,
490        keys: &impl DeserializeParam<K>,
491        values: &impl DeserializeParam<V>,
492    ) -> &mut Self {
493        self.entries = Some(Entries {
494            keys: ChildDescription::new(keys, true),
495            values: ChildDescription::new(values, true),
496            sep: None,
497        });
498        self
499    }
500
501    pub(crate) fn set_entries_sep(&mut self, entries: PatternDisplay, kv: PatternDisplay) {
502        if let Some(items) = &mut self.entries {
503            items.sep = Some((entries, kv));
504        }
505    }
506
507    /// Adds a fallback deserializer description.
508    pub fn set_fallback<T: 'static>(&mut self, fallback: &impl DeserializeParam<T>) {
509        self.fallback = Some(ChildDescription::new(fallback, false));
510    }
511}
512
513impl fmt::Display for TypeDescription {
514    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
515        if let Some(description) = &self.details {
516            write!(formatter, ", {description}")?;
517        }
518        if let Some(unit) = self.unit {
519            write!(formatter, " [unit: {unit}]")?;
520        }
521        Ok(())
522    }
523}
524
525/// Mention of a nested configuration within a configuration.
526#[derive(Debug, Clone, Copy)]
527pub struct NestedConfigMetadata {
528    /// Name of the config in config sources. Empty for flattened configs. Not necessarily the Rust field name!
529    pub name: &'static str,
530    /// Aliases for the config. Cannot be present for flattened configs.
531    pub aliases: &'static [(&'static str, AliasOptions)],
532    /// Name of the config field in Rust code.
533    pub rust_field_name: &'static str,
534    /// Tag variant in the enclosing [`ConfigMetadata`] that enables this parameter. `None` means that the parameter is unconditionally enabled.
535    pub tag_variant: Option<&'static ConfigVariant>,
536    /// Config metadata.
537    pub meta: &'static ConfigMetadata,
538}
539
540/// Unit of time measurement.
541///
542/// # Examples
543///
544/// You can use multiplication to define durations (e.g., for parameter values):
545///
546/// ```
547/// # use std::time::Duration;
548/// # use smart_config::metadata::TimeUnit;
549/// let dur = 5 * TimeUnit::Hours;
550/// assert_eq!(dur, Duration::from_secs(5 * 3_600));
551/// ```
552#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
553#[non_exhaustive]
554pub enum TimeUnit {
555    /// Millisecond (0.001 seconds).
556    Millis,
557    /// Base unit – second.
558    Seconds,
559    /// Minute (60 seconds).
560    Minutes,
561    /// Hour (3,600 seconds).
562    Hours,
563    /// Day (86,400 seconds).
564    Days,
565    /// Week (7 days).
566    Weeks,
567    // No larger units since they are less useful and may be ambiguous (e.g., is a month 30 days? is a year 365 days or 365.25...)
568}
569
570impl TimeUnit {
571    pub(crate) fn plural(self) -> &'static str {
572        match self {
573            TimeUnit::Millis => "milliseconds",
574            TimeUnit::Seconds => "seconds",
575            TimeUnit::Minutes => "minutes",
576            TimeUnit::Hours => "hours",
577            TimeUnit::Days => "days",
578            TimeUnit::Weeks => "weeks",
579        }
580    }
581
582    /// Multiplies this time unit by the specified factor.
583    pub fn checked_mul(self, factor: u64) -> Option<Duration> {
584        Some(match self {
585            Self::Millis => Duration::from_millis(factor),
586            Self::Seconds => Duration::from_secs(factor),
587            Self::Minutes => {
588                let val = factor.checked_mul(60)?;
589                Duration::from_secs(val)
590            }
591            Self::Hours => {
592                let val = factor.checked_mul(3_600)?;
593                Duration::from_secs(val)
594            }
595            Self::Days => {
596                let val = factor.checked_mul(86_400)?;
597                Duration::from_secs(val)
598            }
599            Self::Weeks => {
600                let val = factor.checked_mul(86_400 * 7)?;
601                Duration::from_secs(val)
602            }
603        })
604    }
605}
606
607impl fmt::Display for TimeUnit {
608    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
609        formatter.write_str(self.plural())
610    }
611}
612
613impl From<TimeUnit> for Duration {
614    fn from(unit: TimeUnit) -> Self {
615        match unit {
616            TimeUnit::Millis => Duration::from_millis(1),
617            TimeUnit::Seconds => Duration::from_secs(1),
618            TimeUnit::Minutes => Duration::from_secs(60),
619            TimeUnit::Hours => Duration::from_secs(3_600),
620            TimeUnit::Days => Duration::from_secs(86_400),
621            TimeUnit::Weeks => Duration::from_secs(86_400 * 7),
622        }
623    }
624}
625
626/// Panics on overflow.
627impl ops::Mul<u64> for TimeUnit {
628    type Output = Duration;
629
630    fn mul(self, rhs: u64) -> Self::Output {
631        self.checked_mul(rhs)
632            .unwrap_or_else(|| panic!("Integer overflow getting {rhs} * {self}"))
633    }
634}
635
636/// Panics on overflow.
637impl ops::Mul<TimeUnit> for u64 {
638    type Output = Duration;
639
640    fn mul(self, rhs: TimeUnit) -> Self::Output {
641        rhs.checked_mul(self)
642            .unwrap_or_else(|| panic!("Integer overflow getting {self} * {rhs}"))
643    }
644}
645
646/// Unit of byte size measurement.
647#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
648#[non_exhaustive]
649pub enum SizeUnit {
650    /// Base unit – bytes.
651    Bytes,
652    /// Binary kilobyte (aka kibibyte) = 1,024 bytes.
653    KiB,
654    /// Binary megabyte (aka mibibyte) = 1,048,576 bytes.
655    MiB,
656    /// Binary gigabyte (aka gibibyte) = 1,073,741,824 bytes.
657    GiB,
658}
659
660impl SizeUnit {
661    pub(crate) const fn as_str(self) -> &'static str {
662        match self {
663            Self::Bytes => "bytes",
664            Self::KiB => "kilobytes",
665            Self::MiB => "megabytes",
666            Self::GiB => "gigabytes",
667        }
668    }
669
670    pub(crate) const fn value_in_unit(self) -> u64 {
671        match self {
672            Self::Bytes => 1,
673            Self::KiB => 1_024,
674            Self::MiB => 1_024 * 1_024,
675            Self::GiB => 1_024 * 1_024 * 1_024,
676        }
677    }
678}
679
680impl fmt::Display for SizeUnit {
681    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
682        formatter.write_str(self.as_str())
683    }
684}
685
686/// Unit of ether amount measurement.
687#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
688#[non_exhaustive]
689pub enum EtherUnit {
690    /// Smallest unit of measurement.
691    Wei,
692    /// `10^9` wei.
693    Gwei,
694    /// `10^18` wei.
695    Ether,
696}
697
698impl fmt::Display for EtherUnit {
699    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
700        formatter.write_str(self.as_str())
701    }
702}
703
704impl EtherUnit {
705    pub(crate) const fn value_in_unit(self) -> u128 {
706        match self {
707            Self::Wei => 1,
708            Self::Gwei => 1_000_000_000,
709            Self::Ether => 1_000_000_000_000_000_000,
710        }
711    }
712
713    pub(crate) const fn as_str(self) -> &'static str {
714        match self {
715            Self::Wei => "wei",
716            Self::Gwei => "gwei",
717            Self::Ether => "ether",
718        }
719    }
720}
721
722/// General unit of measurement.
723#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
724#[non_exhaustive]
725pub enum UnitOfMeasurement {
726    /// Unit of time measurement.
727    Time(TimeUnit),
728    /// Unit of byte size measurement.
729    ByteSize(SizeUnit),
730    /// Unit of ether amount measurement.
731    Ether(EtherUnit),
732}
733
734impl fmt::Display for UnitOfMeasurement {
735    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
736        match self {
737            Self::Time(unit) => fmt::Display::fmt(unit, formatter),
738            Self::ByteSize(unit) => fmt::Display::fmt(unit, formatter),
739            Self::Ether(unit) => fmt::Display::fmt(unit, formatter),
740        }
741    }
742}
743
744impl From<TimeUnit> for UnitOfMeasurement {
745    fn from(unit: TimeUnit) -> Self {
746        Self::Time(unit)
747    }
748}
749
750impl From<SizeUnit> for UnitOfMeasurement {
751    fn from(unit: SizeUnit) -> Self {
752        Self::ByteSize(unit)
753    }
754}
755
756impl From<EtherUnit> for UnitOfMeasurement {
757    fn from(unit: EtherUnit) -> Self {
758        Self::Ether(unit)
759    }
760}