titan-types 0.1.33

Types for Titan bitcoin and runes indexer
Documentation
use {
    crate::rune_type::Rune,
    borsh::{BorshDeserialize, BorshSerialize},
    serde::{Deserialize, Deserializer, Serialize, Serializer},
    std::{
        fmt::{self, Display, Formatter},
        io::{Read, Write},
        str::FromStr,
    },
};

#[derive(Copy, Clone, Debug, PartialEq, Ord, PartialOrd, Eq, Default)]
pub struct SpacedRune {
    pub rune: Rune,
    pub spacers: u32,
}

impl SpacedRune {
    pub fn new(rune: Rune, spacers: u32) -> Self {
        Self { rune, spacers }
    }
}

impl FromStr for SpacedRune {
    type Err = SpacedRuneError;

    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        let mut rune = String::new();
        let mut spacers = 0u32;

        for c in s.chars() {
            match c {
                'A'..='Z' => rune.push(c),
                '.' | '' => {
                    let flag = 1
                        << rune
                            .len()
                            .checked_sub(1)
                            .ok_or(SpacedRuneError::LeadingSpacer)?;
                    if spacers & flag != 0 {
                        return Err(SpacedRuneError::DoubleSpacer);
                    }
                    spacers |= flag;
                }
                _ => return Err(SpacedRuneError::Character(c)),
            }
        }

        if 32 - spacers.leading_zeros() >= rune.len().try_into().unwrap() {
            return Err(SpacedRuneError::TrailingSpacer);
        }

        Ok(SpacedRune {
            rune: rune.parse().map_err(SpacedRuneError::Rune)?,
            spacers,
        })
    }
}

impl Display for SpacedRune {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        let rune = self.rune.to_string();

        for (i, c) in rune.chars().enumerate() {
            write!(f, "{c}")?;

            if i < rune.len() - 1 && self.spacers & 1 << i != 0 {
                write!(f, "")?;
            }
        }

        Ok(())
    }
}

impl Serialize for SpacedRune {
    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

impl<'de> Deserialize<'de> for SpacedRune {
    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = <String as Deserialize>::deserialize(deserializer)?;
        Self::from_str(&s).map_err(serde::de::Error::custom)
    }
}

impl BorshSerialize for SpacedRune {
    fn serialize<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
        BorshSerialize::serialize(&self.rune.0, writer)?;
        BorshSerialize::serialize(&self.spacers, writer)
    }
}

impl BorshDeserialize for SpacedRune {
    fn deserialize_reader<R: Read>(reader: &mut R) -> std::io::Result<Self> {
        let rune_value = u128::deserialize_reader(reader)?;
        let spacers = u32::deserialize_reader(reader)?;
        Ok(SpacedRune {
            rune: Rune(rune_value),
            spacers,
        })
    }
}

#[derive(Debug, PartialEq)]
pub enum SpacedRuneError {
    LeadingSpacer,
    TrailingSpacer,
    DoubleSpacer,
    Character(char),
    Rune(crate::rune_type::RuneError),
}

impl Display for SpacedRuneError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match self {
            Self::Character(c) => write!(f, "invalid character `{c}`"),
            Self::DoubleSpacer => write!(f, "double spacer"),
            Self::LeadingSpacer => write!(f, "leading spacer"),
            Self::TrailingSpacer => write!(f, "trailing spacer"),
            Self::Rune(err) => write!(f, "{err}"),
        }
    }
}

impl std::error::Error for SpacedRuneError {}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn display() {
        assert_eq!("A.B".parse::<SpacedRune>().unwrap().to_string(), "A•B");
        assert_eq!("A.B.C".parse::<SpacedRune>().unwrap().to_string(), "A•B•C");
        assert_eq!(
            SpacedRune {
                rune: Rune(0),
                spacers: 1
            }
            .to_string(),
            "A"
        );
    }

    #[test]
    fn from_str() {
        #[track_caller]
        fn case(s: &str, rune: &str, spacers: u32) {
            assert_eq!(
                s.parse::<SpacedRune>().unwrap(),
                SpacedRune {
                    rune: rune.parse().unwrap(),
                    spacers
                },
            );
        }

        assert_eq!(
            ".A".parse::<SpacedRune>().unwrap_err(),
            SpacedRuneError::LeadingSpacer,
        );

        assert_eq!(
            "A..B".parse::<SpacedRune>().unwrap_err(),
            SpacedRuneError::DoubleSpacer,
        );

        assert_eq!(
            "A.".parse::<SpacedRune>().unwrap_err(),
            SpacedRuneError::TrailingSpacer,
        );

        assert_eq!(
            "Ax".parse::<SpacedRune>().unwrap_err(),
            SpacedRuneError::Character('x')
        );

        case("A.B", "AB", 0b1);
        case("A.B.C", "ABC", 0b11);
        case("A•B", "AB", 0b1);
        case("A•B•C", "ABC", 0b11);
        case("A•BC", "ABC", 0b1);
    }

    #[test]
    fn serde() {
        let spaced_rune = SpacedRune {
            rune: Rune(26),
            spacers: 1,
        };
        let json = "\"A•A\"";
        assert_eq!(serde_json::to_string(&spaced_rune).unwrap(), json);
        assert_eq!(
            serde_json::from_str::<SpacedRune>(json).unwrap(),
            spaced_rune
        );
    }

    #[test]
    fn borsh_roundtrip() {
        let spaced_rune = SpacedRune {
            rune: Rune(42),
            spacers: 0b101,
        };
        let serialized = borsh::to_vec(&spaced_rune).unwrap();
        let deserialized: SpacedRune = borsh::from_slice(&serialized).unwrap();
        assert_eq!(spaced_rune, deserialized);
    }
}