Drop AmountStr (#612)

* Drop AmountStr

Fixes #609

Instead write a customer serializer for Keys to serialize amounts as strings

* Add a custom error for invalid amounts
This commit is contained in:
C
2025-02-22 14:46:02 -03:00
committed by GitHub
parent 6e86235970
commit d5df413d6b
3 changed files with 70 additions and 61 deletions

View File

@@ -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;
@@ -23,6 +23,9 @@ pub enum Error {
/// Cannot convert units
#[error("Cannot convert units")]
CannotConvertUnits,
/// Invalid amount
#[error("Invalid Amount: {0}")]
InvalidAmount(String),
}
/// Amount can be any unit
@@ -31,6 +34,17 @@ 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::InvalidAmount(s.to_owned()))?;
Ok(Amount(value))
}
}
impl Amount {
/// Amount zero
pub const ZERO: Amount = Amount(0);
@@ -216,54 +230,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 {

View File

@@ -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()
}
}

View File

@@ -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