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