use crate::{Error, Result};
pub trait Setting: Copy + Eq + Sized + 'static {
const SETTING: &'static str;
const ALL: &'static [Self];
fn as_str(&self) -> &'static str;
fn parse(raw: &str) -> Result<Self> {
let wanted = raw.trim().replace('_', "-");
Self::ALL
.iter()
.copied()
.find(|variant| variant.as_str().eq_ignore_ascii_case(&wanted))
.ok_or_else(|| {
Error::internal(format!(
"`{}` is not a valid {}; expected one of {}",
raw.trim(),
Self::SETTING,
Self::options()
))
})
}
fn options() -> String {
Self::ALL
.iter()
.map(|variant| format!("`{}`", variant.as_str()))
.collect::<Vec<_>>()
.join(", ")
}
}
#[macro_export]
macro_rules! setting_enum {
(
$(#[$enum_meta:meta])*
$vis:vis enum $name:ident: $setting:literal {
$(
$(#[$variant_meta:meta])*
$variant:ident = $wire:literal
),+ $(,)?
}
) => {
$(#[$enum_meta])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
$vis enum $name {
$(
$(#[$variant_meta])*
$variant,
)+
}
impl $crate::__private::serde::Serialize for $name {
fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
where
S: $crate::__private::serde::Serializer,
{
serializer.serialize_str(<Self as $crate::Setting>::as_str(self))
}
}
impl<'de> $crate::__private::serde::Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: $crate::__private::serde::Deserializer<'de>,
{
use $crate::__private::serde::de::Error as _;
let raw = <::std::string::String as $crate::__private::serde::Deserialize>::deserialize(
deserializer,
)?;
<Self as $crate::Setting>::parse(&raw)
.map_err(|e| D::Error::custom(e.message()))
}
}
impl $crate::Setting for $name {
const SETTING: &'static str = $setting;
const ALL: &'static [Self] = &[$(Self::$variant),+];
fn as_str(&self) -> &'static str {
match self {
$(Self::$variant => $wire,)+
}
}
}
impl $name {
$vis fn as_str(&self) -> &'static str {
<Self as $crate::Setting>::as_str(self)
}
}
impl ::std::fmt::Display for $name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
f.write_str(<Self as $crate::Setting>::as_str(self))
}
}
impl ::std::str::FromStr for $name {
type Err = $crate::Error;
fn from_str(raw: &str) -> ::std::result::Result<Self, Self::Err> {
<Self as $crate::Setting>::parse(raw)
}
}
impl ::std::convert::AsRef<str> for $name {
fn as_ref(&self) -> &str {
<Self as $crate::Setting>::as_str(self)
}
}
};
}
#[cfg(test)]
mod tests {
use super::*;
setting_enum! {
pub enum Driver: "cache driver" {
#[default]
Memory = "memory",
Redis = "redis",
RedisCluster = "redis-cluster",
}
}
#[test]
fn round_trips_through_its_wire_spelling() {
for driver in Driver::ALL {
assert_eq!(Driver::parse(driver.as_str()).unwrap(), *driver);
assert_eq!(driver.to_string(), driver.as_str());
}
}
#[test]
fn parsing_is_forgiving_about_case_whitespace_and_underscores() {
assert_eq!(Driver::parse(" redis-cluster \n").unwrap(), Driver::RedisCluster);
assert_eq!(Driver::parse("Redis-Cluster").unwrap(), Driver::RedisCluster);
assert_eq!(Driver::parse("REDIS_CLUSTER").unwrap(), Driver::RedisCluster);
}
#[test]
fn an_unknown_value_names_itself_and_lists_the_alternatives() {
let err = Driver::parse("redys").unwrap_err();
assert_eq!(
err.message(),
"`redys` is not a valid cache driver; expected one of `memory`, `redis`, `redis-cluster`"
);
}
#[test]
fn the_reported_value_is_what_was_written_not_what_was_normalised() {
let err = Driver::parse(" REDYS ").unwrap_err();
assert!(err.message().starts_with("`REDYS` is not"), "{}", err.message());
}
#[test]
fn an_empty_value_is_an_error_rather_than_the_default() {
assert!(Driver::parse("").is_err());
}
#[test]
fn serde_uses_the_same_spelling_as_the_parser() {
let json = serde_json::to_string(&Driver::RedisCluster).unwrap();
assert_eq!(json, "\"redis-cluster\"");
let back: Driver = serde_json::from_str(&json).unwrap();
assert_eq!(back, Driver::RedisCluster);
}
#[test]
fn the_default_is_the_marked_variant() {
assert_eq!(Driver::default(), Driver::Memory);
}
#[test]
fn from_str_is_the_same_parser() {
assert_eq!("redis".parse::<Driver>().unwrap(), Driver::Redis);
assert!("nope".parse::<Driver>().is_err());
}
#[test]
fn options_lists_every_variant_in_declaration_order() {
assert_eq!(Driver::options(), "`memory`, `redis`, `redis-cluster`");
}
}