use resources::Amount;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::str::FromStr;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum AssetIdentifier {
Native,
CreditAlphanum4(AssetId),
CreditAlphanum12(AssetId),
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct AssetId {
code: String,
issuer: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct IntermediateAssetIdentifier {
asset_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
asset_code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
asset_issuer: Option<String>,
}
impl<'de> Deserialize<'de> for AssetIdentifier {
fn deserialize<D>(d: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let rep: IntermediateAssetIdentifier = IntermediateAssetIdentifier::deserialize(d)?;
AssetIdentifier::new(&rep.asset_type, rep.asset_code, rep.asset_issuer)
.map_err(de::Error::custom)
}
}
impl Serialize for AssetIdentifier {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let rep = match *self {
AssetIdentifier::Native => IntermediateAssetIdentifier {
asset_type: "native".to_string(),
asset_code: None,
asset_issuer: None,
},
_ => IntermediateAssetIdentifier {
asset_type: self.asset_type().to_string(),
asset_code: Some(self.code().to_string()),
asset_issuer: Some(self.issuer().to_string()),
},
};
rep.serialize(s)
}
}
impl AssetIdentifier {
pub fn asset_type(&self) -> &str {
match *self {
AssetIdentifier::Native => &"native",
AssetIdentifier::CreditAlphanum4(_) => &"credit_alphanum4",
AssetIdentifier::CreditAlphanum12(_) => &"credit_alphanum12",
}
}
pub fn code(&self) -> &str {
match *self {
AssetIdentifier::Native => &"XLM",
AssetIdentifier::CreditAlphanum4(ref asset_id) => &asset_id.code,
AssetIdentifier::CreditAlphanum12(ref asset_id) => &asset_id.code,
}
}
pub fn asset_code(&self) -> Option<String> {
match *self {
AssetIdentifier::Native => None,
AssetIdentifier::CreditAlphanum4(ref asset_id) => Some(asset_id.code.clone()),
AssetIdentifier::CreditAlphanum12(ref asset_id) => Some(asset_id.code.clone()),
}
}
pub fn issuer(&self) -> &str {
match *self {
AssetIdentifier::Native => &"Stellar Foundation",
AssetIdentifier::CreditAlphanum4(ref asset_id) => &asset_id.issuer,
AssetIdentifier::CreditAlphanum12(ref asset_id) => &asset_id.issuer,
}
}
pub fn asset_issuer(&self) -> Option<String> {
match *self {
AssetIdentifier::Native => None,
AssetIdentifier::CreditAlphanum4(ref asset_id) => Some(asset_id.issuer.clone()),
AssetIdentifier::CreditAlphanum12(ref asset_id) => Some(asset_id.issuer.clone()),
}
}
pub fn is_native(&self) -> bool {
&AssetIdentifier::Native == self
}
pub fn new(
asset_type: &str,
code: Option<String>,
issuer: Option<String>,
) -> Result<AssetIdentifier, String> {
match asset_type {
"native" => Ok(AssetIdentifier::Native),
"credit_alphanum4" => Ok(AssetIdentifier::CreditAlphanum4(AssetId {
code: code.unwrap(),
issuer: issuer.unwrap(),
})),
"credit_alphanum12" => Ok(AssetIdentifier::CreditAlphanum12(AssetId {
code: code.unwrap(),
issuer: issuer.unwrap(),
})),
_ => Err("Invalid Asset Type.".to_string()),
}
}
pub fn native() -> AssetIdentifier {
AssetIdentifier::Native
}
pub fn alphanum4(code: &str, issuer: &str) -> AssetIdentifier {
AssetIdentifier::CreditAlphanum4(AssetId {
code: code.to_string(),
issuer: issuer.to_string(),
})
}
pub fn alphanum12(code: &str, issuer: &str) -> AssetIdentifier {
AssetIdentifier::CreditAlphanum12(AssetId {
code: code.to_string(),
issuer: issuer.to_string(),
})
}
}
#[cfg(test)]
mod asset_identifier_tests {
use super::*;
use serde_json;
fn asset_json() -> &'static str {
include_str!("../../fixtures/asset.json")
}
fn native_asset_json() -> &'static str {
include_str!("../../fixtures/native_asset.json")
}
#[test]
fn it_parses_native_assets_from_json() {
let native_asset: AssetIdentifier = serde_json::from_str(&native_asset_json()).unwrap();
assert_eq!(native_asset.asset_type(), "native");
assert_eq!(native_asset.code(), "XLM");
assert_eq!(native_asset.asset_code(), None);
assert_eq!(native_asset.issuer(), "Stellar Foundation");
assert_eq!(native_asset.asset_issuer(), None);
assert!(native_asset.is_native());
}
#[test]
fn it_parses_an_identifier() {
let asset: AssetIdentifier = serde_json::from_str(&asset_json()).unwrap();
assert_eq!(asset.asset_type(), "credit_alphanum4");
assert_eq!(asset.code(), "USD");
assert_eq!(
asset.issuer(),
"GBAUUA74H4XOQYRSOW2RZUA4QL5PB37U3JS5NE3RTB2ELJVMIF5RLMAG"
);
assert!(!asset.is_native());
}
#[test]
fn it_serializes_non_native_assets() {
let asset: AssetIdentifier = serde_json::from_str(&asset_json()).unwrap();
assert_eq!(
serde_json::to_string(&asset).unwrap(),
"{\
\"asset_type\":\"credit_alphanum4\",\
\"asset_code\":\"USD\",\
\"asset_issuer\":\"GBAUUA74H4XOQYRSOW2RZUA4QL5PB37U3JS5NE3RTB2ELJVMIF5RLMAG\"\
}"
);
}
#[test]
fn it_serializes_native_assets() {
let native_asset: AssetIdentifier = serde_json::from_str(&native_asset_json()).unwrap();
assert_eq!(
serde_json::to_string(&native_asset).unwrap(),
"{\
\"asset_type\":\"native\"\
}"
);
}
#[test]
fn it_creates_a_native_asset() {
let asset: AssetIdentifier = AssetIdentifier::native();
assert_eq!(asset.asset_type(), "native");
assert_eq!(asset.code(), "XLM");
assert_eq!(asset.asset_code(), None);
assert_eq!(asset.issuer(), "Stellar Foundation");
assert_eq!(asset.asset_issuer(), None);
assert!(asset.is_native());
}
#[test]
fn it_creates_an_alphanum4_asset() {
let asset: AssetIdentifier = AssetIdentifier::alphanum4("ABCD", "ISSUER");
assert_eq!(asset.asset_type(), "credit_alphanum4");
assert_eq!(asset.code(), "ABCD");
assert_eq!(asset.issuer(), "ISSUER");
assert!(!asset.is_native());
}
#[test]
fn it_creates_an_alphanum12_asset() {
let asset: AssetIdentifier = AssetIdentifier::alphanum12("ABCD", "ISSUER");
assert_eq!(asset.asset_type(), "credit_alphanum12");
assert_eq!(asset.code(), "ABCD");
assert_eq!(asset.issuer(), "ISSUER");
assert!(!asset.is_native());
}
}
#[derive(Debug)]
pub enum ParseAssetIdentifierError {
FormattedIncorrectly,
}
impl fmt::Display for ParseAssetIdentifierError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self)
}
}
impl FromStr for AssetIdentifier {
type Err = ParseAssetIdentifierError;
fn from_str(s: &str) -> Result<AssetIdentifier, ParseAssetIdentifierError> {
let tokens: Vec<&str> = s.split('-').collect();
match &tokens[..] {
["XLM"] | ["xlm"] | ["lumen"] => Ok(AssetIdentifier::Native),
[code, issuer] if code.len() <= 4 => Ok(AssetIdentifier::alphanum4(code, issuer)),
[code, issuer] if code.len() <= 12 => Ok(AssetIdentifier::alphanum12(code, issuer)),
_ => Err(ParseAssetIdentifierError::FormattedIncorrectly),
}
}
}
#[cfg(test)]
mod from_str_asset_identifier_tests {
use super::*;
#[test]
fn it_knows_some_lumen_aliases() {
assert_eq!(
AssetIdentifier::from_str("XLM").unwrap(),
AssetIdentifier::Native
);
assert_eq!(
AssetIdentifier::from_str("xlm").unwrap(),
AssetIdentifier::Native
);
assert_eq!(
AssetIdentifier::from_str("lumen").unwrap(),
AssetIdentifier::Native
);
}
#[test]
fn it_can_parse_asset_identifiers() {
assert_eq!(
AssetIdentifier::from_str("XLM").unwrap(),
AssetIdentifier::Native
);
assert_eq!(
AssetIdentifier::from_str("xlm").unwrap(),
AssetIdentifier::Native
);
assert_eq!(
AssetIdentifier::from_str("fox-123ABC").unwrap(),
AssetIdentifier::alphanum4("fox", "123ABC")
);
assert_eq!(
AssetIdentifier::from_str("starfox-123ABC").unwrap(),
AssetIdentifier::alphanum12("starfox", "123ABC")
);
}
#[test]
fn it_returns_appropriate_errors() {
assert!(AssetIdentifier::from_str("fox-123-abs").is_err());
assert!(AssetIdentifier::from_str("foxisareallycoolanimal-123").is_err());
}
}
#[derive(Deserialize, Debug, Copy, Clone, PartialEq, Eq)]
pub struct Flags {
auth_required: bool,
auth_revocable: bool,
}
impl Flags {
pub fn new(auth_required: bool, auth_revocable: bool) -> Flags {
Flags {
auth_required,
auth_revocable,
}
}
pub fn is_auth_required(&self) -> bool {
self.auth_required
}
pub fn is_auth_revocable(&self) -> bool {
self.auth_revocable
}
}
#[derive(Debug, Clone)]
pub struct Asset {
asset_identifier: AssetIdentifier,
amount: Amount,
num_accounts: u32,
flags: Flags,
}
#[derive(Deserialize, Debug)]
pub struct IntermediateAsset {
asset_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
asset_code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
asset_issuer: Option<String>,
amount: Amount,
num_accounts: u32,
flags: Flags,
}
impl<'de> Deserialize<'de> for Asset {
fn deserialize<D>(d: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let rep: IntermediateAsset = IntermediateAsset::deserialize(d)?;
let asset_identifier: Result<AssetIdentifier, D::Error> =
AssetIdentifier::new(&rep.asset_type, rep.asset_code, rep.asset_issuer)
.map_err(de::Error::custom);
Ok(Asset {
asset_identifier: asset_identifier.unwrap(),
amount: rep.amount,
num_accounts: rep.num_accounts,
flags: rep.flags,
})
}
}
impl Asset {
pub fn identifier(&self) -> &AssetIdentifier {
&self.asset_identifier
}
pub fn asset_type(&self) -> &str {
&self.asset_identifier.asset_type()
}
pub fn code(&self) -> &str {
&self.asset_identifier.code()
}
pub fn issuer(&self) -> &str {
&self.asset_identifier.issuer()
}
pub fn amount(&self) -> Amount {
self.amount
}
pub fn num_accounts(&self) -> u32 {
self.num_accounts
}
pub fn is_auth_required(&self) -> bool {
self.flags.auth_required
}
pub fn is_auth_revocable(&self) -> bool {
self.flags.auth_revocable
}
pub fn flags(&self) -> Flags {
self.flags
}
}
#[cfg(test)]
mod asset_tests {
use super::*;
use serde_json;
fn asset_json() -> &'static str {
include_str!("../../fixtures/asset.json")
}
#[test]
fn it_parses_an_asset_from_json() {
let asset: Asset = serde_json::from_str(&asset_json()).unwrap();
assert_eq!(asset.asset_type(), "credit_alphanum4");
assert_eq!(asset.code(), "USD");
assert_eq!(
asset.issuer(),
"GBAUUA74H4XOQYRSOW2RZUA4QL5PB37U3JS5NE3RTB2ELJVMIF5RLMAG"
);
assert_eq!(asset.amount(), Amount::new(1000000000));
assert_eq!(asset.num_accounts(), 91547871);
assert!(!asset.is_auth_required());
assert!(!asset.flags().is_auth_required());
assert!(asset.is_auth_revocable());
assert!(asset.flags().is_auth_revocable());
assert_eq!(
asset.identifier(),
&AssetIdentifier::alphanum4(
"USD",
"GBAUUA74H4XOQYRSOW2RZUA4QL5PB37U3JS5NE3RTB2ELJVMIF5RLMAG"
),
);
}
}