use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct WalletId(pub u32);
impl std::fmt::Display for WalletId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Amount(pub u64);
impl Amount {
pub fn mojos(self) -> u64 {
self.0
}
}
impl Serialize for Amount {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.0.to_string())
}
}
impl<'de> Deserialize<'de> for Amount {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
#[derive(Deserialize)]
#[serde(untagged)]
enum StringOrNumber {
Text(String),
Number(u64),
}
match StringOrNumber::deserialize(deserializer)? {
StringOrNumber::Text(s) => s.parse().map(Amount).map_err(serde::de::Error::custom),
StringOrNumber::Number(n) => Ok(Amount(n)),
}
}
}
impl std::fmt::Display for Amount {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct AssetId(pub String);
impl std::fmt::Display for AssetId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
const MAX_JS_SAFE_INTEGER: u64 = 9_007_199_254_740_991;
#[test]
fn small_amount_serializes_as_string() {
assert_eq!(serde_json::to_string(&Amount(5)).unwrap(), "\"5\"");
assert_eq!(serde_json::to_string(&Amount(0)).unwrap(), "\"0\"");
}
#[test]
fn large_amount_serializes_as_string() {
let big = Amount(MAX_JS_SAFE_INTEGER + 1);
assert_eq!(serde_json::to_string(&big).unwrap(), "\"9007199254740992\"");
}
#[test]
fn large_amount_matches_extraction_source_byte_for_byte() {
let value = MAX_JS_SAFE_INTEGER + 1;
assert_eq!(
serde_json::to_string(&Amount(value)).unwrap(),
"\"9007199254740992\"",
);
assert_eq!(
serde_json::to_string(&Amount(u64::MAX)).unwrap(),
"\"18446744073709551615\"",
);
}
#[test]
fn amount_deserializes_from_string() {
let from_str: Amount = serde_json::from_str("\"9007199254740992\"").unwrap();
assert_eq!(from_str, Amount(9_007_199_254_740_992));
}
#[test]
fn amount_leniently_deserializes_from_bare_number() {
let from_num: Amount = serde_json::from_str("42").unwrap();
assert_eq!(from_num, Amount(42));
}
#[test]
fn amount_round_trips_across_the_threshold() {
for value in [
0u64,
1,
MAX_JS_SAFE_INTEGER,
MAX_JS_SAFE_INTEGER + 1,
u64::MAX,
] {
let json = serde_json::to_string(&Amount(value)).unwrap();
let back: Amount = serde_json::from_str(&json).unwrap();
assert_eq!(back, Amount(value), "round-trip failed for {value}");
}
}
#[test]
fn amount_bad_string_is_an_error() {
assert!(serde_json::from_str::<Amount>("\"not-a-number\"").is_err());
}
#[test]
fn mojos_accessor_returns_raw() {
assert_eq!(Amount(555).mojos(), 555);
}
}