Files
movies-diary/crates/config/src/lenient.rs

80 lines
2.0 KiB
Rust

//! Deserializers for values that arrive as strings from the environment but
//! may be native numbers/bools in a TOML file.
use std::fmt;
use std::marker::PhantomData;
use std::str::FromStr;
use serde::Deserializer;
use serde::de::{self, Visitor};
struct Number<T>(PhantomData<T>);
impl<T: FromStr> Visitor<'_> for Number<T> {
type Value = T;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a number")
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<T, E> {
v.trim()
.parse()
.map_err(|_| E::custom("expected a number in range"))
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<T, E> {
self.visit_str(&v.to_string())
}
fn visit_i64<E: de::Error>(self, v: i64) -> Result<T, E> {
self.visit_str(&v.to_string())
}
}
pub fn number<'de, D, T>(d: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: FromStr,
{
d.deserialize_any(Number(PhantomData))
}
struct Flag {
accepts_one: bool,
}
impl Visitor<'_> for Flag {
type Value = bool;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a boolean")
}
fn visit_bool<E: de::Error>(self, v: bool) -> Result<bool, E> {
Ok(v)
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<bool, E> {
Ok(v == "true" || (self.accepts_one && v == "1"))
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<bool, E> {
Ok(self.accepts_one && v == 1)
}
fn visit_i64<E: de::Error>(self, v: i64) -> Result<bool, E> {
Ok(self.accepts_one && v == 1)
}
}
/// `true` only for `true` or `1` (anything else is false, like the old readers).
pub fn true_or_one<'de, D: Deserializer<'de>>(d: D) -> Result<bool, D::Error> {
d.deserialize_any(Flag { accepts_one: true })
}
/// `true` only for `true`.
pub fn true_only<'de, D: Deserializer<'de>>(d: D) -> Result<bool, D::Error> {
d.deserialize_any(Flag { accepts_one: false })
}