docker_compose 0.3.3

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

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

/// Handle a value which may either a struct that deserializes as type `T`,
/// or a bare string that can be turned into a type `T` using
/// `FromStr::from_str`.  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.
///
/// An earlier and much uglier version of this parser was the inspiration
/// for [this official `serde`
/// example](https://serde.rs/string-or-struct.html), which in turn forms
/// the basis of this code.
pub fn deserialize_string_or_struct<T, D>(d: &mut D) -> Result<T, D::Error>
    where T: Deserialize + FromStr,
          <T as FromStr>::Err: Display,
          D: Deserializer,
{
    struct StringOrStruct<T>(PhantomData<T>);

    impl<T> de::Visitor for StringOrStruct<T>
        where T: Deserialize + FromStr,
              <T as FromStr>::Err: Display,
    {
        type Value = T;

        fn visit_str<E>(&mut self, value: &str) -> Result<T, E>
            where E: de::Error
        {
            FromStr::from_str(value).map_err(|err| {
                // Just convert the underlying error type into a string and
                // pass it to serde as a custom error.
                de::Error::custom(format!("{}", err))
            })
        }

        fn visit_map<M>(&mut self, visitor: M) -> Result<T, M::Error>
            where M: de::MapVisitor
        {
            let mut mvd = de::value::MapVisitorDeserializer::new(visitor);
            Deserialize::deserialize(&mut mvd)
        }
    }

    d.deserialize(StringOrStruct(PhantomData))
}

/// Like `opt_string_or_struct`, but it also handles the case where the
/// value is optional.
///
/// We could probably make this more generic, supporting underlying
/// functions other than `string_or_struct`, but that's a project for
/// another day.
pub fn deserialize_opt_string_or_struct<T, D>(d: &mut D) ->
    Result<Option<T>, D::Error>
    where T: Deserialize + FromStr,
          <T as FromStr>::Err: Display,
          D: Deserializer,
{
    struct OptStringOrStruct<T>(PhantomData<T>);

    impl<T> de::Visitor for OptStringOrStruct<T>
        where T: Deserialize + FromStr,
              <T as FromStr>::Err: Display,
    {
        type Value = Option<T>;

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

        fn visit_some<D>(&mut self, deserializer: &mut D) ->
            Result<Self::Value, D::Error>
            where D: Deserializer
        {
            deserialize_string_or_struct(deserializer).map(Some)
        }
    }

    d.deserialize_option(OptStringOrStruct(PhantomData))
}

/// Some structs can serialized as a string, but only under certain
/// circumstances.
pub trait SerializeStringOrStruct: Serialize {
    /// Serialize either a string representation of this struct, or a full
    /// struct if the object cannot be represented as a string.
    fn serialize_string_or_struct<S>(&self, serializer: &mut S) ->
        Result<(), S::Error>
        where S: Serializer;
}

/// Serialize the specified value as a string if we can, and a struct
/// otherwise.
pub fn serialize_string_or_struct<T, S>(value: &T, serializer: &mut S) ->
    Result<(), S::Error>
    where T: SerializeStringOrStruct,
          S: Serializer
{
    value.serialize_string_or_struct(serializer)
}

/// Like `serialize_string_or_struct`, but can also handle missing values.
pub fn serialize_opt_string_or_struct<T, S>(value: &Option<T>,
                                            serializer: &mut S) ->
    Result<(), S::Error>
    where T: SerializeStringOrStruct,
          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: 'a + SerializeStringOrStruct;

    impl<'a, T> Serialize for Wrap<'a, T> where T: 'a + SerializeStringOrStruct {
        fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
            where S: Serializer
        {
            match self {
                &Wrap(v) => serialize_string_or_struct(v, serializer),
            }
        }
    }

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