Bläddra i källkod

refactor: use quote id to string (#1026)

thesimplekid 2 månader sedan
förälder
incheckning
734e62b04a

+ 3 - 104
crates/cashu/src/lib.rs

@@ -16,6 +16,9 @@ pub use self::mint_url::MintUrl;
 pub use self::nuts::*;
 pub use self::util::SECP256K1;
 
+#[cfg(feature = "mint")]
+pub mod quote_id;
+
 #[doc(hidden)]
 #[macro_export]
 macro_rules! ensure_cdk {
@@ -25,107 +28,3 @@ macro_rules! ensure_cdk {
         }
     };
 }
-
-#[cfg(feature = "mint")]
-/// Quote ID. The specifications only define a string but CDK uses Uuid, so we use an enum to port compatibility.
-pub mod quote_id {
-    use std::fmt;
-    use std::str::FromStr;
-
-    use bitcoin::base64::engine::general_purpose;
-    use bitcoin::base64::Engine as _;
-    use serde::{de, Deserialize, Deserializer, Serialize};
-    use thiserror::Error;
-    use uuid::Uuid;
-
-    /// Invalid UUID
-    #[derive(Debug, Error)]
-    pub enum QuoteIdError {
-        /// UUID Error
-        #[error("invalid UUID: {0}")]
-        Uuid(#[from] uuid::Error),
-        /// Invalid base64
-        #[error("invalid base64")]
-        Base64,
-        /// Invalid quote ID
-        #[error("neither a valid UUID nor a valid base64 string")]
-        InvalidQuoteId,
-    }
-
-    /// Mint Quote ID
-    #[derive(Serialize, Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
-    #[serde(untagged)]
-    pub enum QuoteId {
-        /// (Nutshell) base64 quote ID
-        BASE64(String),
-        /// UUID quote ID
-        UUID(Uuid),
-    }
-
-    impl QuoteId {
-        /// Create a new UUID-based MintQuoteId
-        pub fn new_uuid() -> Self {
-            Self::UUID(Uuid::new_v4())
-        }
-    }
-
-    impl From<Uuid> for QuoteId {
-        fn from(uuid: Uuid) -> Self {
-            Self::UUID(uuid)
-        }
-    }
-
-    impl fmt::Display for QuoteId {
-        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-            match self {
-                QuoteId::BASE64(s) => write!(f, "{}", s),
-                QuoteId::UUID(u) => write!(f, "{}", u),
-            }
-        }
-    }
-
-    impl FromStr for QuoteId {
-        type Err = QuoteIdError;
-
-        fn from_str(s: &str) -> Result<Self, Self::Err> {
-            // Try UUID first
-            if let Ok(u) = Uuid::parse_str(s) {
-                return Ok(QuoteId::UUID(u));
-            }
-
-            // Try base64: decode, then re-encode and compare to ensure canonical form
-            // Use the standard (URL/filename safe or standard) depending on your needed alphabet.
-            // Here we use standard base64.
-            match general_purpose::URL_SAFE.decode(s) {
-                Ok(_bytes) => Ok(QuoteId::BASE64(s.to_string())),
-                Err(_) => Err(QuoteIdError::InvalidQuoteId),
-            }
-        }
-    }
-
-    impl<'de> Deserialize<'de> for QuoteId {
-        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-        where
-            D: Deserializer<'de>,
-        {
-            // Deserialize as plain string first
-            let s = String::deserialize(deserializer)?;
-
-            // Try UUID first
-            if let Ok(u) = Uuid::parse_str(&s) {
-                return Ok(QuoteId::UUID(u));
-            }
-
-            if general_purpose::URL_SAFE.decode(&s).is_ok() {
-                return Ok(QuoteId::BASE64(s));
-            }
-
-            // Neither matched — return a helpful error
-            Err(de::Error::custom(format!(
-                "QuoteId must be either a UUID (e.g. {}) or a valid base64 string; got: {}",
-                Uuid::nil(),
-                s
-            )))
-        }
-    }
-}

