xpring 0.0.10

Rust client-side library for the XRP Ledger
Documentation
use crate::javascript::{JavaScript, JsCall};
use fehler::throws;
use serde::{Deserialize, Serialize};
use anyhow::Error;

#[derive(Debug, Serialize)]
struct XAddressOptions {
    #[serde(skip_serializing_if = "Option::is_none")]
    classic_address: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    x_address: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    tag: Option<u16>,
    test: bool,
}

#[derive(PartialEq, Debug, Deserialize)]
pub struct XClassicAddress {
    pub address: String,
    pub tag: Option<u16>,
    pub test: bool,
}

impl<'a> XAddressOptions {
    pub(crate) fn new(test: bool) -> XAddressOptions {
        XAddressOptions {
            classic_address: None,
            x_address: None,
            tag: None,
            test,
        }
    }

    pub(crate) fn classic_address(
        &'a mut self,
        classic_addres: String,
    ) -> &'a mut XAddressOptions {
        self.classic_address = Some(classic_addres);
        self
    }

    pub(crate) fn x_address(&'a mut self, x_address: String) -> &'a mut XAddressOptions {
        self.x_address = Some(x_address);
        self
    }

    pub(crate) fn tag(&'a mut self, tag: u16) -> &'a mut XAddressOptions {
        self.tag = Some(tag);
        self
    }
}

#[throws(_)]
pub(crate) fn is_valid_address(jscontext: &mut JavaScript, address: &str) -> bool {
    let result = js!(jscontext.utils.isValidAddress::<bool>(address))?;
    result
}

#[throws(_)]
pub(crate) fn encode_classic_address(
    jscontext: &mut JavaScript,
    classic_address: &str,
    tag: Option<u16>,
    test: Option<bool>,
) -> String {
    let mut address = XAddressOptions::new(test.unwrap_or(false));
    address.classic_address(classic_address.to_owned());
    if let Some(i) = tag {
        address.tag(i);
    }
    let result = js!(jscontext.utils.encodeXAddress::<String>(address))?;
    result
}

#[throws(_)]
pub(crate) fn is_valid_x_address(jscontext: &mut JavaScript, address: &str) -> bool {
    let result = js!(jscontext.utils.isValidXAddress::<bool>(address))?;
    result
}

#[throws(_)]
pub(crate) fn is_valid_classic_address(jscontext: &mut JavaScript, address: &str) -> bool {
    let result = js!(jscontext.utils.isValidClassicAddress::<bool>(address))?;
    result
}

#[throws(_)]
pub(crate) fn decode_x_address(
    jscontext: &mut JavaScript,
    x_address: &str,
) -> XClassicAddress {
    let mut address = XAddressOptions::new(false);
    address.x_address(x_address.to_owned());
    let result = js!(jscontext.utils.decodeXAddress::<XClassicAddress>(address))?;
    result
}

#[cfg(test)]
mod tests {

    use super::*;
    use fehler::throws;

    #[throws(_)]
    #[test]
    fn test_valid_classic_address() {
        let out_dir = std::env::var("OUT_DIR").unwrap();
        let mut jscontext = JavaScript::new(format!("{}/xpring.js", out_dir))?;
        assert_eq!(
            is_valid_address(&mut jscontext, "rU6K7V3Po4snVhBBaU29sesqs2qTQJWDw1").unwrap(),
            true
        );
    }

    #[throws(_)]
    #[test]
    fn test_valid_x_address() {
        let out_dir = std::env::var("OUT_DIR").unwrap();
        let mut jscontext = JavaScript::new(format!("{}/xpring.js", out_dir))?;
        assert_eq!(
            is_valid_address(
                &mut jscontext,
                "XVLhHMPHU98es4dbozjVtdWzVrDjtV18pX8yuPT7y4xaEHi"
            )
            .unwrap(),
            true
        );
    }

    #[throws(_)]
    #[test]
    fn test_address_wrong_alphabet() {
        let out_dir = std::env::var("OUT_DIR").unwrap();
        let mut jscontext = JavaScript::new(format!("{}/xpring.js", out_dir))?;
        assert_eq!(
            is_valid_address(&mut jscontext, "1EAG1MwmzkG6gRZcYqcRMfC17eMt8TDTit").unwrap(),
            false
        );
    }

    #[throws(_)]
    #[test]
    fn test_classic_address_wrong_checksum() {
        let out_dir = std::env::var("OUT_DIR").unwrap();
        let mut jscontext = JavaScript::new(format!("{}/xpring.js", out_dir))?;
        assert_eq!(
            is_valid_address(&mut jscontext, "rU6K7V3Po4sBBBBBaU29sesqs2qTQJWDw1").unwrap(),
            false
        );
    }

    #[throws(_)]
    #[test]
    fn test_x_address_wrong_checksum() {
        let out_dir = std::env::var("OUT_DIR").unwrap();
        let mut jscontext = JavaScript::new(format!("{}/xpring.js", out_dir))?;
        assert_eq!(
            is_valid_address(
                &mut jscontext,
                "XVLhHMPHU98es4dbozjVtdWzVrDjtV18pX8yuPT7y4xaEHI"
            )
            .unwrap(),
            false
        );
    }

    #[throws(_)]
    #[test]
    fn test_address_invalid_character() {
        let out_dir = std::env::var("OUT_DIR").unwrap();
        let mut jscontext = JavaScript::new(format!("{}/xpring.js", out_dir))?;
        assert_eq!(
            is_valid_address(&mut jscontext, "rU6K7V3Po4sBBBBBaU@#$%qs2qTQJWDw1").unwrap(),
            false
        );
    }

    #[throws(_)]
    #[test]
    fn test_address_invalid_too_long() {
        let out_dir = std::env::var("OUT_DIR").unwrap();
        let mut jscontext = JavaScript::new(format!("{}/xpring.js", out_dir))?;
        assert_eq!(
            is_valid_address(
                &mut jscontext,
                "rU6K7V3Po4snVhBBaU29sesqs2qTQJWDw1rU6K7V3Po4snVhBBaU29sesqs2qTQJWDw1"
            )
            .unwrap(),
            false
        );
    }

    #[throws(_)]
    #[test]
    fn test_address_invalid_too_short() {
        let out_dir = std::env::var("OUT_DIR").unwrap();
        let mut jscontext = JavaScript::new(format!("{}/xpring.js", out_dir))?;
        assert_eq!(
            is_valid_address(&mut jscontext, "rU6K7V3Po4s2qTQJWDw1").unwrap(),
            false
        );
    }

    #[throws(_)]
    #[test]
    fn test_encode_x_address_mainnet_and_tag() {
        let out_dir = std::env::var("OUT_DIR").unwrap();
        let mut jscontext = JavaScript::new(format!("{}/xpring.js", out_dir))?;
        assert_eq!(
            encode_classic_address(
                &mut jscontext,
                "rU6K7V3Po4snVhBBaU29sesqs2qTQJWDw1",
                Some(12345),
                None
            )
            .unwrap(),
            "XVfC9CTCJh6GN2x8bnrw3LtdbqiVCUvtU3HnooQDgBnUpQT".to_owned()
        );
    }

    #[throws(_)]
    #[test]
    fn test_encode_x_address_testnet_and_tag() {
        let out_dir = std::env::var("OUT_DIR").unwrap();
        let mut jscontext = JavaScript::new(format!("{}/xpring.js", out_dir))?;
        assert_eq!(
            encode_classic_address(
                &mut jscontext,
                "rU6K7V3Po4snVhBBaU29sesqs2qTQJWDw1",
                Some(12345),
                None
            )
            .unwrap(),
            "XVfC9CTCJh6GN2x8bnrw3LtdbqiVCUvtU3HnooQDgBnUpQT".to_owned()
        );
    }

    #[throws(_)]
    #[test]
    fn test_encode_x_address_only() {
        let out_dir = std::env::var("OUT_DIR").unwrap();
        let mut jscontext = JavaScript::new(format!("{}/xpring.js", out_dir))?;
        assert_eq!(
            encode_classic_address(
                &mut jscontext,
                "rU6K7V3Po4snVhBBaU29sesqs2qTQJWDw1",
                None,
                Some(false)
            )
            .unwrap(),
            "XVfC9CTCJh6GN2x8bnrw3LtdbqiVCUFyQVMzRrMGUZpokKH".to_owned()
        );
    }

    #[throws(_)]
    #[test]
    fn test_encode_x_address_invalid_address() {
        let out_dir = std::env::var("OUT_DIR").unwrap();
        let mut jscontext = JavaScript::new(format!("{}/xpring.js", out_dir))?;
        let x_address =
            encode_classic_address(&mut jscontext, "xrp", None, Some(false)).unwrap_err();
        assert_eq!(
            "Invalid Parameters",
            x_address.downcast_ref::<String>().unwrap()
        );
    }

    #[throws(_)]
    #[test]
    fn test_decode_x_address_valid_mainnet_with_tag() {
        let out_dir = std::env::var("OUT_DIR").unwrap();
        let mut jscontext = JavaScript::new(format!("{}/xpring.js", out_dir))?;
        let expected = XClassicAddress {
            address: "rU6K7V3Po4snVhBBaU29sesqs2qTQJWDw1".to_owned(),
            tag: Some(12345),
            test: false,
        };
        assert_eq!(
            decode_x_address(
                &mut jscontext,
                "XVfC9CTCJh6GN2x8bnrw3LtdbqiVCUvtU3HnooQDgBnUpQT"
            )
            .unwrap(),
            expected
        );
    }

    #[throws(_)]
    #[test]
    fn test_decode_x_address_valid_testnet_with_tag() {
        let out_dir = std::env::var("OUT_DIR").unwrap();
        let mut jscontext = JavaScript::new(format!("{}/xpring.js", out_dir))?;
        let expected = XClassicAddress {
            address: "rU6K7V3Po4snVhBBaU29sesqs2qTQJWDw1".to_owned(),
            tag: Some(12345),
            test: true,
        };
        assert_eq!(
            decode_x_address(
                &mut jscontext,
                "TVsBZmcewpEHgajPi1jApLeYnHPJw82v9JNYf7dkGmWphmh"
            )
            .unwrap(),
            expected
        );
    }

    #[throws(_)]
    #[test]
    fn test_decode_x_address_valid_testnet_without_tag() {
        let out_dir = std::env::var("OUT_DIR").unwrap();
        let mut jscontext = JavaScript::new(format!("{}/xpring.js", out_dir))?;
        let expected = XClassicAddress {
            address: "rU6K7V3Po4snVhBBaU29sesqs2qTQJWDw1".to_owned(),
            tag: None,
            test: false,
        };
        assert_eq!(
            decode_x_address(
                &mut jscontext,
                "XVfC9CTCJh6GN2x8bnrw3LtdbqiVCUFyQVMzRrMGUZpokKH"
            )
            .unwrap(),
            expected
        );
    }

    #[throws(_)]
    #[test]
    fn test_decode_x_address_invalid_address() {
        let out_dir = std::env::var("OUT_DIR").unwrap();
        let mut jscontext = JavaScript::new(format!("{}/xpring.js", out_dir))?;
        let x_address = decode_x_address(&mut jscontext, "xrp").unwrap_err();
        assert_eq!(
            "Invalid Address",
            x_address.downcast_ref::<String>().unwrap()
        );
    }

    #[throws(_)]
    #[test]
    fn test_is_valid_x_address_with_classic_address() {
        let out_dir = std::env::var("OUT_DIR").unwrap();
        let mut jscontext = JavaScript::new(format!("{}/xpring.js", out_dir))?;
        assert_eq!(
            is_valid_x_address(&mut jscontext, "rU6K7V3Po4snVhBBaU29sesqs2qTQJWDw1").unwrap(),
            false
        );
    }

    #[throws(_)]
    #[test]
    fn test_is_valid_x_address_with_invalid_address() {
        let out_dir = std::env::var("OUT_DIR").unwrap();
        let mut jscontext = JavaScript::new(format!("{}/xpring.js", out_dir))?;
        assert_eq!(is_valid_x_address(&mut jscontext, "xrp").unwrap(), false);
    }

    #[throws(_)]
    #[test]
    fn test_is_valid_x_address_with_valid_x_address() {
        let out_dir = std::env::var("OUT_DIR").unwrap();
        let mut jscontext = JavaScript::new(format!("{}/xpring.js", out_dir))?;
        assert_eq!(
            is_valid_x_address(
                &mut jscontext,
                "XVfC9CTCJh6GN2x8bnrw3LtdbqiVCUvtU3HnooQDgBnUpQT"
            )
            .unwrap(),
            true
        );
    }

    #[throws(_)]
    #[test]
    fn test_is_valid_classic_address_with_valid_classic_address() {
        let out_dir = std::env::var("OUT_DIR").unwrap();
        let mut jscontext = JavaScript::new(format!("{}/xpring.js", out_dir))?;
        assert_eq!(
            is_valid_classic_address(&mut jscontext, "rU6K7V3Po4snVhBBaU29sesqs2qTQJWDw1").unwrap(),
            true
        );
    }

    #[throws(_)]
    #[test]
    fn test_is_valid_classic_address_with_invalid_classic_address() {
        let out_dir = std::env::var("OUT_DIR").unwrap();
        let mut jscontext = JavaScript::new(format!("{}/xpring.js", out_dir))?;
        assert_eq!(
            is_valid_classic_address(&mut jscontext, "xrp").unwrap(),
            false
        );
    }
}