mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-02-21 14:24:09 +01:00
cln-plugin: Add options to the getmanifest
call
This commit is contained in:
parent
fe21b89b56
commit
249fa8675a
5 changed files with 183 additions and 18 deletions
|
@ -1,12 +1,19 @@
|
|||
//! This is a test plugin used to verify that we can compile and run
|
||||
//! plugins using the Rust API against c-lightning.
|
||||
|
||||
use cln_plugin::Builder;
|
||||
use cln_plugin::{options, Builder};
|
||||
use tokio;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), anyhow::Error> {
|
||||
let (plugin, stdin) = Builder::new((), tokio::io::stdin(), tokio::io::stdout()).build();
|
||||
let (plugin, stdin) = Builder::new((), tokio::io::stdin(), tokio::io::stdout())
|
||||
.option(options::ConfigOption::new(
|
||||
"test-option",
|
||||
options::Value::Integer(42),
|
||||
"a test-option with default 42",
|
||||
))
|
||||
.build();
|
||||
|
||||
tokio::spawn(async {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await;
|
||||
log::info!("Hello world");
|
||||
|
|
|
@ -18,6 +18,10 @@ mod messages;
|
|||
#[macro_use]
|
||||
extern crate serde_json;
|
||||
|
||||
pub mod options;
|
||||
|
||||
use options::ConfigOption;
|
||||
|
||||
/// Builder for a new plugin.
|
||||
pub struct Builder<S, I, O>
|
||||
where
|
||||
|
@ -35,6 +39,8 @@ where
|
|||
|
||||
#[allow(dead_code)]
|
||||
subscriptions: Subscriptions,
|
||||
|
||||
options: Vec<ConfigOption>,
|
||||
}
|
||||
|
||||
impl<S, I, O> Builder<S, I, O>
|
||||
|
@ -50,9 +56,15 @@ where
|
|||
output,
|
||||
hooks: Hooks::default(),
|
||||
subscriptions: Subscriptions::default(),
|
||||
options: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn option(mut self, opt: options::ConfigOption) -> Builder<S, I, O> {
|
||||
self.options.push(opt);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> (Plugin<S, I, O>, I) {
|
||||
let output = Arc::new(Mutex::new(FramedWrite::new(
|
||||
self.output,
|
||||
|
@ -67,6 +79,7 @@ where
|
|||
state: Arc::new(Mutex::new(self.state)),
|
||||
output,
|
||||
input_type: PhantomData,
|
||||
options: self.options,
|
||||
},
|
||||
self.input,
|
||||
)
|
||||
|
@ -85,7 +98,20 @@ where
|
|||
/// The state gets cloned for each request
|
||||
state: Arc<Mutex<S>>,
|
||||
input_type: PhantomData<I>,
|
||||
options: Vec<ConfigOption>,
|
||||
}
|
||||
|
||||
impl<S, I, O> Plugin<S, I, O>
|
||||
where
|
||||
S: Clone + Send,
|
||||
I: AsyncRead + Send + Unpin,
|
||||
O: Send + AsyncWrite + Unpin,
|
||||
{
|
||||
pub fn options(&self) -> Vec<ConfigOption> {
|
||||
self.options.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, I, O> Plugin<S, I, O>
|
||||
where
|
||||
S: Clone + Send,
|
||||
|
@ -130,8 +156,7 @@ where
|
|||
let state = self.state.clone();
|
||||
let res: serde_json::Value = match request {
|
||||
messages::Request::Getmanifest(c) => {
|
||||
serde_json::to_value(Plugin::<S, I, O>::handle_get_manifest(c, state).await?)
|
||||
.unwrap()
|
||||
serde_json::to_value(self.handle_get_manifest(c, state).await?).unwrap()
|
||||
}
|
||||
messages::Request::Init(c) => {
|
||||
serde_json::to_value(Plugin::<S, I, O>::handle_init(c, state).await?).unwrap()
|
||||
|
@ -160,10 +185,14 @@ where
|
|||
}
|
||||
|
||||
async fn handle_get_manifest(
|
||||
&mut self,
|
||||
_call: messages::GetManifestCall,
|
||||
_state: Arc<Mutex<S>>,
|
||||
) -> Result<messages::GetManifestResponse, Error> {
|
||||
Ok(messages::GetManifestResponse::default())
|
||||
Ok(messages::GetManifestResponse {
|
||||
options: self.options.clone(),
|
||||
rpcmethods: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_init(
|
||||
|
@ -192,6 +221,6 @@ mod test {
|
|||
#[test]
|
||||
fn init() {
|
||||
let builder = Builder::new((), tokio::io::stdin(), tokio::io::stdout());
|
||||
let plugin = builder.build();
|
||||
builder.build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::options::ConfigOption;
|
||||
use serde::de::{self, Deserializer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
@ -149,9 +150,9 @@ where
|
|||
}
|
||||
|
||||
#[derive(Serialize, Default, Debug)]
|
||||
pub struct GetManifestResponse {
|
||||
options: Vec<()>,
|
||||
rpcmethods: Vec<()>,
|
||||
pub(crate) struct GetManifestResponse {
|
||||
pub(crate) options: Vec<ConfigOption>,
|
||||
pub(crate) rpcmethods: Vec<()>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Default, Debug)]
|
||||
|
|
122
plugins/src/options.rs
Normal file
122
plugins/src/options.rs
Normal file
|
@ -0,0 +1,122 @@
|
|||
use serde::ser::{SerializeStruct, Serializer};
|
||||
use serde::{Serialize};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Value {
|
||||
String(String),
|
||||
Integer(i64),
|
||||
Boolean(bool),
|
||||
}
|
||||
|
||||
/// An stringly typed option that is passed to
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ConfigOption {
|
||||
name: String,
|
||||
pub(crate) value: Option<Value>,
|
||||
default: Value,
|
||||
description: String,
|
||||
}
|
||||
|
||||
impl ConfigOption {
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
pub fn default(&self) -> &Value {
|
||||
&self.default
|
||||
}
|
||||
}
|
||||
|
||||
// When we serialize we don't add the value. This is because we only
|
||||
// ever serialize when we pass the option back to lightningd during
|
||||
// the getmanifest call.
|
||||
impl Serialize for ConfigOption {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut s = serializer.serialize_struct("ConfigOption", 4)?;
|
||||
s.serialize_field("name", &self.name)?;
|
||||
match &self.default {
|
||||
Value::String(ss) => {
|
||||
s.serialize_field("type", "string")?;
|
||||
s.serialize_field("default", ss)?;
|
||||
}
|
||||
Value::Integer(i) => {
|
||||
s.serialize_field("type", "int")?;
|
||||
s.serialize_field("default", i)?;
|
||||
}
|
||||
|
||||
Value::Boolean(b) => {
|
||||
s.serialize_field("type", "bool")?;
|
||||
s.serialize_field("default", b)?;
|
||||
}
|
||||
}
|
||||
|
||||
s.serialize_field("description", &self.description)?;
|
||||
s.end()
|
||||
}
|
||||
}
|
||||
impl ConfigOption {
|
||||
pub fn new(name: &str, default: Value, description: &str) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
default,
|
||||
description: description.to_string(),
|
||||
value: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn value(&self) -> Value {
|
||||
match &self.value {
|
||||
None => self.default.clone(),
|
||||
Some(v) => v.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn description(&self) -> String {
|
||||
self.description.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_option_serialize() {
|
||||
let tests = vec![
|
||||
(
|
||||
ConfigOption::new("name", Value::String("default".to_string()), "description"),
|
||||
json!({
|
||||
"name": "name",
|
||||
"description":"description",
|
||||
"default": "default",
|
||||
"type": "string",
|
||||
}),
|
||||
),
|
||||
(
|
||||
ConfigOption::new("name", Value::Integer(42), "description"),
|
||||
json!({
|
||||
"name": "name",
|
||||
"description":"description",
|
||||
"default": 42,
|
||||
"type": "int",
|
||||
}),
|
||||
),
|
||||
(
|
||||
ConfigOption::new("name", Value::Boolean(true), "description"
|
||||
json!({
|
||||
"name": "name",
|
||||
"description":"description",
|
||||
"default": true,
|
||||
"type": "booltes",
|
||||
}),
|
||||
)),
|
||||
];
|
||||
|
||||
for (input, expected) in tests.iter() {
|
||||
let res = serde_json::to_value(input).unwrap();
|
||||
assert_eq!(&res, expected);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,14 +21,20 @@ def test_rpc_client(node_factory):
|
|||
|
||||
|
||||
def test_plugin_start(node_factory):
|
||||
"""Start a minimal plugin and ensure it is well-behaved
|
||||
"""
|
||||
bin_path = Path.cwd() / "target" / "debug" / "examples" / "cln-plugin-startup"
|
||||
l1 = node_factory.get_node(options={"plugin": str(bin_path)})
|
||||
"""Start a minimal plugin and ensure it is well-behaved
|
||||
"""
|
||||
bin_path = Path.cwd() / "target" / "debug" / "examples" / "cln-plugin-startup"
|
||||
l1 = node_factory.get_node(options={"plugin": str(bin_path)})
|
||||
|
||||
# The plugin should be in the list of active plugins
|
||||
plugins = l1.rpc.plugin('list')['plugins']
|
||||
assert len([p for p in plugins if 'cln-plugin-startup' in p['name'] and p['active']]) == 1
|
||||
cfg = l1.rpc.listconfigs()
|
||||
p = cfg['plugins'][0]
|
||||
p['path'] = None # The path is host-specific, so blank it.
|
||||
expected = {
|
||||
'name': 'cln-plugin-startup',
|
||||
'options': {
|
||||
'test-option': 42
|
||||
},
|
||||
'path': None
|
||||
}
|
||||
assert expected == p
|
||||
|
||||
# Logging should also work through the log integration
|
||||
l1.daemon.wait_for_log(r'Hello world')
|
||||
|
|
Loading…
Add table
Reference in a new issue