use schematic::schema::UnionType;
use schematic::{Schema, SchemaBuilder, Schematic};
use serde::{Deserialize, Deserializer, Serialize};
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(untagged)]
pub enum OneOrMany<T: Schematic> {
One(T),
Many(Vec<T>),
}
impl<T: Schematic> Default for OneOrMany<T> {
fn default() -> Self {
Self::Many(vec![])
}
}
impl<T: Schematic + Clone + PartialEq> OneOrMany<T> {
pub fn matches(&self, value: &T) -> bool {
match self {
Self::One(item) => item == value,
Self::Many(list) => list.contains(value),
}
}
}
impl<T: Schematic + Clone> OneOrMany<T> {
pub fn is_empty(&self) -> bool {
match self {
Self::One(_) => false,
Self::Many(list) => list.is_empty(),
}
}
pub fn to_list(&self) -> Vec<&T> {
match self {
Self::One(item) => vec![item],
Self::Many(list) => list.iter().collect(),
}
}
pub fn to_owned_list(&self) -> Vec<T> {
match self {
Self::One(item) => vec![item.to_owned()],
Self::Many(list) => list.to_owned(),
}
}
}
impl<T: Schematic> Schematic for OneOrMany<T> {
fn schema_name() -> Option<String> {
None
}
fn build_schema(mut schema: SchemaBuilder) -> Schema {
schema.union(UnionType::new_any([
schema.infer::<T>(),
schema.infer::<Vec<T>>(),
]))
}
}
impl<'de, T: Deserialize<'de> + Schematic> Deserialize<'de> for OneOrMany<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error as _;
let content = deserializer.deserialize_any(schematic::serde_content::ValueVisitor)?;
let mut errors: Vec<(&str, String)> = Vec::new();
match T::deserialize(
schematic::serde_content::Deserializer::new(content.clone())
.coerce_numbers()
.human_readable(),
) {
Ok(value) => return Ok(Self::One(value)),
Err(error) => errors.push(("One", error.to_string())),
};
match Vec::<T>::deserialize(
schematic::serde_content::Deserializer::new(content.clone())
.coerce_numbers()
.human_readable(),
) {
Ok(value) => return Ok(Self::Many(value)),
Err(error) => errors.push(("Many", error.to_string())),
};
let mut error_msg =
"failed to parse as a single value, or a list of multiple values:".to_owned();
for (variant_name, error) in &errors {
error_msg.push_str(&format!("\n- {}: {}", variant_name, error));
}
Err(D::Error::custom(error_msg))
}
}