xand-api-proto 49.0.0

Protobuf definitions for the Xand API
Documentation
use serde::{Deserialize, Serialize};
use snafu::Snafu;
use std::{
    convert::TryFrom,
    fmt::{self, Debug, Display},
    str::FromStr,
};

#[derive(Clone, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(transparent)]
pub struct TransactionId {
    #[serde(with = "crate::proto_models::serde_hex")]
    value: Vec<u8>,
}

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Snafu)]
pub enum TransactionIdError {
    #[snafu(display(
        "The expected Transaction Id format is an optional '0x' followed by 64 hex characters. '{}' did not parse: {}",
        value, message
    ))]
    Parsing { value: String, message: String },

    #[snafu(display(
        "Cannot create a TransactionId from the supplied bytes. Must be a byte array with a length of 32 bytes."
    ))]
    Encoding { input: Vec<u8> },
}

impl TransactionId {
    pub fn decode(&self) -> Vec<u8> {
        self.value.clone()
    }
}

impl FromStr for TransactionId {
    type Err = TransactionIdError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut view = s;
        // If ignore the "0x" if the id starts with it
        if view.starts_with("0x") {
            view = &view[2..];
        }
        let value = hex::decode(view).map_err(|e| TransactionIdError::Parsing {
            value: s.to_string(),
            message: e.to_string(),
        })?;

        if value.len() != 32 {
            return Err(TransactionIdError::Parsing {
                value: s.to_string(),
                message: "incorrect length".to_string(),
            });
        }

        Ok(TransactionId { value })
    }
}

impl Default for TransactionId {
    fn default() -> Self {
        Self { value: vec![0; 32] }
    }
}

impl Debug for TransactionId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        Display::fmt(self, f)
    }
}

impl Display for TransactionId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let value_str = hex::encode(&self.value);
        write!(f, "0x{}", value_str)
    }
}

impl TryFrom<String> for TransactionId {
    type Error = TransactionIdError;
    fn try_from(input: String) -> Result<TransactionId, Self::Error> {
        input.parse()
    }
}

impl TryFrom<Vec<u8>> for TransactionId {
    type Error = TransactionIdError;

    fn try_from(input: Vec<u8>) -> Result<TransactionId, Self::Error> {
        if input.len() != 32 {
            return Err(TransactionIdError::Encoding { input });
        }

        Ok(TransactionId { value: input })
    }
}

impl From<[u8; 32]> for TransactionId {
    fn from(input: [u8; 32]) -> TransactionId {
        TransactionId {
            value: input.to_vec(),
        }
    }
}

impl From<&[u8; 32]> for TransactionId {
    fn from(input: &[u8; 32]) -> TransactionId {
        TransactionId {
            value: input.to_vec(),
        }
    }
}

impl From<TransactionId> for [u8; 32] {
    fn from(input: TransactionId) -> [u8; 32] {
        let mut array = [0; 32];
        array.copy_from_slice(&input.value);
        array
    }
}

impl From<TransactionId> for Vec<u8> {
    fn from(input: TransactionId) -> Vec<u8> {
        input.value
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use assert_matches::assert_matches;
    use std::convert::TryInto;

    #[test]
    fn default() {
        let id = TransactionId::default();

        let output = id.to_string();

        assert_eq!(
            output,
            "0x0000000000000000000000000000000000000000000000000000000000000000"
        );
    }

    #[test]
    fn to_string() {
        let input =
            "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string();
        let id: TransactionId = input.clone().try_into().unwrap();

        let output = id.to_string();

        assert_eq!(input, output);
    }

    #[test]
    fn str_try_into() {
        let input =
            "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string();
        let id: TransactionId = input.clone().try_into().unwrap();
        assert_eq!(id.to_string(), input);
    }

    #[test]
    fn from_str() {
        let input = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
        let id: TransactionId = TransactionId::from_str(input).unwrap();
        assert_eq!(id.to_string(), input);
    }

    #[test]
    fn to_from_u8_32() {
        let input: [u8; 32] = [
            18, 52, 86, 120, 144, 171, 205, 239, 18, 52, 86, 120, 144, 171, 205, 239, 18, 52, 86,
            120, 144, 171, 205, 239, 18, 52, 86, 120, 144, 171, 205, 239,
        ];

        let id: TransactionId = input.into();

        let output: [u8; 32] = id.into();
        assert_eq!(input, output);
    }

    #[test]
    fn to_from_vec_u8() {
        let input: Vec<u8> = vec![
            18, 52, 86, 120, 144, 171, 205, 239, 18, 52, 86, 120, 144, 171, 205, 239, 18, 52, 86,
            120, 144, 171, 205, 239, 18, 52, 86, 120, 144, 171, 205, 239,
        ];

        let id: TransactionId = input.clone().try_into().unwrap();

        let output: Vec<u8> = id.into();
        assert_eq!(input, output);
    }

    #[test]
    pub fn parse() {
        let input = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";

        let id: TransactionId = input.parse().unwrap();

        assert_eq!(id.to_string(), input);
    }

    #[test]
    pub fn parse_case_insensitive() {
        let input = "0x1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF";

        let id: TransactionId = input.parse().unwrap();

        assert_eq!(
            id.to_string(),
            "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
        );
    }

    #[test]
    pub fn parse_missing_header() {
        let input = "1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF";

        let id: TransactionId = input.parse().unwrap();

        assert_eq!(
            id.to_string(),
            "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
        );
    }

    #[test]
    pub fn arbitrary_string() {
        let id: Result<TransactionId, _> = "fredbob".parse();

        assert_matches!(id, Err(TransactionIdError::Parsing { .. }));
    }

    #[test]
    pub fn parse_too_short() {
        let input = "0x1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDE";

        let id: Result<TransactionId, _> = input.parse();

        assert_matches!(id, Err(TransactionIdError::Parsing { .. }));
    }

    #[test]
    pub fn parse_too_long() {
        let input = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef00";

        let id: Result<TransactionId, _> = input.parse();

        assert_matches!(id, Err(TransactionIdError::Parsing { .. }));
    }

    #[test]
    pub fn parse_non_hex_character() {
        let input = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdeg";

        let id: Result<TransactionId, _> = input.parse();

        assert_matches!(id, Err(TransactionIdError::Parsing { .. }));
    }

    #[test]
    pub fn decode() {
        let input = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";

        let id: TransactionId = input.parse().unwrap();

        let decoded = id.decode();

        // 0x12 = 1 * 16 + 2 = 18, etc.
        let expected = vec![
            18, 52, 86, 120, 144, 171, 205, 239, 18, 52, 86, 120, 144, 171, 205, 239, 18, 52, 86,
            120, 144, 171, 205, 239, 18, 52, 86, 120, 144, 171, 205, 239,
        ];

        assert_eq!(decoded, expected);
    }

    #[test]
    fn serialize() {
        let input = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
        let id = input.parse::<TransactionId>().unwrap();
        assert_eq!(
            serde_json::to_string(&id).unwrap(),
            format!(r#""{}""#, input),
        );
    }

    #[test]
    fn deserialize() {
        let input = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
        let id = input.parse::<TransactionId>().unwrap();
        assert_eq!(
            serde_json::from_str::<TransactionId>(&format!(r#""{}""#, input)).unwrap(),
            id,
        );
    }
}