1use crate::options::UntypedConfigOption;
2use serde::de::{self, Deserializer};
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::collections::HashMap;
6use std::fmt::Debug;
7
8#[derive(Deserialize, Debug)]
9#[serde(tag = "method", content = "params")]
10#[serde(rename_all = "snake_case")]
11pub(crate) enum Request {
12 Getmanifest(GetManifestCall),
14 Init(InitCall),
15 }
39
40#[derive(Deserialize, Debug)]
41#[serde(tag = "method", content = "params")]
42#[serde(rename_all = "snake_case")]
43pub(crate) enum Notification {
44 }
59
60#[derive(Deserialize, Debug)]
61pub(crate) struct GetManifestCall {}
62
63#[derive(Deserialize, Debug)]
64pub(crate) struct InitCall {
65 pub(crate) options: HashMap<String, Value>,
66 pub configuration: Configuration,
67}
68
69#[derive(Clone, Deserialize, Debug)]
70pub struct Configuration {
71 #[serde(rename = "lightning-dir")]
72 pub lightning_dir: String,
73 #[serde(rename = "rpc-file")]
74 pub rpc_file: String,
75 pub startup: bool,
76 pub network: String,
77 pub feature_set: HashMap<String, String>,
78
79 pub proxy: Option<ProxyInfo>,
82 #[serde(rename = "torv3-enabled")]
83 pub torv3_enabled: Option<bool>,
84 pub always_use_proxy: Option<bool>,
85}
86
87#[derive(Clone, Debug, Deserialize)]
88pub struct ProxyInfo {
89 #[serde(alias = "type")]
90 pub typ: String,
91 pub address: String,
92 pub port: i64,
93}
94
95#[derive(Debug)]
96pub(crate) enum JsonRpc<N, R> {
97 Request(serde_json::Value, R),
98 Notification(N),
99 CustomRequest(serde_json::Value, Value),
100 CustomNotification(Value),
101}
102
103impl<'de, N, R> Deserialize<'de> for JsonRpc<N, R>
118where
119 N: Deserialize<'de> + Debug,
120 R: Deserialize<'de> + Debug,
121{
122 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
123 where
124 D: Deserializer<'de>,
125 {
126 #[derive(Deserialize, Debug)]
127 struct IdHelper {
128 id: Option<serde_json::Value>,
129 }
130
131 let v = Value::deserialize(deserializer)?;
132 let helper = IdHelper::deserialize(&v).map_err(de::Error::custom)?;
133 match helper.id {
134 Some(id) => match R::deserialize(v.clone()) {
135 Ok(r) => Ok(JsonRpc::Request(id, r)),
136 Err(_) => Ok(JsonRpc::CustomRequest(id, v)),
137 },
138 None => match N::deserialize(v.clone()) {
139 Ok(n) => Ok(JsonRpc::Notification(n)),
140 Err(_) => Ok(JsonRpc::CustomNotification(v)),
141 },
142 }
143 }
144}
145
146#[derive(Serialize, Default, Debug)]
147pub(crate) struct RpcMethod {
148 pub(crate) name: String,
149 pub(crate) description: String,
150 pub(crate) usage: String,
151}
152
153#[derive(Serialize, Default, Debug, Clone)]
154pub struct NotificationTopic {
155 pub method: String,
156}
157
158impl NotificationTopic {
159 pub fn method(&self) -> &str {
160 &self.method
161 }
162}
163
164impl NotificationTopic {
165 pub fn new(method: &str) -> Self {
166 Self {
167 method: method.to_string(),
168 }
169 }
170}
171
172#[derive(Serialize, Default, Debug)]
173pub(crate) struct GetManifestResponse {
174 pub(crate) options: Vec<UntypedConfigOption>,
175 pub(crate) rpcmethods: Vec<RpcMethod>,
176 pub(crate) subscriptions: Vec<String>,
177 pub(crate) notifications: Vec<NotificationTopic>,
178 pub(crate) hooks: Vec<String>,
179 pub(crate) dynamic: bool,
180 pub(crate) featurebits: FeatureBits,
181 pub(crate) nonnumericids: bool,
182 #[serde(skip_serializing_if = "Vec::is_empty")]
183 pub(crate) custommessages: Vec<u16>,
184}
185
186#[derive(Serialize, Default, Debug, Clone)]
187pub(crate) struct FeatureBits {
188 #[serde(skip_serializing_if = "Option::is_none")]
189 pub node: Option<String>,
190 #[serde(skip_serializing_if = "Option::is_none")]
191 pub channel: Option<String>,
192 #[serde(skip_serializing_if = "Option::is_none")]
193 pub init: Option<String>,
194 #[serde(skip_serializing_if = "Option::is_none")]
195 pub invoice: Option<String>,
196}
197
198#[derive(Serialize, Default, Debug)]
199pub struct InitResponse {
200 #[serde(skip_serializing_if = "Option::is_none")]
201 pub disable: Option<String>,
202}
203
204pub trait Response: Serialize + Debug {}
205
206#[cfg(test)]
207mod test {
208 use super::*;
209 use crate::messages;
210 use serde_json::json;
211
212 #[test]
213 fn test_init_message_parsing() {
214 let value = json!({
215 "jsonrpc": "2.0",
216 "method": "init",
217 "params": {
218 "options": {
219 "greeting": "World",
220 "number": [0]
221 },
222 "configuration": {
223 "lightning-dir": "/home/user/.lightning/testnet",
224 "rpc-file": "lightning-rpc",
225 "startup": true,
226 "network": "testnet",
227 "feature_set": {
228 "init": "02aaa2",
229 "node": "8000000002aaa2",
230 "channel": "",
231 "invoice": "028200"
232 },
233 "proxy": {
234 "type": "ipv4",
235 "address": "127.0.0.1",
236 "port": 9050
237 },
238 "torv3-enabled": true,
239 "always_use_proxy": false
240 }
241 },
242 "id": "10",
243 });
244 let req: JsonRpc<Notification, Request> = serde_json::from_value(value).unwrap();
245 match req {
246 messages::JsonRpc::Request(_, messages::Request::Init(init)) => {
247 assert_eq!(init.options["greeting"], "World");
248 assert_eq!(
249 init.configuration.lightning_dir,
250 String::from("/home/user/.lightning/testnet")
251 );
252 }
253 _ => panic!("Couldn't parse init message"),
254 }
255 }
256}