sashite-feen 0.2.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.
///
/// Serialization is fallible in the same way [`crate::encode`] is: a `Qi` whose
/// geometry FEEN cannot spell, or whose canonical form would exceed
/// [`crate::MAX_STRING_LENGTH`], is reported as a serializer error rather than
/// written out as a string this crate's own `deserialize` would refuse.
///
/// ```
/// use sashite_feen::sashite_epin::Identifier as Piece;
/// use sashite_feen::sashite_qi::Qi;
/// use sashite_feen::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::{ser, 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
    ///
    /// Fails if the position has no canonical FEEN form (see
    /// [`EncodeError`](crate::EncodeError)) — writing one out anyway would emit
    /// a string [`deserialize`] itself rejects — and propagates any error
    /// returned by the serializer.
    pub fn serialize<S: Serializer>(
        position: &Qi<Piece, Style>,
        serializer: S,
    ) -> Result<S::Ok, S::Error> {
        let feen = encode(position).map_err(ser::Error::custom)?;
        serializer.serialize_str(&feen)
    }

    /// 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)
        }
    }
}