+ 100 - 0
crates/cashu/src/quote_id.rs

@@ -0,0 +1,100 @@
+//! Quote ID. The specifications only define a string but CDK uses Uuid, so we use an enum to port compatibility.
+use std::fmt;
+use std::str::FromStr;
+
+use bitcoin::base64::engine::general_purpose;
+use bitcoin::base64::Engine as _;
+use serde::{de, Deserialize, Deserializer, Serialize};
+use thiserror::Error;
+use uuid::Uuid;
+
+/// Invalid UUID
+#[derive(Debug, Error)]
+pub enum QuoteIdError {
+    /// UUID Error
+    #[error("invalid UUID: {0}")]
+    Uuid(#[from] uuid::Error),
+    /// Invalid base64
+    #[error("invalid base64")]
+    Base64,
+    /// Invalid quote ID
+    #[error("neither a valid UUID nor a valid base64 string")]
+    InvalidQuoteId,
+}
+
+/// Mint Quote ID
+#[derive(Serialize, Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
+#[serde(untagged)]
+pub enum QuoteId {
+    /// (Nutshell) base64 quote ID
+    BASE64(String),
+    /// UUID quote ID
+    UUID(Uuid),
+}
+
+impl QuoteId {
+    /// Create a new UUID-based MintQuoteId
+    pub fn new_uuid() -> Self {
+        Self::UUID(Uuid::new_v4())
+    }
+}
+
+impl From<Uuid> for QuoteId {
+    fn from(uuid: Uuid) -> Self {
+        Self::UUID(uuid)
+    }
+}
+
+impl fmt::Display for QuoteId {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            QuoteId::BASE64(s) => write!(f, "{}", s),
+            QuoteId::UUID(u) => write!(f, "{}", u.hyphenated()),
+        }
+    }
+}
+
+impl FromStr for QuoteId {
+    type Err = QuoteIdError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        // Try UUID first
+        if let Ok(u) = Uuid::parse_str(s) {
+            return Ok(QuoteId::UUID(u));
+        }
+
+        // Try base64: decode, then re-encode and compare to ensure canonical form
+        // Use the standard (URL/filename safe or standard) depending on your needed alphabet.
+        // Here we use standard base64.
+        match general_purpose::URL_SAFE.decode(s) {
+            Ok(_bytes) => Ok(QuoteId::BASE64(s.to_string())),
+            Err(_) => Err(QuoteIdError::InvalidQuoteId),
+        }
+    }
+}
+
+impl<'de> Deserialize<'de> for QuoteId {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        // Deserialize as plain string first
+        let s = String::deserialize(deserializer)?;
+
+        // Try UUID first
+        if let Ok(u) = Uuid::parse_str(&s) {
+            return Ok(QuoteId::UUID(u));
+        }
+
+        if general_purpose::URL_SAFE.decode(&s).is_ok() {
+            return Ok(QuoteId::BASE64(s));
+        }
+
+        // Neither matched — return a helpful error
+        Err(de::Error::custom(format!(
+            "QuoteId must be either a UUID (e.g. {}) or a valid base64 string; got: {}",
+            Uuid::nil(),
+            s
+        )))
+    }
+}

+ 12 - 6
crates/cdk-common/src/database/mint/mod.rs

@@ -5,7 +5,6 @@ use std::collections::HashMap;
 use async_trait::async_trait;
 use cashu::quote_id::QuoteId;
 use cashu::{Amount, MintInfo};
-use uuid::Uuid;
 
 use super::Error;
 use crate::common::QuoteTTL;
@@ -89,7 +88,7 @@ pub trait QuotesTransaction<'a> {
     /// Get [`mint::MeltQuote`] and lock it for update in this transaction
     async fn get_melt_quote(
         &mut self,
-        quote_id: &Uuid,
+        quote_id: &QuoteId,
     ) -> Result<Option<mint::MeltQuote>, Self::Err>;
     /// Add [`mint::MeltQuote`]
     async fn add_melt_quote(&mut self, quote: mint::MeltQuote) -> Result<(), Self::Err>;
