use serde_core::de::{self, Deserialize, Deserializer, Visitor};
use std::fmt;
use std::marker::PhantomData;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FlowSeq<T>(pub T);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FlowMap<T>(pub T);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DoubleQuoted<T>(pub T);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SingleQuoted<T>(pub T);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SpaceAfter<T>(pub T);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NullableTilde<T>(pub Option<T>);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Commented<T>(pub T, pub String);
#[cfg(feature = "garde")]
impl<T: garde::Validate> garde::Validate for Commented<T> {
type Context = T::Context;
fn validate_into(
&self,
ctx: &Self::Context,
parent: &mut dyn FnMut() -> garde::Path,
report: &mut garde::Report,
) {
self.0.validate_into(ctx, parent, report);
}
}
#[cfg(feature = "validator")]
impl<T: validator::Validate> validator::Validate for Commented<T> {
fn validate(&self) -> Result<(), validator::ValidationErrors> {
self.0.validate()
}
}
#[cfg(feature = "validator")]
impl<'v_a, T: validator::ValidateArgs<'v_a>> validator::ValidateArgs<'v_a> for Commented<T> {
type Args = T::Args;
fn validate_with_args(&self, args: Self::Args) -> Result<(), validator::ValidationErrors> {
self.0.validate_with_args(args)
}
}
impl<'de, T: Deserialize<'de>> Deserialize<'de> for FlowSeq<T> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
T::deserialize(deserializer).map(FlowSeq)
}
}
impl<'de, T: Deserialize<'de>> Deserialize<'de> for FlowMap<T> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
T::deserialize(deserializer).map(FlowMap)
}
}
impl<'de, T> Deserialize<'de> for DoubleQuoted<T>
where
T: Deserialize<'de> + AsRef<str>,
{
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
T::deserialize(deserializer).map(DoubleQuoted)
}
}
impl<'de, T> Deserialize<'de> for SingleQuoted<T>
where
T: Deserialize<'de> + AsRef<str>,
{
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
T::deserialize(deserializer).map(SingleQuoted)
}
}
impl<'de, T: Deserialize<'de>> Deserialize<'de> for Commented<T> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
struct CommentedVisitor<T>(PhantomData<T>);
impl<'de, T: Deserialize<'de>> Visitor<'de> for CommentedVisitor<T> {
type Value = Commented<T>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a commented YAML value")
}
fn visit_newtype_struct<D>(
self,
deserializer: D,
) -> std::result::Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
T::deserialize(deserializer).map(|value| Commented(value, String::new()))
}
fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Self::Value, A::Error>
where
A: de::SeqAccess<'de>,
{
let value = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
let comment = seq.next_element()?.unwrap_or_default();
Ok(Commented(value, comment))
}
}
deserializer.deserialize_newtype_struct("__yaml_commented", CommentedVisitor(PhantomData))
}
}
impl<'de, T: Deserialize<'de>> Deserialize<'de> for SpaceAfter<T> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
T::deserialize(deserializer).map(SpaceAfter)
}
}
impl<'de, T: Deserialize<'de>> Deserialize<'de> for NullableTilde<T> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
Option::<T>::deserialize(deserializer).map(NullableTilde)
}
}
#[cfg(all(test, feature = "deserialize"))]
mod tests {
use serde::Deserialize;
use crate::{
Commented, DoubleQuoted, FlowMap, FlowSeq, NullableTilde, SingleQuoted, SpaceAfter,
};
#[derive(Debug, Deserialize, PartialEq)]
struct WrappersDoc {
seq: FlowSeq<Vec<u32>>,
map: FlowMap<std::collections::BTreeMap<String, u32>>,
after: SpaceAfter<String>,
nullable_tilde_none: NullableTilde<String>,
nullable_tilde_some: NullableTilde<String>,
commented: Commented<bool>,
double_quoted: DoubleQuoted<String>,
single_quoted: SingleQuoted<String>,
}
#[test]
fn wrappers_remain_deserializable_without_serialize() {
let value: WrappersDoc = crate::from_str(
"seq: [1, 2]\nmap: {a: 1}\nafter: hello\nnullable_tilde_none: ~\nnullable_tilde_some: value\ncommented: true\ndouble_quoted: value\nsingle_quoted: value\n",
)
.unwrap();
assert_eq!(value.seq, FlowSeq(vec![1, 2]));
assert_eq!(value.after, SpaceAfter("hello".to_string()));
assert_eq!(value.nullable_tilde_none, NullableTilde(None));
assert_eq!(
value.nullable_tilde_some,
NullableTilde(Some("value".to_string()))
);
assert_eq!(value.commented, Commented(true, String::new()));
assert_eq!(value.double_quoted, DoubleQuoted("value".to_string()));
assert_eq!(value.single_quoted, SingleQuoted("value".to_string()));
assert_eq!(value.map.0.get("a"), Some(&1));
}
}