use std::{borrow::Borrow, fmt::Display, marker::PhantomData, ops::Deref, str::FromStr};
use {
serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer},
serde_json::Number,
};
use crate::check::StringListCheck;
#[derive(Serialize, Clone, Debug, PartialEq, Eq)]
#[serde(transparent)]
pub struct VectorStringish {
vec: Vec<String>,
#[serde(skip)]
is_string: bool,
}
impl<'de> Deserialize<'de> for VectorStringish {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(VectorStringishVisitor)
}
}
struct VectorStringishVisitor;
impl<'de> Visitor<'de> for VectorStringishVisitor {
type Value = VectorStringish;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("expected an array of strings")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(VectorStringish {
vec: vec![v.to_owned()],
is_string: true,
})
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut v = vec![];
loop {
let n = seq.next_element()?;
if let Some(s) = n {
v.push(s);
} else {
break;
}
}
Ok(VectorStringish {
vec: v,
is_string: false,
})
}
}
impl From<String> for VectorStringish {
fn from(value: String) -> Self {
Self {
vec: vec![value],
is_string: false,
}
}
}
impl From<&str> for VectorStringish {
fn from(value: &str) -> Self {
Self {
vec: vec![value.to_owned()],
is_string: false,
}
}
}
impl From<Vec<String>> for VectorStringish {
fn from(value: Vec<String>) -> Self {
Self {
vec: value,
is_string: false,
}
}
}
impl From<VectorStringish> for Vec<String> {
fn from(value: VectorStringish) -> Self {
value.vec
}
}
impl From<&VectorStringish> for Vec<String> {
fn from(value: &VectorStringish) -> Self {
value.vec.to_owned()
}
}
impl VectorStringish {
pub fn into_vec(self) -> Vec<String> {
self.vec
}
pub fn vec(&self) -> &Vec<String> {
&self.vec
}
pub fn is_string(&self) -> bool {
self.is_string
}
}
impl StringListCheck for VectorStringish {
fn is_empty_or_any_empty_or_whitespace(&self) -> bool {
self.vec().is_empty_or_any_empty_or_whitespace()
}
fn is_ldh_string_list(&self) -> bool {
self.vec().is_ldh_string_list()
}
}
pub fn to_opt_vectorstringish(vec: Vec<String>) -> Option<VectorStringish> {
(!vec.is_empty()).then_some(VectorStringish::from(vec))
}
#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(untagged)]
enum StringishInner {
String(String),
Bool(bool),
Number(Number),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Stringish {
value: String,
is_number: bool,
is_bool: bool,
}
impl Serialize for Stringish {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.value)
}
}
impl<'de> Deserialize<'de> for Stringish {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let inner = StringishInner::deserialize(deserializer)?;
let (value, is_number, is_bool) = match inner {
StringishInner::String(s) => (s, false, false),
StringishInner::Bool(b) => (b.to_string(), false, true),
StringishInner::Number(n) => (n.to_string(), true, false),
};
Ok(Stringish {
value,
is_number,
is_bool,
})
}
}
impl From<String> for Stringish {
fn from(value: String) -> Self {
Self {
value,
is_number: false,
is_bool: false,
}
}
}
impl From<&str> for Stringish {
fn from(value: &str) -> Self {
Self {
value: value.to_owned(),
is_number: false,
is_bool: false,
}
}
}
impl From<Stringish> for String {
fn from(value: Stringish) -> Self {
value.value
}
}
impl Display for Stringish {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.value)
}
}
impl Deref for Stringish {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl AsRef<str> for Stringish {
fn as_ref(&self) -> &str {
&self.value
}
}
impl Borrow<str> for Stringish {
fn borrow(&self) -> &str {
&self.value
}
}
impl PartialEq<str> for Stringish {
fn eq(&self, other: &str) -> bool {
self.value == other
}
}
impl PartialEq<&str> for Stringish {
fn eq(&self, other: &&str) -> bool {
self.value == *other
}
}
impl PartialEq<String> for Stringish {
fn eq(&self, other: &String) -> bool {
self.value == *other
}
}
impl Stringish {
pub fn is_number(&self) -> bool {
self.is_number
}
pub fn is_bool(&self) -> bool {
self.is_bool
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(untagged)]
enum BoolishInner {
Bool(bool),
String(String),
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(transparent)]
pub struct Boolish {
inner: BoolishInner,
}
impl From<bool> for Boolish {
fn from(value: bool) -> Self {
Self {
inner: BoolishInner::Bool(value),
}
}
}
impl Display for Boolish {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.into_bool())
}
}
impl Boolish {
pub fn into_bool(&self) -> bool {
match &self.inner {
BoolishInner::Bool(value) => *value,
BoolishInner::String(value) => Boolish::is_true(value),
}
}
pub fn is_string(&self) -> bool {
match &self.inner {
BoolishInner::Bool(_) => false,
BoolishInner::String(_) => true,
}
}
fn is_true(value: &str) -> bool {
let s = value.trim().to_lowercase();
s == "true" || s == "t" || s == "yes" || s == "y"
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(untagged)]
enum NumberishInner {
Number(Number),
String(String),
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(transparent)]
pub struct Numberish<T> {
inner: NumberishInner,
phantom: PhantomData<T>,
}
impl<T> From<T> for Numberish<T>
where
Number: From<T>,
{
fn from(value: T) -> Self {
Self {
inner: NumberishInner::Number(Number::from(value)),
phantom: PhantomData,
}
}
}
impl<T> Display for Numberish<T>
where
Number: From<T>,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
self.as_u64()
.map_or("RANGE_ERROR".to_string(), |u| u.to_string())
)
}
}
impl<T> Numberish<T>
where
Number: From<T>,
{
pub fn is_string(&self) -> bool {
match &self.inner {
NumberishInner::Number(_) => false,
NumberishInner::String(_) => true,
}
}
pub fn as_u64(&self) -> Option<u64> {
match &self.inner {
NumberishInner::Number(n) => n.as_u64(),
NumberishInner::String(s) => Number::from_str(s).ok()?.as_u64(),
}
}
pub fn as_u32(&self) -> Option<u32> {
match &self.inner {
NumberishInner::Number(n) => n.as_u64()?.try_into().ok(),
NumberishInner::String(s) => Number::from_str(s).ok()?.as_u64()?.try_into().ok(),
}
}
pub fn as_u16(&self) -> Option<u16> {
match &self.inner {
NumberishInner::Number(n) => n.as_u64()?.try_into().ok(),
NumberishInner::String(s) => Number::from_str(s).ok()?.as_u64()?.try_into().ok(),
}
}
pub fn as_u8(&self) -> Option<u8> {
match &self.inner {
NumberishInner::Number(n) => n.as_u64()?.try_into().ok(),
NumberishInner::String(s) => Number::from_str(s).ok()?.as_u64()?.try_into().ok(),
}
}
}
#[cfg(test)]
mod tests {
use {
super::*,
serde_json::{from_str, to_string},
};
#[test]
fn test_vectorstringish_serialize_many() {
let many = VectorStringish::from(vec!["one".to_string(), "two".to_string()]);
let serialized = to_string(&many).unwrap();
assert_eq!(serialized, r#"["one","two"]"#);
}
#[test]
fn test_vectorstringish_serialize_one() {
let one = VectorStringish::from("one".to_string());
let serialized = to_string(&one).unwrap();
assert_eq!(serialized, r#"["one"]"#);
}
#[test]
fn test_vectorstringish_deserialize_many() {
let json_str = r#"["one","two"]"#;
let deserialized: VectorStringish = from_str(json_str).unwrap();
assert_eq!(
deserialized.vec(),
&vec!["one".to_string(), "two".to_string()]
);
assert!(!deserialized.is_string())
}
#[test]
fn test_vectorstringish_deserialize_one() {
let json_str = r#""one""#;
let deserialized: VectorStringish = from_str(json_str).unwrap();
assert_eq!(deserialized.vec(), &vec!["one".to_string()]);
assert!(deserialized.is_string())
}
#[test]
fn test_stringish_serialize() {
let a_string = Stringish::from("one");
let serialized = to_string(&a_string).unwrap();
assert_eq!(serialized, r#""one""#);
}
#[test]
fn test_string_from_stringish() {
let stringish = Stringish::from("hello");
let s = String::from(stringish);
assert_eq!(s, "hello");
}
#[test]
fn test_stringish_deref() {
let stringish = Stringish::from("hello");
let s: &str = &stringish;
assert_eq!(s, "hello");
assert_eq!(stringish.len(), 5);
}
#[test]
fn test_stringish_more_traits() {
let stringish = Stringish::from("hello");
assert_eq!(stringish.as_ref(), "hello");
assert_eq!(stringish.borrow() as &str, "hello");
assert!(stringish == "hello");
assert!(stringish == *"hello");
assert!(stringish == *"hello");
}
#[test]
fn test_stringish_serialize_number() {
let deserialized: Stringish = from_str("123").unwrap();
let serialized = to_string(&deserialized).unwrap();
assert_eq!(serialized, r#""123""#);
}
#[test]
fn test_stringish_serialize_bool() {
let deserialized: Stringish = from_str("true").unwrap();
let serialized = to_string(&deserialized).unwrap();
assert_eq!(serialized, r#""true""#);
}
#[test]
fn test_stringish_deserialize_string() {
let json_str = r#""one""#;
let deserialized: Stringish = from_str(json_str).unwrap();
assert_eq!(deserialized, Stringish::from("one"));
assert!(!deserialized.is_bool());
assert!(!deserialized.is_number());
}
#[test]
fn test_stringish_deserialize_bool() {
let json_str = r#"true"#;
let deserialized: Stringish = from_str(json_str).unwrap();
assert_eq!(deserialized.to_string(), "true");
assert!(deserialized.is_bool());
assert!(!deserialized.is_number());
}
#[test]
fn test_stringish_deserialize_number() {
let json_str = r#"1234"#;
let deserialized: Stringish = from_str(json_str).unwrap();
assert_eq!(deserialized.to_string(), "1234");
assert!(!deserialized.is_bool());
assert!(deserialized.is_number());
}
#[test]
fn test_boolish_serialize_bool() {
let b = Boolish::from(true);
let serialized = to_string(&b).unwrap();
assert_eq!(serialized, "true");
}
#[test]
fn test_boolish_deserialize_bool_true() {
let json_str = "true";
let deserialized: Boolish = from_str(json_str).unwrap();
assert!(deserialized.into_bool());
assert!(!deserialized.is_string());
}
#[test]
fn test_boolish_deserialize_bool_false() {
let json_str = "false";
let deserialized: Boolish = from_str(json_str).unwrap();
assert!(!deserialized.into_bool());
assert!(!deserialized.is_string());
}
#[test]
fn test_boolish_deserialize_string_true() {
let json_str = r#""true""#;
let deserialized: Boolish = from_str(json_str).unwrap();
assert!(deserialized.into_bool());
assert!(deserialized.is_string());
}
#[test]
fn test_boolish_deserialize_string_false() {
let json_str = r#""false""#;
let deserialized: Boolish = from_str(json_str).unwrap();
assert!(!deserialized.into_bool());
assert!(deserialized.is_string());
}
#[test]
fn test_boolish_is_true() {
let true_values = ["true", "t", "yes", "y", " True ", " T ", " Yes ", " Y "];
for value in true_values {
assert!(Boolish::is_true(value));
}
}
#[test]
fn test_boolish_is_false() {
let false_values = ["false", "f", "no", "n", "False", "blah", "1", "0", ""];
for value in false_values {
assert!(!Boolish::is_true(value));
}
}
#[test]
fn test_boolish_from_bool() {
assert!(Boolish::from(true).into_bool());
assert!(!Boolish::from(false).into_bool());
}
#[test]
fn test_numberish_serialize_number() {
let n = Numberish::<u32>::from(123);
let serialized = to_string(&n).unwrap();
assert_eq!(serialized, "123");
}
#[test]
fn test_numberish_deserialize_number() {
let json_str = "123";
let deserialized: Numberish<u32> = from_str(json_str).unwrap();
assert_eq!(deserialized.as_u32(), Some(123));
assert!(!deserialized.is_string());
}
#[test]
fn test_numberish_deserialize_string() {
let json_str = r#""123""#;
let deserialized: Numberish<u32> = from_str(json_str).unwrap();
assert_eq!(deserialized.as_u32(), Some(123));
assert!(deserialized.is_string());
}
#[test]
fn test_numberish_as_u64_number() {
let n = Numberish::from(123u64);
let result = n.as_u64();
assert_eq!(result, Some(123));
}
#[test]
fn test_numberish_as_u64_string_invalid() {
let n = Numberish {
inner: NumberishInner::String("abc".to_string()),
phantom: PhantomData::<u64>,
};
let result = n.as_u64();
assert_eq!(result, None);
}
#[test]
fn test_numberish_as_smaller_types() {
let n = Numberish::from(123u64);
assert_eq!(n.as_u32(), Some(123));
assert_eq!(n.as_u16(), Some(123));
assert_eq!(n.as_u8(), Some(123));
let n = Numberish::from(u32::MAX as u64 + 1);
assert_eq!(n.as_u32(), None);
assert_eq!(n.as_u16(), None);
assert_eq!(n.as_u8(), None);
let n = Numberish {
inner: NumberishInner::String("123".to_string()),
phantom: PhantomData::<u64>,
};
assert_eq!(n.as_u32(), Some(123));
assert_eq!(n.as_u16(), Some(123));
assert_eq!(n.as_u8(), Some(123));
let n = Numberish {
inner: NumberishInner::String((u32::MAX as u64 + 1).to_string()),
phantom: PhantomData::<u64>,
};
assert_eq!(n.as_u32(), None);
}
#[test]
fn test_numberish_display_number() {
let n = Numberish::<u32>::from(123);
assert_eq!(format!("{}", n), "123");
}
#[test]
fn test_numberish_display_string_valid() {
let n = Numberish {
inner: NumberishInner::String("123".to_string()),
phantom: PhantomData::<u32>,
};
assert_eq!(format!("{}", n), "123");
}
#[test]
fn test_numberish_display_string_invalid() {
let n = Numberish {
inner: NumberishInner::String("abc".to_string()),
phantom: PhantomData::<u32>,
};
assert_eq!(format!("{}", n), "RANGE_ERROR");
}
}