clightningrpc_plugin/
plugin.rs

1//! Core of the plugin API
2//!
3//! Unofficial API interface to develop plugin in Rust.
4use crate::commands::builtin::{InitRPC, ManifestRPC};
5use crate::commands::types::{CLNConf, RPCHookInfo, RPCMethodInfo};
6use crate::commands::RPCCommand;
7use crate::errors::PluginError;
8use crate::types::{LogLevel, RpcOption};
9use clightningrpc_common::json_utils::{add_str, init_payload, init_success_response};
10use clightningrpc_common::types::Request;
11use serde_json::Value;
12use std::collections::{HashMap, HashSet};
13use std::string::String;
14use std::sync::Arc;
15use std::{io, io::Write};
16
17#[cfg(feature = "log")]
18pub use log::*;
19
20#[derive(Clone)]
21#[allow(dead_code)]
22pub struct Plugin<T>
23where
24    // FIXME: move the static life time to a local life time for plugin
25    T: 'static + Clone,
26{
27    pub state: T,
28    /// all the option contained inside the
29    /// hash map.
30    pub option: HashMap<String, RpcOption>,
31    /// all the options rpc method that the
32    /// plugin need to support, included the builtin rpc method.
33    pub rpc_method: HashMap<String, Box<dyn RPCCommand<T>>>,
34    /// keep the info of the method in a separate list
35    /// FIXME: move the RPCMethodInfo as key of the rpc_method map.
36    pub rpc_info: HashSet<RPCMethodInfo>,
37    /// all the hook where the plugin is register during the configuration
38    pub rpc_hook: HashMap<String, Box<dyn RPCCommand<T>>>,
39    /// keep all the info about the hooks in a separate set.
40    /// FIXME: put the RPCHookInfo as key of the hash map.
41    pub hook_info: HashSet<RPCHookInfo>,
42    /// all the notification that the plugin is register on
43    pub rpc_notification: HashMap<String, Box<dyn RPCCommand<T>>>,
44    /// mark a plugin as dynamic, in this way the plugin can be run
45    /// from core lightning without stop the lightningd daemon
46    pub dynamic: bool,
47    /// core lightning configuration sent with the init call.
48    pub configuration: Option<CLNConf>,
49    /// onInit callback called when the method on init is ran.
50    on_init: Option<Arc<dyn Fn(&mut Plugin<T>) -> Value>>,
51}
52
53#[cfg(feature = "log")]
54pub struct Log;
55
56#[cfg(feature = "log")]
57impl log::Log for Log {
58    fn enabled(&self, _: &Metadata) -> bool {
59        true
60    }
61
62    fn log(&self, record: &Record) {
63        if self.enabled(record.metadata()) {
64            let level: LogLevel = record.level().into();
65            let msg = record.args();
66
67            let mut writer = io::stdout();
68            let mut payload = init_payload();
69            add_str(&mut payload, "level", &level.to_string());
70            add_str(&mut payload, "message", &format!("{msg}"));
71            let request = Request {
72                id: None,
73                jsonrpc: "2.0".to_owned(),
74                method: "log".to_owned(),
75                params: payload,
76            };
77            writer
78                .write_all(serde_json::to_string(&request).unwrap().as_bytes())
79                .unwrap();
80            writer.flush().unwrap();
81        }
82    }
83
84    fn flush(&self) {}
85}
86
87impl<'a, T: 'a + Clone> Plugin<T> {
88    pub fn new(state: T, dynamic: bool) -> Self {
89        Plugin {
90            state,
91            option: HashMap::new(),
92            rpc_method: HashMap::new(),
93            rpc_info: HashSet::new(),
94            rpc_hook: HashMap::new(),
95            hook_info: HashSet::new(),
96            rpc_notification: HashMap::new(),
97            dynamic,
98            configuration: None,
99            on_init: None,
100        }
101    }
102
103    pub fn on_init<C: 'static>(&'a mut self, callback: C) -> Self
104    where
105        C: Fn(&mut Plugin<T>) -> Value,
106    {
107        self.on_init = Some(Arc::new(callback));
108        self.clone()
109    }
110
111    pub fn log(&self, level: LogLevel, msg: &str) {
112        let mut writer = io::stdout();
113        let mut payload = init_payload();
114        add_str(&mut payload, "level", &level.to_string());
115        add_str(&mut payload, "message", msg);
116        let request = Request {
117            id: None,
118            jsonrpc: "2.0".to_owned(),
119            method: "log".to_owned(),
120            params: payload,
121        };
122        writer
123            .write_all(serde_json::to_string(&request).unwrap().as_bytes())
124            .unwrap();
125        writer.flush().unwrap();
126    }
127
128    /// register the plugin option.
129    pub fn add_opt(
130        &mut self,
131        name: &str,
132        opt_type: &str,
133        def_val: Option<String>,
134        description: &str,
135        deprecated: bool,
136    ) -> &mut Self {
137        self.option.insert(
138            name.to_owned(),
139            RpcOption {
140                name: name.to_string(),
141                opt_typ: opt_type.to_string(),
142                default: def_val,
143                description: description.to_string(),
144                deprecated,
145                value: None,
146            },
147        );
148        self
149    }
150
151    /// get an optionue that cln sent back to the plugin.
152    pub fn get_opt<R: for<'de> serde::de::Deserialize<'de>>(
153        &self,
154        name: &str,
155    ) -> Result<R, PluginError> {
156        let opt = self.option.get(name).unwrap();
157        Ok(opt.value())
158    }
159
160    // FIXME: adding the long description as parameter
161    pub fn add_rpc_method<F: 'static>(
162        &'a mut self,
163        name: &str,
164        usage: &str,
165        description: &str,
166        callback: F,
167    ) -> Self
168    where
169        F: RPCCommand<T> + 'static,
170    {
171        self.rpc_method.insert(name.to_owned(), Box::new(callback));
172        self.rpc_info.insert(RPCMethodInfo {
173            name: name.to_string(),
174            usage: usage.to_string(),
175            description: description.to_string(),
176            long_description: description.to_string(),
177            deprecated: false,
178        });
179        self.clone()
180    }
181
182    fn call_rpc_method(
183        &'a mut self,
184        name: &str,
185        params: serde_json::Value,
186    ) -> Result<serde_json::Value, PluginError> {
187        let command = self.rpc_method.get(name).unwrap().clone();
188        command.call(self, params)
189    }
190
191    fn handle_notification(&'a mut self, name: &str, params: serde_json::Value) {
192        let notification = self.rpc_notification.get(name).unwrap().clone();
193        if let Err(json_res) = notification.call(self, params) {
194            self.log(
195                LogLevel::Debug,
196                format!("Notification end with and error: {json_res}").as_str(),
197            );
198        }
199    }
200
201    pub fn register_hook<F: 'static>(
202        &'a mut self,
203        hook_name: &str,
204        before: Option<Vec<String>>,
205        after: Option<Vec<String>>,
206        callback: F,
207    ) -> Self
208    where
209        F: RPCCommand<T> + 'static,
210    {
211        self.rpc_hook
212            .insert(hook_name.to_owned(), Box::new(callback));
213        self.hook_info.insert(RPCHookInfo {
214            name: hook_name.to_owned(),
215            before,
216            after,
217        });
218        self.clone()
219    }
220
221    pub fn register_notification<F: 'static>(&mut self, name: &str, callback: F) -> Self
222    where
223        F: 'static + RPCCommand<T> + Clone,
224    {
225        self.rpc_notification
226            .insert(name.to_owned(), Box::new(callback));
227        self.clone()
228    }
229
230    fn write_respose(
231        &mut self,
232        result: &Result<serde_json::Value, PluginError>,
233        response: &mut serde_json::Value,
234    ) {
235        match result {
236            Ok(json_resp) => response["result"] = json_resp.to_owned(),
237            Err(json_err) => {
238                let err_resp = serde_json::to_value(json_err).unwrap();
239                response["error"] = err_resp;
240            }
241        }
242    }
243
244    pub fn start(mut self) {
245        let reader = io::stdin();
246        let mut writer = io::stdout();
247        let mut buffer = String::new();
248        #[cfg(feature = "log")]
249        {
250            let _ = log::set_logger(&Log {}).map(|()| log::set_max_level(LevelFilter::Trace));
251        }
252        self.rpc_method
253            .insert("getmanifest".to_owned(), Box::new(ManifestRPC {}));
254        self.rpc_method.insert(
255            "init".to_owned(),
256            Box::new(InitRPC::<T> {
257                on_init: self.on_init.clone(),
258            }),
259        );
260        // FIXME: core lightning end with the double endline, so this can cause
261        // problem for some input reader.
262        // we need to parse the writer, and avoid this while loop
263        loop {
264            let _ = reader.read_line(&mut buffer);
265            let req_str = buffer.to_string();
266            if req_str.trim().is_empty() {
267                continue;
268            }
269            buffer.clear();
270            let request: Request<serde_json::Value> = serde_json::from_str(&req_str).unwrap();
271            if let Some(id) = request.id {
272                // when the id is specified this is a RPC or Hook, so we need to return a response
273                let response = self.call_rpc_method(&request.method, request.params);
274                let mut rpc_response = init_success_response(id);
275                self.write_respose(&response, &mut rpc_response);
276                writer
277                    .write_all(serde_json::to_string(&rpc_response).unwrap().as_bytes())
278                    .unwrap();
279                writer.flush().unwrap();
280            } else {
281                // in case of the id is None, we are receiving the notification, so the server is not
282                // interested in the answer.
283                self.handle_notification(&request.method, request.params);
284            }
285        }
286    }
287}