Browse Source

Drop AmountStr

Fixes #609

Instead write a customer serializer for Keys to serialize amounts as strings
Cesar Rodas 1 month ago
parent
commit
040259c131
3 changed files with 65 additions and 61 deletions
  1. 10 49
      crates/cashu/src/amount.rs
  2. 54 9
      crates/cashu/src/nuts/nut01/mod.rs
  3. 1 3
      crates/cashu/src/nuts/nut02.rs

+ 10 - 49
crates/cashu/src/amount.rs

@@ -6,7 +6,7 @@ use std::cmp::Ordering;
 use std::fmt;
 use std::str::FromStr;
 
-use serde::{Deserialize, Deserializer, Serialize, Serializer};
+use serde::{Deserialize, Serialize};
 use thiserror::Error;
 
 use crate::nuts::CurrencyUnit;
@@ -31,6 +31,15 @@ pub enum Error {
 #[serde(transparent)]
 pub struct Amount(u64);
 
+impl FromStr for Amount {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let value = s.parse::<u64>().map_err(|_| Error::AmountOverflow)?;
+        Ok(Amount(value))
+    }
+}
+
 impl Amount {
     /// Amount zero
     pub const ZERO: Amount = Amount(0);
@@ -216,54 +225,6 @@ impl std::ops::Div for Amount {
     }
 }
 
-/// String wrapper for an [Amount].
-///
-/// It ser-/deserializes the inner [Amount] to a string, while at the same time using the [u64]
-/// value of the [Amount] for comparison and ordering. This helps automatically sort the keys of
-/// a [BTreeMap] when [AmountStr] is used as key.
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct AmountStr(Amount);
-
-impl AmountStr {
-    pub(crate) fn from(amt: Amount) -> Self {
-        Self(amt)
-    }
-}
-
-impl PartialOrd<Self> for AmountStr {
-    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl Ord for AmountStr {
-    fn cmp(&self, other: &Self) -> Ordering {
-        self.0.cmp(&other.0)
-    }
-}
-
-impl<'de> Deserialize<'de> for AmountStr {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: Deserializer<'de>,
-    {
-        let s = String::deserialize(deserializer)?;
-        u64::from_str(&s)
-            .map(Amount)
-            .map(Self)
-            .map_err(serde::de::Error::custom)
-    }
-}
-
-impl Serialize for AmountStr {
-    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
-    where
-        S: Serializer,
-    {
-        serializer.serialize_str(&self.0.to_string())
-    }
-}
-
 /// Kinds of targeting that are supported
 #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)]
 pub enum SplitTarget {

+ 54 - 9
crates/cashu/src/nuts/nut01/mod.rs

@@ -3,10 +3,12 @@
 //! <https://github.com/cashubtc/nuts/blob/main/01.md>
 
 use std::collections::BTreeMap;
+use std::fmt;
 use std::ops::{Deref, DerefMut};
 
 use bitcoin::secp256k1;
-use serde::{Deserialize, Serialize};
+use serde::de::{self, MapAccess, Visitor};
+use serde::{Deserialize, Deserializer, Serialize, Serializer};
 use serde_with::{serde_as, VecSkipError};
 use thiserror::Error;
 
@@ -16,7 +18,7 @@ mod secret_key;
 pub use self::public_key::PublicKey;
 pub use self::secret_key::SecretKey;
 use super::nut02::KeySet;
-use crate::amount::{Amount, AmountStr};
+use crate::amount::Amount;
 
 /// Nut01 Error
 #[derive(Debug, Error)]
@@ -42,16 +44,59 @@ pub enum Error {
 /// This is a variation of [MintKeys] that only exposes the public keys.
 ///
 /// See [NUT-01]
-#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
-pub struct Keys(BTreeMap<AmountStr, PublicKey>);
+pub struct Keys(BTreeMap<Amount, PublicKey>);
+
+impl Serialize for Keys {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let map: BTreeMap<String, _> = self.0.iter().map(|(k, v)| (k.to_string(), v)).collect();
+        map.serialize(serializer)
+    }
+}
+
+impl<'de> Deserialize<'de> for Keys {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct KeysVisitor;
+
+        impl<'de> Visitor<'de> for KeysVisitor {
+            type Value = Keys;
+
+            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+                formatter.write_str("a map with string keys representing u64 values")
+            }
+
+            fn visit_map<M>(self, mut map: M) -> Result<Keys, M::Error>
+            where
+                M: MapAccess<'de>,
+            {
+                let mut btree_map = BTreeMap::new();
+
+                while let Some((key, value)) = map.next_entry::<String, _>()? {
+                    let parsed_key = key.parse::<Amount>().map_err(de::Error::custom)?;
+                    btree_map.insert(parsed_key, value);
+                }
+
+                Ok(Keys(btree_map))
+            }
+        }
+
+        deserializer.deserialize_map(KeysVisitor)
+    }
+}
 
 impl From<MintKeys> for Keys {
     fn from(keys: MintKeys) -> Self {
         Self(
             keys.0
                 .into_iter()
-                .map(|(amount, keypair)| (AmountStr::from(amount), keypair.public_key))
+                .map(|(amount, keypair)| (amount, keypair.public_key))
                 .collect(),
         )
     }
@@ -60,25 +105,25 @@ impl From<MintKeys> for Keys {
 impl Keys {
     /// Create new [`Keys`]
     #[inline]
-    pub fn new(keys: BTreeMap<AmountStr, PublicKey>) -> Self {
+    pub fn new(keys: BTreeMap<Amount, PublicKey>) -> Self {
         Self(keys)
     }
 
     /// Get [`Keys`]
     #[inline]
-    pub fn keys(&self) -> &BTreeMap<AmountStr, PublicKey> {
+    pub fn keys(&self) -> &BTreeMap<Amount, PublicKey> {
         &self.0
     }
 
     /// Get [`PublicKey`] for [`Amount`]
     #[inline]
     pub fn amount_key(&self, amount: Amount) -> Option<PublicKey> {
-        self.0.get(&AmountStr::from(amount)).copied()
+        self.0.get(&amount).copied()
     }
 
     /// Iterate through the (`Amount`, `PublicKey`) entries in the Map
     #[inline]
-    pub fn iter(&self) -> impl Iterator<Item = (&AmountStr, &PublicKey)> {
+    pub fn iter(&self) -> impl Iterator<Item = (&Amount, &PublicKey)> {
         self.0.iter()
     }
 }

+ 1 - 3
crates/cashu/src/nuts/nut02.rs

@@ -23,10 +23,8 @@ use thiserror::Error;
 use super::nut01::Keys;
 #[cfg(feature = "mint")]
 use super::nut01::{MintKeyPair, MintKeys};
-use crate::amount::AmountStr;
 use crate::nuts::nut00::CurrencyUnit;
 use crate::util::hex;
-#[cfg(feature = "mint")]
 use crate::Amount;
 
 /// NUT02 Error
@@ -181,7 +179,7 @@ impl From<&Keys> for Id {
     ///   4. take the first 14 characters of the hex-encoded hash
     ///   5. prefix it with a keyset ID version byte
     fn from(map: &Keys) -> Self {
-        let mut keys: Vec<(&AmountStr, &super::PublicKey)> = map.iter().collect();
+        let mut keys: Vec<(&Amount, &super::PublicKey)> = map.iter().collect();
         keys.sort_by_key(|(amt, _v)| *amt);
 
         let pubkeys_concat: Vec<u8> = keys