spiral-contract 0.1.12

Language-neutral service wire values
Documentation
//! Typed request decoding that rejects unknown values and duplicate JSON keys.

use std::collections::BTreeMap;
use std::fmt::{self, Formatter};
use std::marker::PhantomData;

use serde::de::{Error, MapAccess, SeqAccess, Visitor};
use serde::{Deserialize, Deserializer};
use serde_json::{Map, Number, Value};
use uuid::Uuid;

use crate::safelong::{SafeLong, SafeUnsignedLong};

/// Decode a wire value using the request contract's closed set of fields and variants.
///
/// Generated objects implement this separately from their extensible response decoder.
pub trait DeserializeStrict<'de>: Sized {
    /// Deserialize a request value, including strict decoding of its children.
    fn deserialize_strict<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error>;
}

/// Select the strict request decoder for a wire type.
#[derive(Debug)]
pub struct Strict<T>(pub T);

impl<T: Default> Default for Strict<T> {
    fn default() -> Self {
        Self(T::default())
    }
}

impl<'de, T: DeserializeStrict<'de>> Deserialize<'de> for Strict<T> {
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        T::deserialize_strict(deserializer).map(Self)
    }
}

macro_rules! primitive {
    ($($ty:ty),* $(,)?) => {
        $(impl<'de> DeserializeStrict<'de> for $ty {
            fn deserialize_strict<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
                Self::deserialize(deserializer)
            }
        })*
    };
}

primitive!(String, bool, i32, u32, Uuid, SafeLong, SafeUnsignedLong);

impl<'de> DeserializeStrict<'de> for Value {
    fn deserialize_strict<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        UniqueValue::deserialize(deserializer).map(|value| value.0)
    }
}

impl<'de> DeserializeStrict<'de> for f64 {
    fn deserialize_strict<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        let value = Self::deserialize(deserializer)?;
        if value.is_finite() {
            Ok(value)
        } else {
            Err(D::Error::custom("expected a finite float64"))
        }
    }
}

impl<'de, T: DeserializeStrict<'de>> DeserializeStrict<'de> for Option<T> {
    fn deserialize_strict<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        Option::<Strict<T>>::deserialize(deserializer).map(|value| value.map(|value| value.0))
    }
}

impl<'de, T: DeserializeStrict<'de>> DeserializeStrict<'de> for Vec<T> {
    fn deserialize_strict<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        Vec::<Strict<T>>::deserialize(deserializer)
            .map(|values| values.into_iter().map(|value| value.0).collect())
    }
}

impl<'de, T: DeserializeStrict<'de>> DeserializeStrict<'de> for BTreeMap<String, T> {
    fn deserialize_strict<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        struct MapVisitor<T>(PhantomData<T>);
        impl<'de, T: DeserializeStrict<'de>> Visitor<'de> for MapVisitor<T> {
            type Value = BTreeMap<String, T>;

            fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
                formatter.write_str("a map with unique string keys")
            }

            fn visit_map<A: MapAccess<'de>>(self, mut access: A) -> Result<Self::Value, A::Error> {
                let mut values = BTreeMap::new();
                while let Some(key) = access.next_key::<String>()? {
                    if values.contains_key(&key) {
                        return Err(A::Error::custom(format!("duplicate key {key:?}")));
                    }
                    values.insert(key, access.next_value::<Strict<T>>()?.0);
                }
                Ok(values)
            }
        }
        deserializer.deserialize_map(MapVisitor(PhantomData))
    }
}

/// Decode a strict JSON request, rejecting duplicate keys at every nesting depth.
///
/// Parsing the complete input before typed decoding preserves duplicate detection
/// for union decoders that inspect a JSON object to select the payload type.
pub fn from_slice<T: for<'de> DeserializeStrict<'de>>(
    bytes: &[u8],
) -> Result<T, serde_json::Error> {
    let value = serde_json::from_slice::<UniqueValue>(bytes)?;
    serde_json::from_value::<Strict<T>>(value.0).map(|value| value.0)
}

