stefans-utils 0.12.0

A collection of useful Rust utility functions, types, and traits.
Documentation
#[cfg(feature = "schemars")]
use schemars::{JsonSchema, json_schema};
use serde::de::{Error, MapAccess, Visitor};
use serde::ser::SerializeMap;
use serde::{Deserialize, Deserializer, Serialize};
use std::hash::Hash;
use std::marker::PhantomData;

#[derive(Debug, Clone, Copy, Eq, PartialOrd, Ord, Hash)]
pub struct SingleKVMap<Key, Value> {
    pub key: Key,
    pub value: Value,
}

impl<LeftKey: PartialEq<RightKey>, LeftValue: PartialEq<RightValue>, RightKey, RightValue>
    PartialEq<SingleKVMap<RightKey, RightValue>> for SingleKVMap<LeftKey, LeftValue>
{
    fn eq(&self, other: &SingleKVMap<RightKey, RightValue>) -> bool {
        self.key == other.key && self.value == other.value
    }
}

impl<Key: Serialize, Value: Serialize> Serialize for SingleKVMap<Key, Value> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let mut map = serializer.serialize_map(Some(1))?;
        map.serialize_entry(&self.key, &self.value)?;
        map.end()
    }
}

struct SingleKVMapVisitor<Key, Value>(PhantomData<(Key, Value)>);

impl<Key, Value> Default for SingleKVMapVisitor<Key, Value> {
    fn default() -> Self {
        Self(PhantomData)
    }
}

impl<'de, Key: Deserialize<'de>, Value: Deserialize<'de>> Visitor<'de>
    for SingleKVMapVisitor<Key, Value>
{
    type Value = SingleKVMap<Key, Value>;

    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
        formatter.write_str("a map with a single key-value")
    }

    fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error>
    where
        A::Error: Error,
    {
        let (key, value) = map
            .next_entry()?
            .ok_or(A::Error::missing_field("missing first (and only) entry"))?;

        if let Some(remaining) = map.size_hint()
            && remaining != 0
        {
            Err(A::Error::unknown_field(
                &format!("Found {remaining} remaining entries, expecting 0"),
                &[],
            ))
        } else {
            Ok(SingleKVMap::new(key, value))
        }
    }
}

impl<'de, Key: Deserialize<'de>, Value: Deserialize<'de>> Deserialize<'de>
    for SingleKVMap<Key, Value>
{
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        deserializer.deserialize_map(SingleKVMapVisitor::default())
    }
}

impl<Key, Value> SingleKVMap<Key, Value> {
    pub fn new(key: Key, value: Value) -> Self {
        Self { key, value }
    }

    pub fn from_entry(entry: (Key, Value)) -> Self {
        let (key, value) = entry;
        Self { key, value }
    }

    pub fn as_key(&self) -> &Key {
        &self.key
    }

    pub fn as_key_mut(&mut self) -> &mut Key {
        &mut self.key
    }

    pub fn into_key(self) -> Key {
        self.key
    }

    pub fn set_key(&mut self, new_key: Key) -> Key {
        std::mem::replace(&mut self.key, new_key)
    }

    pub fn as_value(&self) -> &Value {
        &self.value
    }

    pub fn as_value_mut(&mut self) -> &mut Value {
        &mut self.value
    }

    pub fn into_value(self) -> Value {
        self.value
    }

    pub fn set_value(&mut self, new_value: Value) -> Value {
        std::mem::replace(&mut self.value, new_value)
    }

    pub fn as_entry(&self) -> (&Key, &Value) {
        (&self.key, &self.value)
    }

    pub fn as_entry_mut(&mut self) -> (&mut Key, &mut Value) {
        (&mut self.key, &mut self.value)
    }

    pub fn into_entry(self) -> (Key, Value) {
        (self.key, self.value)
    }

