sashite-feen 0.1.0

Field Expression Encoding Notation (FEEN): a compact, ASCII-only, no_std, zero-allocation validator and encoder for board-game positions in abstract strategy games, built on EPIN and SIN.
Documentation
//! `serde` support (behind the `serde` feature).
//!
//! A position is represented as its canonical FEEN string.
//!
//! - [`Feen`] is **serialize-only**: serializing a borrowing view streams its
//!   canonical form without materializing anything. A borrowing `Deserialize`
//!   would compile but fail at run time on any non-borrowing format, so it is
//!   deliberately not provided.
//! - The owned position type is [`sashite_qi::Qi`], whose own `serde` impls are
//!   *structural*. To (de)serialize a `Qi` as a compact canonical FEEN string
//!   instead, use the [`feen_string`] adapter with `#[serde(with = "…")]`.

use serde::{Serialize, Serializer};

use crate::feen::Feen;

impl Serialize for Feen<'_> {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        serializer.collect_str(self)
    }
}

/// A `#[serde(with = "…")]` adapter that (de)serializes a [`sashite_qi::Qi`]
/// position as its canonical FEEN string, rather than structurally.
///
/// ```ignore
/// use sashite_qi::Qi;
/// use sashite_epin::Identifier as Piece;
/// use sashite_sin::Identifier as Style;
///
/// #[derive(serde::Serialize, serde::Deserialize)]
/// struct Saved {
///     #[serde(with = "sashite_feen::feen_string")]
///     position: Qi<Piece, Style>,
/// }
/// ```
pub mod feen_string {
    use core::fmt;

    use serde::de::{self, Visitor};
    use serde::{Deserializer, Serializer};

    use sashite_epin::Identifier as Piece;
    use sashite_qi::Qi;
    use sashite_sin::Identifier as Style;

    use crate::encode::encode;
    use crate::feen::Feen;

    /// Serializes `position` as its canonical FEEN string.
    ///
    /// # Errors
    ///
    /// Propagates any error returned by the serializer.
    pub fn serialize<S: Serializer>(
        position: &Qi<Piece, Style>,
        serializer: S,
    ) -> Result<S::Ok, S::Error> {
        serializer.serialize_str(&encode(position))
    }

    /// Deserializes a canonical FEEN string into an owned [`Qi`] position.
    ///
    /// # Errors
    ///
    /// Returns a deserialization error if the string is not valid, canonical FEEN.
    pub fn deserialize<'de, D: Deserializer<'de>>(
        deserializer: D,
    ) -> Result<Qi<Piece, Style>, D::Error> {
        deserializer.deserialize_str(QiVisitor)
    }

    /// Visits a string and parses it into a [`Qi`] position.
    ///
    /// Only `visit_str` is implemented; serde's `visit_borrowed_str` and
    /// `visit_string` default to it, so borrowed and owned strings are accepted
    /// alike without forcing an allocation on borrowing formats.
    struct QiVisitor;

    impl Visitor<'_> for QiVisitor {
        type Value = Qi<Piece, Style>;

        fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            f.write_str("a canonical FEEN string")
        }

        fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
            Feen::parse(value)
                .map(|feen| feen.to_qi())
                .map_err(de::Error::custom)
        }
    }
}