rust_blocktank_client/
utils.rs1use chrono::{DateTime, TimeZone, Utc};
2use url::Url;
3use crate::error::Result;
4
5pub(crate) fn normalize_url(url: &str) -> Result<Url> {
10 let url = if url.ends_with('/') {
11 &url[..url.len() - 1]
12 } else {
13 url
14 };
15
16 let parsed = Url::parse(url)?;
17 Ok(parsed)
18}
19
20pub fn sats_to_btc(sats: u64) -> f64 {
22 sats as f64 / 100_000_000.0
23}
24
25pub fn btc_to_sats(btc: f64) -> u64 {
27 (btc * 100_000_000.0) as u64
28}
29
30pub fn parse_ln_connection_string(conn_str: &str) -> Option<(String, String, u16)> {
32 let parts: Vec<&str> = conn_str.split('@').collect();
33 if parts.len() != 2 {
34 return None;
35 }
36
37 let pubkey = parts[0].to_string();
38 let addr_parts: Vec<&str> = parts[1].split(':').collect();
39 if addr_parts.len() != 2 {
40 return None;
41 }
42
43 let host = addr_parts[0].to_string();
44 let port = addr_parts[1].parse().ok()?;
45
46 Some((pubkey, host, port))
47}
48
49pub(crate) fn validate_channel_params(
51 lsp_balance_sat: u64,
52 client_balance_sat: u64,
53 channel_expiry_weeks: u32,
54 min_channel_size: u64,
55 max_channel_size: u64,
56 min_expiry: u32,
57 max_expiry: u32,
58) -> Result<()> {
59 let total_size = lsp_balance_sat + client_balance_sat;
60
61 if total_size < min_channel_size {
62 return Err(crate::error::BlocktankError::BlocktankClient {
63 message: "Channel size too small".to_string(),
64 data: serde_json::json!({
65 "min_size": min_channel_size,
66 "requested_size": total_size
67 }),
68 });
69 }
70
71 if total_size > max_channel_size {
72 return Err(crate::error::BlocktankError::BlocktankClient {
73 message: "Channel size too large".to_string(),
74 data: serde_json::json!({
75 "max_size": max_channel_size,
76 "requested_size": total_size
77 }),
78 });
79 }
80
81 if channel_expiry_weeks < min_expiry || channel_expiry_weeks > max_expiry {
82 return Err(crate::error::BlocktankError::BlocktankClient {
83 message: "Invalid expiry period".to_string(),
84 data: serde_json::json!({
85 "min_weeks": min_expiry,
86 "max_weeks": max_expiry,
87 "requested_weeks": channel_expiry_weeks
88 }),
89 });
90 }
91
92 Ok(())
93}
94
95pub fn datetime_to_string(date: &DateTime<Utc>) -> String {
98 date.to_rfc3339()
99}
100
101pub fn timestamp_to_datetime(timestamp: i64) -> DateTime<Utc> {
103 Utc.timestamp_opt(timestamp, 0)
104 .single()
105 .expect("Invalid timestamp")
106}
107
108pub fn datetime_to_timestamp(date: &DateTime<Utc>) -> i64 {
110 date.timestamp()
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn test_normalize_url() {
119 let url = normalize_url("http://example.com/api/").unwrap();
120 assert_eq!(url.as_str(), "http://example.com/api");
121
122 let url = normalize_url("http://example.com/api").unwrap();
123 assert_eq!(url.as_str(), "http://example.com/api");
124 }
125
126 #[test]
127 fn test_sats_conversion() {
128 assert_eq!(sats_to_btc(100_000_000), 1.0);
129 assert_eq!(btc_to_sats(1.0), 100_000_000);
130 }
131
132 #[test]
133 fn test_parse_ln_connection_string() {
134 let conn_str = "023c22c02645293e1600b8c5f14136844af0cc6701f2d3422c571bbb4208aa5781@127.0.0.1:9735";
135 let (pubkey, host, port) = parse_ln_connection_string(conn_str).unwrap();
136 assert_eq!(pubkey, "023c22c02645293e1600b8c5f14136844af0cc6701f2d3422c571bbb4208aa5781");
137 assert_eq!(host, "127.0.0.1");
138 assert_eq!(port, 9735);
139
140 assert!(parse_ln_connection_string("invalid").is_none());
141 }
142
143 #[test]
144 fn test_validate_channel_params() {
145 assert!(validate_channel_params(
147 50_000, 50_000, 4, 50_000, 1_000_000, 1, 52 ).is_ok());
155
156 assert!(validate_channel_params(
158 10_000, 10_000, 4, 50_000, 1_000_000, 1, 52 ).is_err());
166
167 assert!(validate_channel_params(
169 2_000_000, 0, 4, 50_000, 1_000_000, 1, 52 ).is_err());
177
178 assert!(validate_channel_params(
180 50_000, 50_000, 60, 50_000, 1_000_000, 1, 52 ).is_err());
188 }
189
190 #[test]
191 fn test_timestamp_conversions() {
192 let timestamp = 1706013296; let dt = timestamp_to_datetime(timestamp);
194 assert_eq!(datetime_to_timestamp(&dt), timestamp);
195 }
196}