gl_client/lsps/lsps0/
common_schemas.rs

1use core::str::FromStr;
2
3use std::fmt::{Display, Formatter};
4
5use anyhow::{anyhow, Context};
6
7use serde::de::Error as SeError;
8use serde::ser::Error as DeError;
9use serde::{Deserialize, Serialize, Deserializer, Serializer};
10
11use time::format_description::FormatItem;
12use time::macros::format_description;
13use time::{OffsetDateTime, PrimitiveDateTime};
14
15// Implements all the common schema's defined in LSPS0 common schema's
16
17// Initially I used serde_as for the parsing and serialization of this type.
18// However, the spec is more strict.
19// It requires a yyyy-mm-ddThh:mm:ss.uuuZ format
20//
21// The serde_as provides us options such as rfc_3339.
22// Note, that this also allows formats that are not compliant to the LSP-spec such as dropping
23// the fractional seconds or use non UTC timezones.
24//
25// For LSPS2 the `valid_until`-field must be copied verbatim. As a client this can only be
26// achieved if the LSPS2 sends a fully compliant timestamp.
27//
28// I have decided to fail early if another timestamp is received
29#[derive(Debug)]
30pub struct IsoDatetime {
31    pub datetime: PrimitiveDateTime,
32}
33
34const DATETIME_FORMAT: &[FormatItem] =
35    format_description!("[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]Z");
36
37impl IsoDatetime {
38    pub fn from_offset_date_time(datetime: OffsetDateTime) -> Self {
39        let offset = time::UtcOffset::from_whole_seconds(0).unwrap();
40        let datetime_utc = datetime.to_offset(offset);
41        let primitive = PrimitiveDateTime::new(datetime_utc.date(), datetime.time());
42        Self {
43            datetime: primitive,
44        }
45    }
46
47    pub fn from_primitive_date_time(datetime: PrimitiveDateTime) -> Self {
48        Self { datetime }
49    }
50
51    pub fn datetime(&self) -> OffsetDateTime {
52        self.datetime.assume_utc()
53    }
54}
55
56impl Serialize for IsoDatetime {
57    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
58    where
59        S: serde::Serializer,
60    {
61        let datetime_str = self
62            .datetime
63            .format(&DATETIME_FORMAT)
64            .map_err(|err| S::Error::custom(format!("Failed to format datetime {:?}", err)))?;
65
66        serializer.serialize_str(&datetime_str)
67    }
68}
69
70impl<'de> Deserialize<'de> for IsoDatetime {
71    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
72    where
73        D: serde::Deserializer<'de>,
74    {
75        let str_repr = <String as serde::de::Deserialize>::deserialize(deserializer)?;
76        time::PrimitiveDateTime::parse(&str_repr, DATETIME_FORMAT)
77            .map_err(|err| D::Error::custom(format!("Failed to parse Datetime. {:?}", err)))
78            .map(Self::from_primitive_date_time)
79    }
80}
81
82#[derive(Debug)]
83pub struct SatAmount(u64);
84#[derive(Debug)]
85pub struct MsatAmount(u64);
86
87impl SatAmount {
88    pub fn sat_value(&self) -> u64 {
89        self.0
90    }
91
92    pub fn new(value: u64) -> Self {
93        SatAmount(value)
94    }
95}
96
97impl MsatAmount {
98    pub fn msat_value(&self) -> u64 {
99        self.0
100    }
101
102    pub fn new(value: u64) -> Self {
103        MsatAmount(value)
104    }
105}
106
107impl Serialize for SatAmount {
108    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
109    where
110        S: serde::Serializer,
111    {
112        let amount_str = self.0.to_string();
113        serializer.serialize_str(&amount_str)
114    }
115}
116
117impl Serialize for MsatAmount {
118    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
119    where
120        S: serde::Serializer,
121    {
122        let amount_str = self.0.to_string();
123        serializer.serialize_str(&amount_str)
124    }
125}
126
127impl<'de> Deserialize<'de> for SatAmount {
128    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
129    where
130        D: serde::Deserializer<'de>,
131    {
132        let str_repr = <String as serde::de::Deserialize>::deserialize(deserializer)?;
133        let u64_repr: Result<u64, _> = str_repr
134            .parse()
135            .map_err(|_| D::Error::custom(String::from("Failed to parse sat_amount")));
136        Ok(Self(u64_repr.unwrap()))
137    }
138}
139
140impl<'de> Deserialize<'de> for MsatAmount {
141    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
142    where
143        D: serde::Deserializer<'de>,
144    {
145        let str_repr = <String as serde::de::Deserialize>::deserialize(deserializer)?;
146        let u64_repr: Result<u64, _> = str_repr
147            .parse()
148            .map_err(|_| D::Error::custom(String::from("Failed to parse sat_amount")));
149        Ok(Self(u64_repr.unwrap()))
150    }
151}
152
153
154#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
155pub struct ShortChannelId(u64);
156
157impl Serialize for ShortChannelId {
158    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
159    where
160        S: Serializer,
161    {
162        serializer.serialize_str(&self.to_string())
163    }
164}
165
166impl<'de> Deserialize<'de> for ShortChannelId {
167    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
168    where
169        D: Deserializer<'de>,
170    {
171        use serde::de::Error;
172        let s: String = Deserialize::deserialize(deserializer)?;
173        Ok(Self::from_str(&s).map_err(|e| Error::custom(e.to_string()))?)
174    }
175}
176
177impl FromStr for ShortChannelId {
178    type Err = anyhow::Error;
179    fn from_str(s: &str) -> Result<Self, Self::Err> {
180        let parts: Result<Vec<u64>, _> = s.split('x').map(|p| p.parse()).collect();
181        let parts = parts.with_context(|| format!("Malformed short_channel_id: {}", s))?;
182        if parts.len() != 3 {
183            return Err(anyhow!(
184                "Malformed short_channel_id: element count mismatch"
185            ));
186        }
187
188        Ok(ShortChannelId(
189            (parts[0] << 40) | (parts[1] << 16) | (parts[2] << 0),
190        ))
191    }
192}
193impl Display for ShortChannelId {
194    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
195        write!(f, "{}x{}x{}", self.block(), self.txindex(), self.outnum())
196    }
197}
198impl ShortChannelId {
199    pub fn block(&self) -> u32 {
200        (self.0 >> 40) as u32 & 0xFFFFFF
201    }
202    pub fn txindex(&self) -> u32 {
203        (self.0 >> 16) as u32 & 0xFFFFFF
204    }
205    pub fn outnum(&self) -> u16 {
206        self.0 as u16 & 0xFFFF
207    }
208}
209
210#[cfg(test)]
211mod test {
212    use super::*;
213
214    #[test]
215    fn parsing_amount_sats() {
216        // Pick a number which exceeds 2^32 to ensure internal representation exceeds 32 bits
217        let json_str_number = "\"10000000001\"";
218
219        let int_number: u64 = 10000000001;
220
221        let x = serde_json::from_str::<SatAmount>(json_str_number).unwrap();
222        assert_eq!(x.sat_value(), int_number);
223    }
224
225    #[test]
226    fn serializing_amount_sats() {
227        // Pick a number which exceeds 2^32 to ensure internal representation exceeds 32 bits
228        // The json_str includes the " to indicate it is a string
229        let json_str_number = "\"10000000001\"";
230        let int_number: u64 = 10000000001;
231
232        let sat_amount = SatAmount::new(int_number);
233
234        let json_str = serde_json::to_string::<SatAmount>(&sat_amount).unwrap();
235        assert_eq!(json_str, json_str_number);
236    }
237
238    #[test]
239    fn parse_and_serialize_datetime() {
240        let datetime_str = "\"2023-01-01T23:59:59.999Z\"";
241
242        let dt = serde_json::from_str::<IsoDatetime>(datetime_str).unwrap();
243
244        assert_eq!(dt.datetime.year(), 2023);
245        assert_eq!(dt.datetime.month(), time::Month::January);
246        assert_eq!(dt.datetime.day(), 1);
247        assert_eq!(dt.datetime.hour(), 23);
248        assert_eq!(dt.datetime.minute(), 59);
249        assert_eq!(dt.datetime.second(), 59);
250
251        assert_eq!(
252            serde_json::to_string(&dt).expect("Can be serialized"),
253            datetime_str
254        )
255    }
256
257    #[test]
258    fn parse_datetime_that_doesnt_follow_spec() {
259        // The spec doesn't explicitly say that clients have to ignore datetimes that don't follow the spec
260        // However, in LSPS2 the datetime_str must be repeated verbatim
261        let datetime_str = "\"2023-01-01T23:59:59.99Z\"";
262
263        let result = serde_json::from_str::<IsoDatetime>(datetime_str);
264        result.expect_err("datetime_str should not be parsed if it doesn't follow spec");
265    }
266
267    #[test]
268    #[allow(clippy::unusual_byte_groupings)]
269    fn parse_scid_from_string() {
270        // How to read this test
271        //
272        // The shortchannel_id is 8 bytes long.
273        // The 3 first bytes are the blockheight, 3 next bytes are the txid and last 2 bytes are vout
274        // This explains the unusual byte groupings
275
276        // The string representation are the same numbers separated by the letter x
277
278        // Test the largest possible value
279        let scid_str = "16777215x16777215x65535";
280
281        let scid = ShortChannelId::from_str(scid_str).expect("The scid is parseable");
282        assert_eq!(scid.to_string(), scid_str);
283
284        // Test the smallest possible value
285        let scid_str = "0x0x0";
286
287        let scid = ShortChannelId::from_str(scid_str).expect("The scid is parseable");
288        assert_eq!(scid.to_string(), scid_str);
289
290        let scid_str = "1x2x3";
291
292        let scid = ShortChannelId::from_str(scid_str).expect("The scid is parseable");
293        assert_eq!(scid.to_string(), scid_str);
294        // A couple of unparseable scids
295        assert!(ShortChannelId::from_str("xx").is_err());
296        assert!(ShortChannelId::from_str("0x0").is_err());
297        assert!(ShortChannelId::from_str("-2x-12x14").is_err());
298    }
299
300    #[test]
301    fn short_channel_id_is_serialized_as_str() {
302        let scid: ShortChannelId = ShortChannelId::from_str("10x5x8").unwrap();
303        let scid_json_obj = serde_json::to_string(&scid).expect("Can be serialized");
304        assert_eq!("\"10x5x8\"", scid_json_obj);
305    }
306
307    #[test]
308    fn short_channel_id_can_be_deserialized_from_str() {
309        let scid_json = "\"11x12x13\"";
310
311        let scid = serde_json::from_str::<ShortChannelId>(scid_json).expect("scid can be parsed");
312
313        assert_eq!(scid, ShortChannelId::from_str("11x12x13").unwrap());
314    }
315}