quote_id.rs 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. //! Quote ID. The specifications only define a string but CDK uses Uuid, so we use an enum to port compatibility.
  2. use std::fmt;
  3. use std::str::FromStr;
  4. use bitcoin::base64::engine::general_purpose;
  5. use bitcoin::base64::Engine as _;
  6. use serde::{de, Deserialize, Deserializer, Serialize};
  7. use thiserror::Error;
  8. use uuid::Uuid;
  9. /// Invalid UUID
  10. #[derive(Debug, Error)]
  11. pub enum QuoteIdError {
  12. /// UUID Error
  13. #[error("invalid UUID: {0}")]
  14. Uuid(#[from] uuid::Error),
  15. /// Invalid base64
  16. #[error("invalid base64")]
  17. Base64,
  18. /// Invalid quote ID
  19. #[error("neither a valid UUID nor a valid base64 string")]
  20. InvalidQuoteId,
  21. }
  22. /// Mint Quote ID
  23. #[derive(Serialize, Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
  24. #[serde(untagged)]
  25. pub enum QuoteId {
  26. /// (Nutshell) base64 quote ID
  27. BASE64(String),
  28. /// UUID quote ID
  29. UUID(Uuid),
  30. }
  31. impl QuoteId {
  32. /// Create a new UUID-based MintQuoteId
  33. pub fn new_uuid() -> Self {
  34. Self::UUID(Uuid::new_v4())
  35. }
  36. }
  37. impl From<Uuid> for QuoteId {
  38. fn from(uuid: Uuid) -> Self {
  39. Self::UUID(uuid)
  40. }
  41. }
  42. impl fmt::Display for QuoteId {
  43. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  44. match self {
  45. QuoteId::BASE64(s) => write!(f, "{s}"),
  46. QuoteId::UUID(u) => write!(f, "{}", u.hyphenated()),
  47. }
  48. }
  49. }
  50. impl FromStr for QuoteId {
  51. type Err = QuoteIdError;
  52. fn from_str(s: &str) -> Result<Self, Self::Err> {
  53. // Try UUID first
  54. if let Ok(u) = Uuid::parse_str(s) {
  55. return Ok(QuoteId::UUID(u));
  56. }
  57. // Try base64: decode, then re-encode and compare to ensure canonical form
  58. // Use the standard (URL/filename safe or standard) depending on your needed alphabet.
  59. // Here we use standard base64.
  60. match general_purpose::URL_SAFE.decode(s) {
  61. Ok(_bytes) => Ok(QuoteId::BASE64(s.to_string())),
  62. Err(_) => Err(QuoteIdError::InvalidQuoteId),
  63. }
  64. }
  65. }
  66. impl<'de> Deserialize<'de> for QuoteId {
  67. fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
  68. where
  69. D: Deserializer<'de>,
  70. {
  71. // Deserialize as plain string first
  72. let s = String::deserialize(deserializer)?;
  73. // Try UUID first
  74. if let Ok(u) = Uuid::parse_str(&s) {
  75. return Ok(QuoteId::UUID(u));
  76. }
  77. if general_purpose::URL_SAFE.decode(&s).is_ok() {
  78. return Ok(QuoteId::BASE64(s));
  79. }
  80. // Neither matched — return a helpful error
  81. Err(de::Error::custom(format!(
  82. "QuoteId must be either a UUID (e.g. {}) or a valid base64 string; got: {}",
  83. Uuid::nil(),
  84. s
  85. )))
  86. }
  87. }