    pub fn set_entry(&mut self, new_entry: (Key, Value)) -> (Key, Value) {
        let (new_key, new_value) = new_entry;
        (
            std::mem::replace(&mut self.key, new_key),
            std::mem::replace(&mut self.value, new_value),
        )
    }
}

impl<Key, Value> From<(Key, Value)> for SingleKVMap<Key, Value> {
    fn from(entry: (Key, Value)) -> Self {
        Self::from_entry(entry)
    }
}

impl<Key, Value> AsRef<SingleKVMap<Key, Value>> for SingleKVMap<Key, Value> {
    fn as_ref(&self) -> &SingleKVMap<Key, Value> {
        self
    }
}

#[cfg(feature = "schemars")]
impl<Key: JsonSchema, Value: JsonSchema> JsonSchema for SingleKVMap<Key, Value> {
    fn schema_name() -> std::borrow::Cow<'static, str> {
        format!("{}{}SingleKVMap", Key::schema_name(), Value::schema_name()).into()
    }

    fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
        let mut key_schema = generator.subschema_for::<Key>();
        let value_schema = generator.subschema_for::<Value>();

        key_schema.as_object_mut().map(|x| x.remove("type"));

        json_schema!({
            "type": "object",
            "propertyNames": key_schema,
            "additionalProperties": value_schema,
            "minProperties": 1,
            "maxProperties": 1,
        })
    }
}

#[cfg(test)]
mod tests {
    use pretty_assertions::assert_eq;
    #[cfg(feature = "schemars")]
    use schemars::schema_for;
    use serde_json::json;

    use super::*;

    #[derive(Debug, Serialize, Deserialize, PartialEq)]
    #[cfg_attr(feature = "schemars", derive(JsonSchema))]
    enum MyEnum {
        A,
        B,
    }

    #[cfg(feature = "schemars")]
    #[test]
    fn derive_json_schema() {
        assert_eq!(
            schema_for!(SingleKVMap<i32, String>),
            json_schema!({
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "title": "int32stringSingleKVMap",
                "type": "object",
                "additionalProperties": {
                    "type": "string"
                },
                "propertyNames": {
                    "format": "int32"
                },
                "minProperties": 1,
                "maxProperties": 1,
            })
        )
    }

    #[cfg(feature = "schemars")]
    #[test]
    fn derive_json_schema_enum() {
        assert_eq!(
            schema_for!(SingleKVMap<MyEnum, MyEnum>),
            json_schema!({
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "title": "MyEnumMyEnumSingleKVMap",
                "type": "object",
                "additionalProperties": {
                    "$ref": "#/$defs/MyEnum"
                },
                "propertyNames": {
                    "$ref": "#/$defs/MyEnum"
                },
                "minProperties": 1,
                "maxProperties": 1,
                "$defs": {
                    "MyEnum": {
                        "type": "string",
                        "enum": ["A", "B"]
                    }
                }
            })
        )
    }

    #[test]
    fn derive_serialize() {
        assert_eq!(
            serde_json::to_value(SingleKVMap::new(10, "hello")).unwrap(),
            json! ({
                "10": "hello"
            })
        )
    }

    #[test]
    fn derive_serialize_enum() {
        assert_eq!(
            serde_json::to_value(SingleKVMap::new(MyEnum::A, MyEnum::B)).unwrap(),
            json! ({
                "A": "B"
            })
        )
    }

    #[test]
    fn derive_deserialize() {
        assert_eq!(
            serde_json::from_value::<SingleKVMap<i32, String>>(json! ({
                "10": "hello"
            }))
            .unwrap(),
            SingleKVMap::new(10, "hello"),
        )
    }

    #[test]
    fn derive_deserialize_enum() {
        assert_eq!(
            serde_json::from_value::<SingleKVMap<MyEnum, MyEnum>>(json! ({
                "A": "B"
            }))
            .unwrap(),
            SingleKVMap::new(MyEnum::A, MyEnum::B),
        )
    }
}