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