use std::{any, borrow::Cow, fmt, ops, time::Duration};
use self::_private::{BoxedDeserializer, BoxedVisitor};
use crate::{
de::{DeserializeParam, _private::ErasedDeserializer},
fallback::FallbackSource,
validation::Validate,
};
#[doc(hidden)] pub mod _private;
#[cfg(test)]
mod tests;
#[derive(Debug, Clone, Copy)]
#[cfg_attr(test, derive(PartialEq))]
#[non_exhaustive]
pub struct AliasOptions {
pub is_deprecated: bool,
}
impl Default for AliasOptions {
fn default() -> Self {
Self::new()
}
}
impl AliasOptions {
pub const fn new() -> Self {
AliasOptions {
is_deprecated: false,
}
}
#[must_use]
pub const fn deprecated(mut self) -> Self {
self.is_deprecated = true;
self
}
#[doc(hidden)] #[must_use]
pub fn combine(self, other: Self) -> Self {
Self {
is_deprecated: self.is_deprecated || other.is_deprecated,
}
}
}
#[derive(Debug, Clone)]
pub struct ConfigMetadata {
pub ty: RustType,
pub help: &'static str,
pub params: &'static [ParamMetadata],
pub tag: Option<ConfigTag>,
pub nested_configs: &'static [NestedConfigMetadata],
#[doc(hidden)] pub deserializer: BoxedDeserializer,
#[doc(hidden)] pub visitor: BoxedVisitor,
#[doc(hidden)] pub validations: &'static [&'static dyn Validate<dyn any::Any>],
}
#[derive(Debug, Clone, Copy)]
pub struct ConfigTag {
pub param: &'static ParamMetadata,
pub variants: &'static [ConfigVariant],
pub default_variant: Option<&'static ConfigVariant>,
}
#[derive(Debug, Clone, Copy)]
pub struct ConfigVariant {
pub name: &'static str,
pub aliases: &'static [&'static str],
pub rust_name: &'static str,
pub help: &'static str,
}
#[derive(Debug, Clone, Copy)]
pub struct ParamMetadata {
pub name: &'static str,
pub aliases: &'static [(&'static str, AliasOptions)],
pub help: &'static str,
pub rust_field_name: &'static str,
pub rust_type: RustType,
pub expecting: BasicTypes,
pub tag_variant: Option<&'static ConfigVariant>,
#[doc(hidden)] pub deserializer: &'static dyn ErasedDeserializer,
#[doc(hidden)] pub default_value: Option<fn() -> Box<dyn any::Any>>,
#[doc(hidden)] pub example_value: Option<fn() -> Box<dyn any::Any>>,
#[doc(hidden)]
pub fallback: Option<&'static dyn FallbackSource>,
}
impl ParamMetadata {
pub fn default_value(&self) -> Option<Box<dyn any::Any>> {
self.default_value.map(|value_fn| value_fn())
}
pub fn default_value_json(&self) -> Option<serde_json::Value> {
self.default_value()
.map(|val| self.deserializer.serialize_param(val.as_ref()))
}
pub fn example_value_json(&self) -> Option<serde_json::Value> {
let example = self.example_value?();
Some(self.deserializer.serialize_param(example.as_ref()))
}
pub fn type_description(&self) -> TypeDescription {
let mut description = TypeDescription::default();
self.deserializer.describe(&mut description);
description.rust_type = self.rust_type.name_in_code;
description
}
}
#[derive(Clone, Copy)]
pub struct RustType {
id: fn() -> any::TypeId,
name_in_code: &'static str,
}
impl fmt::Debug for RustType {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.name_in_code)
}
}
impl PartialEq for RustType {
fn eq(&self, other: &Self) -> bool {
(self.id)() == (other.id)()
}
}
impl RustType {
pub const fn of<T: 'static>(name_in_code: &'static str) -> Self {
Self {
id: any::TypeId::of::<T>,
name_in_code,
}
}
pub fn id(&self) -> any::TypeId {
(self.id)()
}
pub const fn name_in_code(&self) -> &'static str {
self.name_in_code
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct BasicTypes(u8);
impl BasicTypes {
pub const BOOL: Self = Self(1);
pub const INTEGER: Self = Self(2);
pub const FLOAT: Self = Self(4 | 2);
pub const STRING: Self = Self(8);
pub const ARRAY: Self = Self(16);
pub const OBJECT: Self = Self(32);
pub const ANY: Self = Self(63);
const COMPONENTS: &'static [(Self, &'static str)] = &[
(Self::BOOL, "Boolean"),
(Self::INTEGER, "integer"),
(Self::FLOAT, "float"),
(Self::STRING, "string"),
(Self::ARRAY, "array"),
(Self::OBJECT, "object"),
];
pub(crate) const fn from_raw(raw: u8) -> Self {
assert!(raw != 0, "Raw `BasicTypes` cannot be 0");
assert!(
raw <= Self::ANY.0,
"Unused set bits in `BasicTypes` raw value"
);
Self(raw)
}
#[doc(hidden)] pub const fn raw(self) -> u8 {
self.0
}
#[must_use]
pub const fn or(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
pub const fn contains(self, needle: Self) -> bool {
self.0 & needle.0 == needle.0
}
}
impl fmt::Display for BasicTypes {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
if *self == Self::ANY {
formatter.write_str("any")
} else {
let mut is_empty = true;
for &(component, name) in Self::COMPONENTS {
if self.contains(component) {
if !is_empty {
formatter.write_str(" | ")?;
}
formatter.write_str(name)?;
is_empty = false;
}
}
Ok(())
}
}
}
impl fmt::Debug for BasicTypes {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, formatter)
}
}
#[derive(Debug, Clone)]
struct ChildDescription {
expecting: BasicTypes,
description: Box<TypeDescription>,
}
impl ChildDescription {
fn new<T: 'static, De: DeserializeParam<T>>(deserializer: &De, set_type: bool) -> Self {
let mut description = Box::default();
deserializer.describe(&mut description);
if set_type {
description.rust_type = any::type_name::<T>();
}
Self {
expecting: De::EXPECTING,
description,
}
}
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
#[doc(hidden)] pub enum TypeSuffixes {
All,
DurationUnits,
SizeUnits,
}
#[derive(Debug, Clone, Default)]
pub struct TypeDescription {
rust_type: &'static str,
details: Option<Cow<'static, str>>,
unit: Option<UnitOfMeasurement>,
suffixes: Option<TypeSuffixes>,
pub(crate) is_secret: bool,
validations: Vec<String>,
deserialize_if: Option<String>,
items: Option<ChildDescription>,
entries: Option<(ChildDescription, ChildDescription)>,
fallback: Option<ChildDescription>,
}
impl TypeDescription {
#[doc(hidden)]
pub fn rust_type(&self) -> &str {
self.rust_type
}
pub fn details(&self) -> Option<&str> {
self.details.as_deref()
}
pub fn unit(&self) -> Option<UnitOfMeasurement> {
self.unit
}
#[doc(hidden)] pub fn suffixes(&self) -> Option<TypeSuffixes> {
self.suffixes
}
#[doc(hidden)] pub fn validations(&self) -> &[String] {
&self.validations
}
#[doc(hidden)] pub fn deserialize_if(&self) -> Option<&str> {
self.deserialize_if.as_deref()
}
pub fn items(&self) -> Option<(BasicTypes, &Self)> {
self.items
.as_ref()
.map(|child| (child.expecting, &*child.description))
}
pub fn keys(&self) -> Option<(BasicTypes, &Self)> {
let keys = &self.entries.as_ref()?.0;
Some((keys.expecting, &*keys.description))
}
pub fn values(&self) -> Option<(BasicTypes, &Self)> {
let keys = &self.entries.as_ref()?.1;
Some((keys.expecting, &*keys.description))
}
pub fn fallback(&self) -> Option<(BasicTypes, &Self)> {
let fallback = self.fallback.as_ref()?;
Some((fallback.expecting, &*fallback.description))
}
pub fn contains_secrets(&self) -> bool {
if self.is_secret {
return true;
}
if let Some(item) = &self.items {
if item.description.contains_secrets() {
return true;
}
}
if let Some((key, value)) = &self.entries {
if key.description.contains_secrets() {
return true;
}
if value.description.contains_secrets() {
return true;
}
}
false
}
pub fn set_details(&mut self, details: impl Into<Cow<'static, str>>) -> &mut Self {
self.details = Some(details.into());
self
}
pub fn set_unit(&mut self, unit: UnitOfMeasurement) -> &mut Self {
self.unit = Some(unit);
self
}
pub(crate) fn set_suffixes(&mut self, suffixes: TypeSuffixes) -> &mut Self {
self.suffixes = Some(suffixes);
self
}
pub fn set_validations<T>(&mut self, validations: &[&'static dyn Validate<T>]) -> &mut Self {
self.validations = validations.iter().map(ToString::to_string).collect();
self
}
pub fn set_deserialize_if<T>(&mut self, condition: &'static dyn Validate<T>) -> &mut Self {
self.deserialize_if = Some(condition.to_string());
self
}
pub fn set_secret(&mut self) -> &mut Self {
self.is_secret = true;
self
}
pub fn set_items<T: 'static>(&mut self, items: &impl DeserializeParam<T>) -> &mut Self {
self.items = Some(ChildDescription::new(items, true));
self
}
pub fn set_entries<K: 'static, V: 'static>(
&mut self,
keys: &impl DeserializeParam<K>,
values: &impl DeserializeParam<V>,
) -> &mut Self {
self.entries = Some((
ChildDescription::new(keys, true),
ChildDescription::new(values, true),
));
self
}
pub fn set_fallback<T: 'static>(&mut self, fallback: &impl DeserializeParam<T>) {
self.fallback = Some(ChildDescription::new(fallback, false));
}
}
impl fmt::Display for TypeDescription {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(description) = &self.details {
write!(formatter, ", {description}")?;
}
if let Some(unit) = self.unit {
write!(formatter, " [unit: {unit}]")?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
pub struct NestedConfigMetadata {
pub name: &'static str,
pub aliases: &'static [(&'static str, AliasOptions)],
pub rust_field_name: &'static str,
pub tag_variant: Option<&'static ConfigVariant>,
pub meta: &'static ConfigMetadata,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum TimeUnit {
Millis,
Seconds,
Minutes,
Hours,
Days,
Weeks,
}
impl TimeUnit {
pub(crate) fn plural(self) -> &'static str {
match self {
TimeUnit::Millis => "milliseconds",
TimeUnit::Seconds => "seconds",
TimeUnit::Minutes => "minutes",
TimeUnit::Hours => "hours",
TimeUnit::Days => "days",
TimeUnit::Weeks => "weeks",
}
}
pub fn checked_mul(self, factor: u64) -> Option<Duration> {
Some(match self {
Self::Millis => Duration::from_millis(factor),
Self::Seconds => Duration::from_secs(factor),
Self::Minutes => {
let val = factor.checked_mul(60)?;
Duration::from_secs(val)
}
Self::Hours => {
let val = factor.checked_mul(3_600)?;
Duration::from_secs(val)
}
Self::Days => {
let val = factor.checked_mul(86_400)?;
Duration::from_secs(val)
}
Self::Weeks => {
let val = factor.checked_mul(86_400 * 7)?;
Duration::from_secs(val)
}
})
}
}
impl fmt::Display for TimeUnit {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.plural())
}
}
impl From<TimeUnit> for Duration {
fn from(unit: TimeUnit) -> Self {
match unit {
TimeUnit::Millis => Duration::from_millis(1),
TimeUnit::Seconds => Duration::from_secs(1),
TimeUnit::Minutes => Duration::from_secs(60),
TimeUnit::Hours => Duration::from_secs(3_600),
TimeUnit::Days => Duration::from_secs(86_400),
TimeUnit::Weeks => Duration::from_secs(86_400 * 7),
}
}
}
impl ops::Mul<u64> for TimeUnit {
type Output = Duration;
fn mul(self, rhs: u64) -> Self::Output {
self.checked_mul(rhs)
.unwrap_or_else(|| panic!("Integer overflow getting {rhs} * {self}"))
}
}
impl ops::Mul<TimeUnit> for u64 {
type Output = Duration;
fn mul(self, rhs: TimeUnit) -> Self::Output {
rhs.checked_mul(self)
.unwrap_or_else(|| panic!("Integer overflow getting {self} * {rhs}"))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum SizeUnit {
Bytes,
KiB,
MiB,
GiB,
}
impl SizeUnit {
pub(crate) const fn plural(self) -> &'static str {
match self {
Self::Bytes => "bytes",
Self::KiB => "kilobytes",
Self::MiB => "megabytes",
Self::GiB => "gigabytes",
}
}
pub(crate) const fn bytes_in_unit(self) -> u64 {
match self {
Self::Bytes => 1,
Self::KiB => 1_024,
Self::MiB => 1_024 * 1_024,
Self::GiB => 1_024 * 1_024 * 1_024,
}
}
}
impl fmt::Display for SizeUnit {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.plural())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum UnitOfMeasurement {
Time(TimeUnit),
ByteSize(SizeUnit),
}
impl fmt::Display for UnitOfMeasurement {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Time(unit) => fmt::Display::fmt(unit, formatter),
Self::ByteSize(unit) => fmt::Display::fmt(unit, formatter),
}
}
}
impl From<TimeUnit> for UnitOfMeasurement {
fn from(unit: TimeUnit) -> Self {
Self::Time(unit)
}
}
impl From<SizeUnit> for UnitOfMeasurement {
fn from(unit: SizeUnit) -> Self {
Self::ByteSize(unit)
}
}