grixy 0.6.0

Zero-cost 2D grids for embedded systems and graphics
Documentation
#[cfg(feature = "serde")]
extern crate alloc;

#[cfg(feature = "serde")]
use crate::buf::GridBuf;
#[cfg(feature = "serde")]
use crate::ops::layout;
#[cfg(feature = "serde")]
use core::marker::PhantomData;
#[cfg(feature = "serde")]
use serde::{
    Deserialize, Deserializer, Serialize, Serializer,
    de::{self, Visitor},
};

#[cfg(feature = "serde")]
impl<T, B, L> Serialize for GridBuf<T, B, L>
where
    T: Serialize,
    B: AsRef<[T]>,
    L: layout::LinearLayout,
{
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        use serde::ser::SerializeStruct;
        let mut state = serializer.serialize_struct("GridBuf", 3)?;
        state.serialize_field("width", &self.width)?;
        state.serialize_field("height", &self.height)?;
        state.serialize_field("data", self.buffer.as_ref())?;
        state.end()
    }
}

#[cfg(feature = "serde")]
impl<'de, T, L> Deserialize<'de> for GridBuf<T, alloc::vec::Vec<T>, L>
where
    T: Deserialize<'de> + Default + Copy,
    L: layout::LinearLayout,
{
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        enum Field {
            Width,
            Height,
            Data,
        }

        impl<'de> Deserialize<'de> for Field {
            fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
                struct FieldVisitor;

                impl Visitor<'_> for FieldVisitor {
                    type Value = Field;

                    fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
                        f.write_str("`width`, `height`, or `data`")
                    }

                    fn visit_str<E: de::Error>(self, value: &str) -> Result<Field, E> {
                        match value {
                            "width" => Ok(Field::Width),
                            "height" => Ok(Field::Height),
                            "data" => Ok(Field::Data),
                            _ => Err(de::Error::unknown_field(value, FIELDS)),
                        }
                    }
                }

                deserializer.deserialize_identifier(FieldVisitor)
            }
        }

        const FIELDS: &[&str] = &["width", "height", "data"];

        struct GridBufVisitor<T, L>(PhantomData<(T, L)>);

        impl<'de, T, L> Visitor<'de> for GridBufVisitor<T, L>
        where
            T: Deserialize<'de> + Default + Copy,
            L: layout::LinearLayout,
        {
            type Value = GridBuf<T, alloc::vec::Vec<T>, L>;

            fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
                f.write_str("struct GridBuf")
            }

            fn visit_map<V: de::MapAccess<'de>>(self, mut map: V) -> Result<Self::Value, V::Error> {
                let mut width = None;
                let mut height = None;
                let mut data = None;

                while let Some(key) = map.next_key::<Field>()? {
                    match key {
                        Field::Width => {
                            if width.is_some() {
                                return Err(de::Error::duplicate_field("width"));
                            }
                            width = Some(map.next_value::<usize>()?);
                        }
                        Field::Height => {
                            if height.is_some() {
                                return Err(de::Error::duplicate_field("height"));
                            }
                            height = Some(map.next_value::<usize>()?);
                        }
                        Field::Data => {
                            if data.is_some() {
                                return Err(de::Error::duplicate_field("data"));
                            }
                            data = Some(map.next_value::<alloc::vec::Vec<T>>()?);
                        }
                    }
                }

                let width = width.ok_or_else(|| de::Error::missing_field("width"))?;
                let height = height.ok_or_else(|| de::Error::missing_field("height"))?;
                let data = data.ok_or_else(|| de::Error::missing_field("data"))?;

                if data.len() != width * height {
                    return Err(de::Error::custom(alloc::format!(
                        "data length {} does not match width {} * height {}",
                        data.len(),
                        width,
                        height
                    )));
                }

                Ok(GridBuf {
                    buffer: data,
                    width,
                    height,
                    _layout: PhantomData,
                    _element: PhantomData,
                })
            }
        }

        deserializer.deserialize_struct("GridBuf", FIELDS, GridBufVisitor::<T, L>(PhantomData))
    }
}

#[cfg(test)]
#[cfg(feature = "serde")]
mod tests {
    extern crate alloc;

    #[allow(unused_imports)]
    use crate::ops::{ExactSizeGrid as _, GridRead as _};
    use crate::{buf::GridBuf, core::Pos, ops::layout::RowMajor};

    #[test]
    fn serde_roundtrip() {
        let mut grid = GridBuf::<_, _, RowMajor>::new_filled(3, 3, 0u8);
        grid[Pos::new(1, 1)] = 42;

        let json = serde_json::to_string(&grid).unwrap();
        let deserialized: GridBuf<u8, alloc::vec::Vec<u8>, RowMajor> =
            serde_json::from_str(&json).unwrap();

        assert_eq!(deserialized.width(), 3);
        assert_eq!(deserialized.height(), 3);
        assert_eq!(deserialized.get(Pos::new(1, 1)), Some(&42));
        assert_eq!(deserialized.get(Pos::new(0, 0)), Some(&0));
    }
}