compose_yml 0.0.59

Parse, manipulate and serialize docker-compose.yml in a strongly-typed fashion
Documentation
//! Tools for working with fields that might contain true, or might
//! contain a struct.

use serde::de::{self, Deserialize, Deserializer};
use serde::ser::{Serialize, Serializer};
use std::fmt;
use std::marker::PhantomData;

/// Handle a value which may either a struct that deserializes as type `T`,
/// or a bare value `true` indicating that we should construct a type `T`
/// using `Default::default()`.  We do this in a clever way that allows us
/// to be generic over multiple such types, and which allows us to use the
/// structure deserealization code automatically generated by serde.
pub fn deserialize_true_or_struct<'de, T, D>(d: D) -> Result<T, D::Error>
where
    T: Deserialize<'de> + Default,
    D: Deserializer<'de>,
{
    /// Declare an internal visitor type to handle our input.
    struct TrueOrStruct<T>(PhantomData<T>);

    impl<'de, T> de::Visitor<'de> for TrueOrStruct<T>
    where
        T: Deserialize<'de> + Default,
    {
        type Value = T;

        fn visit_bool<E>(self, value: bool) -> Result<T, E>
        where
            E: de::Error,
        {
            if value {
                Ok(Default::default())
            } else {
                Err(de::Error::custom(
                    "expected true or struct, got false".to_owned(),
                ))
            }
        }

        fn visit_map<M>(self, visitor: M) -> Result<T, M::Error>
        where
            M: de::MapAccess<'de>,
        {
            let mvd = de::value::MapAccessDeserializer::new(visitor);
            Deserialize::deserialize(mvd)
        }

        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(formatter, "a map or a boolean")
        }
    }

    d.deserialize_any(TrueOrStruct(PhantomData))
}

/// Like `opt_true_or_struct`, but it also handles the case where the
/// value is optional.
///
/// TODO MED: Deduplicate with `deserialize_opt_string_or_struct` (not as
/// easy as it looks).
pub fn deserialize_opt_true_or_struct<'de, T, D>(d: D) -> Result<Option<T>, D::Error>
where
    T: Deserialize<'de> + Default,
    D: Deserializer<'de>,
{
    /// Declare an internal visitor type to handle our input.
    struct OptTrueOrStruct<T>(PhantomData<T>);

    impl<'de, T> de::Visitor<'de> for OptTrueOrStruct<T>
    where
        T: Deserialize<'de> + Default,
    {
        type Value = Option<T>;

        fn visit_none<E>(self) -> Result<Self::Value, E>
        where
            E: de::Error,
        {
            Ok(None)
        }

        fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
        where
            D: Deserializer<'de>,
        {
            deserialize_true_or_struct(deserializer).map(Some)
        }

        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(formatter, "a map or a list of strings")
        }
    }

    d.deserialize_option(OptTrueOrStruct(PhantomData))
}

/// Serialize the specified value as `true` if it is equal to
/// `Default::default()`, and a struct otherwise.
pub fn serialize_true_or_struct<T, S>(
    value: &T,
    serializer: S,
) -> Result<S::Ok, S::Error>
where
    T: Serialize + Default + Eq,
    S: Serializer,
{
    if value == &Default::default() {
        serializer.serialize_bool(true)
    } else {
        value.serialize(serializer)
    }
}

/// Like `serialize_true_or_struct`, but can also handle missing values.
///
/// TODO MED: Deduplicate with `serialize_opt_string_or_struct` (not as
/// easy as it looks).
pub fn serialize_opt_true_or_struct<T, S>(
    value: &Option<T>,
    serializer: S,
) -> Result<S::Ok, S::Error>
where
    T: Serialize + Default + Eq,
    S: Serializer,
{
    /// A fun little trick: We need to pass `value` to to `serialize_some`,
    /// but we don't want `serialize_some` to call the normal `serialize`
    /// method on it.  So we define a local wrapper type that overrides the
    /// serialization.  This is one of the more subtle tricks of generic
    /// programming in Rust: using a "newtype" wrapper struct to override
    /// how a trait is applied to a class.
    struct Wrap<'a, T>(&'a T)
    where
        T: Serialize + Default + Eq;

    impl<'a, T> Serialize for Wrap<'a, T>
    where
        T: 'a + Serialize + Default + Eq,
    {
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: Serializer,
        {
            match *self {
                Wrap(v) => serialize_true_or_struct(v, serializer),
            }
        }
    }

    match *value {
        None => serializer.serialize_none(),
        Some(ref v) => serializer.serialize_some(&Wrap(v)),
    }
}