Skip to content

Commit d5df413

Browse files
authored
Drop AmountStr (cashubtc#612)
* Drop AmountStr Fixes cashubtc#609 Instead write a customer serializer for Keys to serialize amounts as strings * Add a custom error for invalid amounts
1 parent 6e86235 commit d5df413

File tree

3 files changed

+70
-61
lines changed

3 files changed

+70
-61
lines changed

crates/cashu/src/amount.rs

Lines changed: 15 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::cmp::Ordering;
66
use std::fmt;
77
use std::str::FromStr;
88

9-
use serde::{Deserialize, Deserializer, Serialize, Serializer};
9+
use serde::{Deserialize, Serialize};
1010
use thiserror::Error;
1111

1212
use crate::nuts::CurrencyUnit;
@@ -23,6 +23,9 @@ pub enum Error {
2323
/// Cannot convert units
2424
#[error("Cannot convert units")]
2525
CannotConvertUnits,
26+
/// Invalid amount
27+
#[error("Invalid Amount: {0}")]
28+
InvalidAmount(String),
2629
}
2730

2831
/// Amount can be any unit
@@ -31,6 +34,17 @@ pub enum Error {
3134
#[serde(transparent)]
3235
pub struct Amount(u64);
3336

37+
impl FromStr for Amount {
38+
type Err = Error;
39+
40+
fn from_str(s: &str) -> Result<Self, Self::Err> {
41+
let value = s
42+
.parse::<u64>()
43+
.map_err(|_| Error::InvalidAmount(s.to_owned()))?;
44+
Ok(Amount(value))
45+
}
46+
}
47+
3448
impl Amount {
3549
/// Amount zero
3650
pub const ZERO: Amount = Amount(0);
@@ -216,54 +230,6 @@ impl std::ops::Div for Amount {
216230
}
217231
}
218232

219-
/// String wrapper for an [Amount].
220-
///
221-
/// It ser-/deserializes the inner [Amount] to a string, while at the same time using the [u64]
222-
/// value of the [Amount] for comparison and ordering. This helps automatically sort the keys of
223-
/// a [BTreeMap] when [AmountStr] is used as key.
224-
#[derive(Debug, Clone, PartialEq, Eq)]
225-
pub struct AmountStr(Amount);
226-
227-
impl AmountStr {
228-
pub(crate) fn from(amt: Amount) -> Self {
229-
Self(amt)
230-
}
231-
}
232-
233-
impl PartialOrd<Self> for AmountStr {
234-
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
235-
Some(self.cmp(other))
236-
}
237-
}
238-
239-
impl Ord for AmountStr {
240-
fn cmp(&self, other: &Self) -> Ordering {
241-
self.0.cmp(&other.0)
242-
}
243-
}
244-
245-
impl<'de> Deserialize<'de> for AmountStr {
246-
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
247-
where
248-
D: Deserializer<'de>,
249-
{
250-
let s = String::deserialize(deserializer)?;
251-
u64::from_str(&s)
252-
.map(Amount)
253-
.map(Self)
254-
.map_err(serde::de::Error::custom)
255-
}
256-
}
257-
258-
impl Serialize for AmountStr {
259-
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
260-
where
261-
S: Serializer,
262-
{
263-
serializer.serialize_str(&self.0.to_string())
264-
}
265-
}
266-
267233
/// Kinds of targeting that are supported
268234
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)]
269235
pub enum SplitTarget {

crates/cashu/src/nuts/nut01/mod.rs

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
//! <https://github.com/cashubtc/nuts/blob/main/01.md>
44
55
use std::collections::BTreeMap;
6+
use std::fmt;
67
use std::ops::{Deref, DerefMut};
78

89
use bitcoin::secp256k1;
9-
use serde::{Deserialize, Serialize};
10+
use serde::de::{self, MapAccess, Visitor};
11+
use serde::{Deserialize, Deserializer, Serialize, Serializer};
1012
use serde_with::{serde_as, VecSkipError};
1113
use thiserror::Error;
1214

@@ -16,7 +18,7 @@ mod secret_key;
1618
pub use self::public_key::PublicKey;
1719
pub use self::secret_key::SecretKey;
1820
use super::nut02::KeySet;
19-
use crate::amount::{Amount, AmountStr};
21+
use crate::amount::Amount;
2022

2123
/// Nut01 Error
2224
#[derive(Debug, Error)]
@@ -42,16 +44,59 @@ pub enum Error {
4244
/// This is a variation of [MintKeys] that only exposes the public keys.
4345
///
4446
/// See [NUT-01]
45-
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
47+
#[derive(Debug, Clone, PartialEq, Eq)]
4648
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
47-
pub struct Keys(BTreeMap<AmountStr, PublicKey>);
49+
pub struct Keys(BTreeMap<Amount, PublicKey>);
50+
51+
impl Serialize for Keys {
52+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
53+
where
54+
S: Serializer,
55+
{
56+
let map: BTreeMap<String, _> = self.0.iter().map(|(k, v)| (k.to_string(), v)).collect();
57+
map.serialize(serializer)
58+
}
59+
}
60+
61+
impl<'de> Deserialize<'de> for Keys {
62+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
63+
where
64+
D: Deserializer<'de>,
65+
{
66+
struct KeysVisitor;
67+
68+
impl<'de> Visitor<'de> for KeysVisitor {
69+
type Value = Keys;
70+
71+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
72+
formatter.write_str("a map with string keys representing u64 values")
73+
}
74+
75+
fn visit_map<M>(self, mut map: M) -> Result<Keys, M::Error>
76+
where
77+
M: MapAccess<'de>,
78+
{
79+
let mut btree_map = BTreeMap::new();
80+
81+
while let Some((key, value)) = map.next_entry::<String, _>()? {
82+
let parsed_key = key.parse::<Amount>().map_err(de::Error::custom)?;
83+
btree_map.insert(parsed_key, value);
84+
}
85+
86+
Ok(Keys(btree_map))
87+
}
88+
}
89+
90+
deserializer.deserialize_map(KeysVisitor)
91+
}
92+
}
4893

4994
impl From<MintKeys> for Keys {
5095
fn from(keys: MintKeys) -> Self {
5196
Self(
5297
keys.0
5398
.into_iter()
54-
.map(|(amount, keypair)| (AmountStr::from(amount), keypair.public_key))
99+
.map(|(amount, keypair)| (amount, keypair.public_key))
55100
.collect(),
56101
)
57102
}
@@ -60,25 +105,25 @@ impl From<MintKeys> for Keys {
60105
impl Keys {
61106
/// Create new [`Keys`]
62107
#[inline]
63-
pub fn new(keys: BTreeMap<AmountStr, PublicKey>) -> Self {
108+
pub fn new(keys: BTreeMap<Amount, PublicKey>) -> Self {
64109
Self(keys)
65110
}
66111

67112
/// Get [`Keys`]
68113
#[inline]
69-
pub fn keys(&self) -> &BTreeMap<AmountStr, PublicKey> {
114+
pub fn keys(&self) -> &BTreeMap<Amount, PublicKey> {
70115
&self.0
71116
}
72117

73118
/// Get [`PublicKey`] for [`Amount`]
74119
#[inline]
75120
pub fn amount_key(&self, amount: Amount) -> Option<PublicKey> {
76-
self.0.get(&AmountStr::from(amount)).copied()
121+
self.0.get(&amount).copied()
77122
}
78123

79124
/// Iterate through the (`Amount`, `PublicKey`) entries in the Map
80125
#[inline]
81-
pub fn iter(&self) -> impl Iterator<Item = (&AmountStr, &PublicKey)> {
126+
pub fn iter(&self) -> impl Iterator<Item = (&Amount, &PublicKey)> {
82127
self.0.iter()
83128
}
84129
}

crates/cashu/src/nuts/nut02.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,8 @@ use thiserror::Error;
2323
use super::nut01::Keys;
2424
#[cfg(feature = "mint")]
2525
use super::nut01::{MintKeyPair, MintKeys};
26-
use crate::amount::AmountStr;
2726
use crate::nuts::nut00::CurrencyUnit;
2827
use crate::util::hex;
29-
#[cfg(feature = "mint")]
3028
use crate::Amount;
3129

3230
/// NUT02 Error
@@ -181,7 +179,7 @@ impl From<&Keys> for Id {
181179
/// 4. take the first 14 characters of the hex-encoded hash
182180
/// 5. prefix it with a keyset ID version byte
183181
fn from(map: &Keys) -> Self {
184-
let mut keys: Vec<(&AmountStr, &super::PublicKey)> = map.iter().collect();
182+
let mut keys: Vec<(&Amount, &super::PublicKey)> = map.iter().collect();
185183
keys.sort_by_key(|(amt, _v)| *amt);
186184

187185
let pubkeys_concat: Vec<u8> = keys

0 commit comments

Comments
 (0)