bitomc 0.1.4

BitOMC wallet and indexer
Documentation
use super::*;

#[derive(
  Debug, PartialEq, Copy, Clone, Hash, Eq, PartialOrd, Ord, DeserializeFromStr, SerializeDisplay,
)]
pub struct InscriptionId {
  pub txid: Txid,
  pub index: u32,
}

impl Default for InscriptionId {
  fn default() -> Self {
    Self {
      txid: Txid::all_zeros(),
      index: 0,
    }
  }
}

impl Display for InscriptionId {
  fn fmt(&self, f: &mut Formatter) -> fmt::Result {
    write!(f, "{}i{}", self.txid, self.index)
  }
}

#[derive(Debug)]
pub enum ParseError {
  Character(char),
  Length(usize),
  Separator(char),
  Txid(bitcoin::hashes::hex::Error),
  Index(std::num::ParseIntError),
}

impl Display for ParseError {
  fn fmt(&self, f: &mut Formatter) -> fmt::Result {
    match self {
      Self::Character(c) => write!(f, "invalid character: '{c}'"),
      Self::Length(len) => write!(f, "invalid length: {len}"),
      Self::Separator(c) => write!(f, "invalid separator: `{c}`"),
      Self::Txid(err) => write!(f, "invalid txid: {err}"),
      Self::Index(err) => write!(f, "invalid index: {err}"),
    }
  }
}

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

impl FromStr for InscriptionId {
  type Err = ParseError;

  fn from_str(s: &str) -> Result<Self, Self::Err> {
    if let Some(char) = s.chars().find(|char| !char.is_ascii()) {
      return Err(ParseError::Character(char));
    }

    const TXID_LEN: usize = 64;
    const MIN_LEN: usize = TXID_LEN + 2;

    if s.len() < MIN_LEN {
      return Err(ParseError::Length(s.len()));
    }

    let txid = &s[..TXID_LEN];

    let separator = s.chars().nth(TXID_LEN).unwrap();

    if separator != 'i' {
      return Err(ParseError::Separator(separator));
    }

    let vout = &s[TXID_LEN + 1..];

    Ok(Self {
      txid: txid.parse().map_err(ParseError::Txid)?,
      index: vout.parse().map_err(ParseError::Index)?,
    })
  }
}

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

  #[test]
  fn display() {
    assert_eq!(
      inscription_id(1).to_string(),
      "1111111111111111111111111111111111111111111111111111111111111111i1",
    );
    assert_eq!(
      InscriptionId {
        txid: txid(1),
        index: 0,
      }
      .to_string(),
      "1111111111111111111111111111111111111111111111111111111111111111i0",
    );
    assert_eq!(
      InscriptionId {
        txid: txid(1),
        index: 0xFFFFFFFF,
      }
      .to_string(),
      "1111111111111111111111111111111111111111111111111111111111111111i4294967295",
    );
  }

  #[test]
  fn from_str() {
    assert_eq!(
      "1111111111111111111111111111111111111111111111111111111111111111i1"
        .parse::<InscriptionId>()
        .unwrap(),
      inscription_id(1),
    );
    assert_eq!(
      "1111111111111111111111111111111111111111111111111111111111111111i4294967295"
        .parse::<InscriptionId>()
        .unwrap(),
      InscriptionId {
        txid: txid(1),
        index: 0xFFFFFFFF,
      },
    );
    assert_eq!(
      "1111111111111111111111111111111111111111111111111111111111111111i4294967295"
        .parse::<InscriptionId>()
        .unwrap(),
      InscriptionId {
        txid: txid(1),
        index: 0xFFFFFFFF,
      },
    );
  }

  #[test]
  fn from_str_bad_character() {
    assert_matches!(
      "".parse::<InscriptionId>(),
      Err(ParseError::Character('')),
    );
  }

  #[test]
  fn from_str_bad_length() {
    assert_matches!("foo".parse::<InscriptionId>(), Err(ParseError::Length(3)));
  }

  #[test]
  fn from_str_bad_separator() {
    assert_matches!(
      "0000000000000000000000000000000000000000000000000000000000000000x0".parse::<InscriptionId>(),
      Err(ParseError::Separator('x')),
    );
  }

  #[test]
  fn from_str_bad_index() {
    assert_matches!(
      "0000000000000000000000000000000000000000000000000000000000000000ifoo"
        .parse::<InscriptionId>(),
      Err(ParseError::Index(_)),
    );
  }

  #[test]
  fn from_str_bad_txid() {
    assert_matches!(
      "x000000000000000000000000000000000000000000000000000000000000000i0".parse::<InscriptionId>(),
      Err(ParseError::Txid(_)),
    );
  }
}