@@ -111,7 +110,7 @@ pub trait QuotesTransaction<'a> {
         payment_proof: Option<String>,
     ) -> Result<(MeltQuoteState, mint::MeltQuote), Self::Err>;
     /// Remove [`mint::MeltQuote`]
-    async fn remove_melt_quote(&mut self, quote_id: &Uuid) -> Result<(), Self::Err>;
+    async fn remove_melt_quote(&mut self, quote_id: &QuoteId) -> Result<(), Self::Err>;
     /// Get all [`MintMintQuote`]s and lock it for update in this transaction
     async fn get_mint_quote_by_request(
         &mut self,
@@ -165,7 +164,11 @@ pub trait ProofsTransaction<'a> {
     ///
     /// Adds proofs to the database. The database should error if the proof already exits, with a
     /// `AttemptUpdateSpentProof` if the proof is already spent or a `Duplicate` error otherwise.
-    async fn add_proofs(&mut self, proof: Proofs, quote_id: Option<Uuid>) -> Result<(), Self::Err>;
+    async fn add_proofs(
+        &mut self,
+        proof: Proofs,
+        quote_id: Option<QuoteId>,
+    ) -> Result<(), Self::Err>;
     /// Updates the proofs to a given states and return the previous states
     async fn update_proofs_states(
         &mut self,
@@ -177,7 +180,7 @@ pub trait ProofsTransaction<'a> {
     async fn remove_proofs(
         &mut self,
         ys: &[PublicKey],
-        quote_id: Option<Uuid>,
+        quote_id: Option<QuoteId>,
     ) -> Result<(), Self::Err>;
 }
 
@@ -190,7 +193,10 @@ pub trait ProofsDatabase {
     /// Get [`Proofs`] by ys
     async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result<Vec<Option<Proof>>, Self::Err>;
     /// Get ys by quote id
-    async fn get_proof_ys_by_quote_id(&self, quote_id: &Uuid) -> Result<Vec<PublicKey>, Self::Err>;
+    async fn get_proof_ys_by_quote_id(
+        &self,
+        quote_id: &QuoteId,
+    ) -> Result<Vec<PublicKey>, Self::Err>;
     /// Get [`Proofs`] state
     async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err>;
     /// Get [`Proofs`] by state

+ 4 - 2
crates/cdk-common/src/database/mint/test.rs

@@ -87,7 +87,7 @@ where
 {
     let keyset_id = setup_keyset(&db).await;
 
-    let quote_id = Uuid::max();
+    let quote_id = QuoteId::new_uuid();
 
     let proofs = vec![
         Proof {
@@ -110,7 +110,9 @@ where
 
     // Add proofs to database
     let mut tx = Database::begin_transaction(&db).await.unwrap();
-    tx.add_proofs(proofs.clone(), Some(quote_id)).await.unwrap();
+    tx.add_proofs(proofs.clone(), Some(quote_id.clone()))
+        .await
+        .unwrap();
     assert!(tx.commit().await.is_ok());
 
     let proofs_from_db = db.get_proofs_by_ys(&[proofs[0].c, proofs[1].c]).await;

+ 0 - 1
crates/cdk-sql-common/Cargo.toml

@@ -27,5 +27,4 @@ tokio.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 lightning-invoice.workspace = true
-uuid.workspace = true
 once_cell.workspace = true

+ 29 - 123
crates/cdk-sql-common/src/mint/mod.rs

@@ -37,7 +37,6 @@ use cdk_common::{
 use lightning_invoice::Bolt11Invoice;
 use migrations::MIGRATIONS;
 use tracing::instrument;
-use uuid::Uuid;
 
 use crate::common::migrate;
 use crate::database::{ConnectionWithTransaction, DatabaseExecutor};
@@ -170,7 +169,7 @@ where
     async fn add_proofs(
         &mut self,
         proofs: Proofs,
-        quote_id: Option<Uuid>,
+        quote_id: Option<QuoteId>,
     ) -> Result<(), Self::Err> {
         let current_time = unix_time();
 
@@ -213,7 +212,7 @@ where
                 proof.witness.map(|w| serde_json::to_string(&w).unwrap()),
             )
             .bind("state", "UNSPENT".to_string())
-            .bind("quote_id", quote_id.map(|q| q.hyphenated().to_string()))
+            .bind("quote_id", quote_id.clone().map(|q| q.to_string()))
             .bind("created_time", current_time as i64)
             .execute(&self.inner)
             .await?;
@@ -254,7 +253,7 @@ where
     async fn remove_proofs(
         &mut self,
         ys: &[PublicKey],
-        _quote_id: Option<Uuid>,
+        _quote_id: Option<QuoteId>,
     ) -> Result<(), Self::Err> {
         if ys.is_empty() {
             return Ok(());
@@ -328,13 +327,7 @@ where
             quote_id=:quote_id
         "#,
     )?
-    .bind(
-        "quote_id",
-        match quote_id {
-            QuoteId::UUID(u) => u.as_hyphenated().to_string(),
-            QuoteId::BASE64(s) => s.to_string(),
-        },
-    )
+    .bind("quote_id", quote_id.to_string())
     .fetch_all(conn)
     .await?
     .into_iter()
@@ -363,13 +356,7 @@ FROM mint_quote_issued
 WHERE quote_id=:quote_id
             "#,
     )?
-    .bind(
-        "quote_id",
-        match quote_id {
-            QuoteId::UUID(u) => u.as_hyphenated().to_string(),
-            QuoteId::BASE64(s) => s.to_string(),
-        },
-    )
+    .bind("quote_id", quote_id.to_string())
     .fetch_all(conn)
     .await?
     .into_iter()
@@ -591,13 +578,7 @@ where
             FOR UPDATE
             "#,
         )?
-        .bind(
-            "quote_id",
-            match quote_id {
-                QuoteId::UUID(u) => u.as_hyphenated().to_string(),
-                QuoteId::BASE64(s) => s.to_string(),
-            },
-        )
+        .bind("quote_id", quote_id.to_string())
         .fetch_one(&self.inner)
         .await
         .inspect_err(|err| {
@@ -632,13 +613,7 @@ where
             "#,
         )?
         .bind("amount_paid", new_amount_paid.to_i64())
-        .bind(
-            "quote_id",
-            match quote_id {
-                QuoteId::UUID(u) => u.as_hyphenated().to_string(),
-                QuoteId::BASE64(s) => s.to_string(),
-            },
-        )
+        .bind("quote_id", quote_id.to_string())
         .execute(&self.inner)
         .await
         .inspect_err(|err| {
@@ -653,13 +628,7 @@ where
             VALUES (:quote_id, :payment_id, :amount, :timestamp)
             "#,
         )?
-        .bind(
-            "quote_id",
-            match quote_id {
-                QuoteId::UUID(u) => u.as_hyphenated().to_string(),
-                QuoteId::BASE64(s) => s.to_string(),
-            },
-        )
+        .bind("quote_id", quote_id.to_string())
         .bind("payment_id", payment_id)
         .bind("amount", amount_paid.to_i64())
         .bind("timestamp", unix_time() as i64)
@@ -688,13 +657,7 @@ where
             FOR UPDATE
             "#,
         )?
-        .bind(
-            "quote_id",
-            match quote_id {
-                QuoteId::UUID(u) => u.as_hyphenated().to_string(),
-                QuoteId::BASE64(s) => s.to_string(),
-            },
-        )
+        .bind("quote_id", quote_id.to_string())
         .fetch_one(&self.inner)
         .await
         .inspect_err(|err| {
@@ -722,13 +685,7 @@ where
             "#,
         )?
         .bind("amount_issued", new_amount_issued.to_i64())
-        .bind(
-            "quote_id",
-            match quote_id {
-                QuoteId::UUID(u) => u.as_hyphenated().to_string(),
-                QuoteId::BASE64(s) => s.to_string(),
-            },
-        )
+        .bind("quote_id", quote_id.to_string())
         .execute(&self.inner)
         .await
         .inspect_err(|err| {
@@ -744,13 +701,7 @@ INSERT INTO mint_quote_issued
 VALUES (:quote_id, :amount, :timestamp);
             "#,
         )?
-        .bind(
-            "quote_id",
-            match quote_id {
-                QuoteId::UUID(u) => u.as_hyphenated().to_string(),
-                QuoteId::BASE64(s) => s.to_string(),
-            },
-        )
+        .bind("quote_id", quote_id.to_string())
         .bind("amount", amount_issued.to_i64())
         .bind("timestamp", current_time as i64)
         .execute(&self.inner)
@@ -792,13 +743,7 @@ VALUES (:quote_id, :amount, :timestamp);
 
     async fn remove_mint_quote(&mut self, quote_id: &QuoteId) -> Result<(), Self::Err> {
         query(r#"DELETE FROM mint_quote WHERE id=:id"#)?
-            .bind(
-                "id",
-                match quote_id {
-                    QuoteId::UUID(u) => u.as_hyphenated().to_string(),
-                    QuoteId::BASE64(s) => s.to_string(),
-                },
-            )
+            .bind("id", quote_id.to_string())
             .execute(&self.inner)
             .await?;
         Ok(())
@@ -861,10 +806,7 @@ VALUES (:quote_id, :amount, :timestamp);
         query(r#"UPDATE melt_quote SET request_lookup_id = :new_req_id, request_lookup_id_kind = :new_kind WHERE id = :id"#)?
             .bind("new_req_id", new_request_lookup_id.to_string())
             .bind("new_kind",new_request_lookup_id.kind() )
-            .bind("id", match quote_id {
-                QuoteId::BASE64(s) => s.to_string(),
-                QuoteId::UUID(u) => u.as_hyphenated().to_string(),
-            })
+            .bind("id", quote_id.to_string())
             .execute(&self.inner)
             .await?;
         Ok(())
@@ -900,13 +842,7 @@ VALUES (:quote_id, :amount, :timestamp);
                 AND state != :state
             "#,
         )?
-        .bind(
-            "id",
-            match quote_id {
-                QuoteId::UUID(u) => u.as_hyphenated().to_string(),
-                QuoteId::BASE64(s) => s.to_string(),
-            },
-        )
+        .bind("id", quote_id.to_string())
         .bind("state", state.to_string())
         .fetch_one(&self.inner)
         .await?
@@ -920,22 +856,13 @@ VALUES (:quote_id, :amount, :timestamp);
                 .bind("state", state.to_string())
                 .bind("paid_time", current_time as i64)
                 .bind("payment_preimage", payment_proof)
-                .bind("id", match quote_id {
-                    QuoteId::UUID(u) => u.as_hyphenated().to_string(),
-                    QuoteId::BASE64(s) => s.to_string(),
-                })
+                .bind("id", quote_id.to_string())
                 .execute(&self.inner)
                 .await
         } else {
             query(r#"UPDATE melt_quote SET state = :state WHERE id = :id"#)?
                 .bind("state", state.to_string())
-                .bind(
-                    "id",
-                    match quote_id {
-                        QuoteId::UUID(u) => u.as_hyphenated().to_string(),
-                        QuoteId::BASE64(s) => s.to_string(),
-                    },
-                )
+                .bind("id", quote_id.to_string())
                 .execute(&self.inner)
                 .await
         };
@@ -954,14 +881,14 @@ VALUES (:quote_id, :amount, :timestamp);
         Ok((old_state, quote))
     }
 
-    async fn remove_melt_quote(&mut self, quote_id: &Uuid) -> Result<(), Self::Err> {
+    async fn remove_melt_quote(&mut self, quote_id: &QuoteId) -> Result<(), Self::Err> {
         query(
             r#"
             DELETE FROM melt_quote
             WHERE id=?
             "#,
         )?
-        .bind("id", quote_id.as_hyphenated().to_string())
+        .bind("id", quote_id.to_string())
         .execute(&self.inner)
         .await?;
 
@@ -993,13 +920,7 @@ VALUES (:quote_id, :amount, :timestamp);
             FOR UPDATE
             "#,
         )?
-        .bind(
-            "id",
-            match quote_id {
-                QuoteId::UUID(u) => u.as_hyphenated().to_string(),
-                QuoteId::BASE64(s) => s.to_string(),
-            },
-        )
+        .bind("id", quote_id.to_string())
         .fetch_one(&self.inner)
         .await?
         .map(|row| sql_row_to_mint_quote(row, payments, issuance))
@@ -1008,7 +929,7 @@ VALUES (:quote_id, :amount, :timestamp);
 
     async fn get_melt_quote(
         &mut self,
-        quote_id: &Uuid,
+        quote_id: &QuoteId,
     ) -> Result<Option<mint::MeltQuote>, Self::Err> {
         Ok(query(
             r#"
@@ -1033,7 +954,7 @@ VALUES (:quote_id, :amount, :timestamp);
                 id=:id
             "#,
         )?
-        .bind("id", quote_id.as_hyphenated().to_string())
+        .bind("id", quote_id.to_string())
         .fetch_one(&self.inner)
         .await?
         .map(sql_row_to_melt_quote)
@@ -1157,13 +1078,7 @@ where
                 mint_quote
             WHERE id = :id"#,
         )?
-        .bind(
-            "id",
-            match quote_id {
-                QuoteId::UUID(u) => u.as_hyphenated().to_string(),
-                QuoteId::BASE64(s) => s.to_string(),
-            },
-        )
+        .bind("id", quote_id.to_string())
         .fetch_one(&*conn)
         .await?
         .map(|row| sql_row_to_mint_quote(row, payments, issuance))
@@ -1319,13 +1234,7 @@ where
                 id=:id
             "#,
         )?
-        .bind(
-            "id",
-            match quote_id {
-                QuoteId::BASE64(s) => s.to_string(),
-                QuoteId::UUID(u) => u.as_hyphenated().to_string(),
-            },
-        )
+        .bind("id", quote_id.to_string())
         .fetch_one(&*conn)
         .await?
         .map(sql_row_to_melt_quote)
@@ -1406,7 +1315,10 @@ where
         Ok(ys.iter().map(|y| proofs.remove(y)).collect())
     }
 
-    async fn get_proof_ys_by_quote_id(&self, quote_id: &Uuid) -> Result<Vec<PublicKey>, Self::Err> {
+    async fn get_proof_ys_by_quote_id(
+        &self,
+        quote_id: &QuoteId,
+    ) -> Result<Vec<PublicKey>, Self::Err> {
         let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
         Ok(query(
             r#"
@@ -1422,7 +1334,7 @@ where
                 quote_id = :quote_id
             "#,
         )?
-        .bind("quote_id", quote_id.as_hyphenated().to_string())
+        .bind("quote_id", quote_id.to_string())
         .fetch_all(&*conn)
         .await?
         .into_iter()
@@ -1661,13 +1573,7 @@ where
                 quote_id=:quote_id
             "#,
         )?
-        .bind(
-            "quote_id",
-            match quote_id {
-                QuoteId::BASE64(s) => s.to_string(),
-                QuoteId::UUID(u) => u.as_hyphenated().to_string(),
-            },
-        )
+        .bind("quote_id", quote_id.to_string())
         .fetch_all(&*conn)
         .await?
         .into_iter()