struct UniqueValue(Value);

impl<'de> Deserialize<'de> for UniqueValue {
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        struct ValueVisitor;
        impl<'de> Visitor<'de> for ValueVisitor {
            type Value = UniqueValue;

            fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
                formatter.write_str("a JSON value without duplicate object keys")
            }

            fn visit_unit<E: Error>(self) -> Result<Self::Value, E> {
                Ok(UniqueValue(Value::Null))
            }

            fn visit_bool<E: Error>(self, value: bool) -> Result<Self::Value, E> {
                Ok(UniqueValue(Value::Bool(value)))
            }

            fn visit_i64<E: Error>(self, value: i64) -> Result<Self::Value, E> {
                Ok(UniqueValue(Value::Number(value.into())))
            }

            fn visit_u64<E: Error>(self, value: u64) -> Result<Self::Value, E> {
                Ok(UniqueValue(Value::Number(value.into())))
            }

            fn visit_f64<E: Error>(self, value: f64) -> Result<Self::Value, E> {
                Number::from_f64(value)
                    .map(|value| UniqueValue(Value::Number(value)))
                    .ok_or_else(|| E::custom("expected a finite JSON number"))
            }

            fn visit_str<E: Error>(self, value: &str) -> Result<Self::Value, E> {
                Ok(UniqueValue(Value::String(value.to_owned())))
            }

            fn visit_string<E: Error>(self, value: String) -> Result<Self::Value, E> {
                Ok(UniqueValue(Value::String(value)))
            }

            fn visit_seq<A: SeqAccess<'de>>(self, mut access: A) -> Result<Self::Value, A::Error> {
                let mut values = Vec::new();
                while let Some(value) = access.next_element::<UniqueValue>()? {
                    values.push(value.0);
                }
                Ok(UniqueValue(Value::Array(values)))
            }

            fn visit_map<A: MapAccess<'de>>(self, mut access: A) -> Result<Self::Value, A::Error> {
                let mut values = Map::new();
                while let Some(key) = access.next_key::<String>()? {
                    if values.contains_key(&key) {
                        return Err(A::Error::custom(format!("duplicate key {key:?}")));
                    }
                    values.insert(key, access.next_value::<UniqueValue>()?.0);
                }
                Ok(UniqueValue(Value::Object(values)))
            }
        }
        deserializer.deserialize_any(ValueVisitor)
    }
}

#[cfg(test)]
mod tests {
    use serde::{Deserialize, Deserializer};
    use serde_json::Value;

    use super::{DeserializeStrict, from_slice};

    // Union decoders inspect the discriminator through Value before decoding its payload.
    #[derive(Deserialize)]
    struct Union(Value);

    impl<'de> DeserializeStrict<'de> for Union {
        fn deserialize_strict<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
            Self::deserialize(deserializer)
        }
    }

    #[test]
    fn duplicate_keys_are_rejected_before_union_or_map_decoding() {
        for input in [
            r#"{"type":"table","type":"location"}"#,
            r#"{"type":"table","table":{"labels":{"team":"first","team":"second"}}}"#,
            r#"[{"nested":{"key":1,"key":2}}]"#,
            r#"{"key":1,"\u006bey":2}"#,
        ] {
            let error = from_slice::<Union>(input.as_bytes()).err().unwrap();
            assert!(error.to_string().contains("duplicate key"));
            assert!(from_slice::<Value>(input.as_bytes()).is_err());
        }
        let decoded =
            from_slice::<Union>(br#"{"type":"table","table":{"labels":{"team":"first"}}}"#)
                .unwrap();
        assert_eq!(decoded.0["table"]["labels"]["team"], "first");
        assert!(from_slice::<Union>(b"{} {}").is_err());
        assert_eq!(from_slice::<Value>(b"null").unwrap(), Value::Null);
        assert_eq!(from_slice::<Value>(b"[null, 1.5, true]").unwrap()[1], 1.5);
        assert!(from_slice::<Value>(b"NaN").is_err());
    }
}