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