diff --git a/cln-rpc/src/primitives.rs b/cln-rpc/src/primitives.rs index 0c1faa31ac7e..0a719620210b 100644 --- a/cln-rpc/src/primitives.rs +++ b/cln-rpc/src/primitives.rs @@ -28,6 +28,23 @@ pub enum ChannelStateChangeCause { ONCHAIN, } +/// An `Amount` that can also be `any`. Useful for cases in which you +/// want to delegate the Amount selection so someone else, e.g., an +/// amountless invoice. +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum AmountOrAny { + Amount(Amount), + Any, +} + +/// An amount that can also be `all`. Useful for cases where you want +/// to delegate the amount computation to the cln node. +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum AmountOrAll { + Amount(Amount), + All, +} + #[derive(Copy, Clone, Debug, PartialEq)] pub struct Amount { msat: u64, @@ -75,6 +92,62 @@ impl Serialize for Amount { } } +impl Serialize for AmountOrAll { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + AmountOrAll::Amount(a) => serializer.serialize_str(&format!("{}msat", a.msat)), + AmountOrAll::All => serializer.serialize_str("all"), + } + } +} + +impl Serialize for AmountOrAny { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + AmountOrAny::Amount(a) => serializer.serialize_str(&format!("{}msat", a.msat)), + AmountOrAny::Any => serializer.serialize_str("any"), + } + } +} + +impl<'de> Deserialize<'de> for AmountOrAny { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s: String = Deserialize::deserialize(deserializer)?; + Ok(match s.to_lowercase().as_ref() { + "any" => AmountOrAny::Any, + v => AmountOrAny::Amount( + v.try_into() + .map_err(|_e| serde::de::Error::custom("could not parse amount"))?, + ), + }) + } +} + +impl<'de> Deserialize<'de> for AmountOrAll { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s: String = Deserialize::deserialize(deserializer)?; + Ok(match s.to_lowercase().as_ref() { + "all" => AmountOrAll::All, + v => AmountOrAll::Amount( + v.try_into() + .map_err(|_e| serde::de::Error::custom("could not parse amount"))?, + ), + }) + } +} + impl TryFrom<&str> for Amount { type Error = Error; fn try_from(s: &str) -> Result { @@ -139,4 +212,33 @@ mod test { assert_eq!(s, serialized); } } + + #[test] + fn test_amount_all_any() { + let t = r#"{"any": "any", "all": "all", "not_any": "42msat", "not_all": "31337msat"}"#; + + #[derive(Serialize, Deserialize, Debug, PartialEq)] + struct T { + all: AmountOrAll, + not_all: AmountOrAll, + any: AmountOrAny, + not_any: AmountOrAny, + } + + let parsed: T = serde_json::from_str(t).unwrap(); + + let expected = T { + all: AmountOrAll::All, + any: AmountOrAny::Any, + not_all: AmountOrAll::Amount(Amount { msat: 31337 }), + not_any: AmountOrAny::Amount(Amount { msat: 42 }), + }; + assert_eq!(expected, parsed); + + let serialized: String = serde_json::to_string(&parsed).unwrap(); + assert_eq!( + serialized, + r#"{"all":"all","not_all":"31337msat","any":"any","not_any":"42msat"}"# + ); + } }