Skip to content

Commit ac39d31

Browse files
committed
cln-plugin: include the full error chain when given a context
Changelog-None
1 parent 066056d commit ac39d31

3 files changed

Lines changed: 44 additions & 8 deletions

File tree

plugins/examples/cln-plugin-startup.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
//! plugins using the Rust API against Core Lightning.
33
#[macro_use]
44
extern crate serde_json;
5+
use anyhow::Context;
56
use cln_plugin::options::{
67
self, BooleanConfigOption, DefaultIntegerArrayConfigOption, DefaultIntegerConfigOption,
78
DefaultStringArrayConfigOption, IntegerArrayConfigOption, IntegerConfigOption,
89
StringArrayConfigOption,
910
};
1011
use cln_plugin::{Builder, Error, HookBuilder, Plugin, messages};
12+
use cln_rpc::ClnRpc;
1113

1214
const TEST_NOTIF_TAG: &str = "test_custom_notification";
1315

@@ -77,6 +79,7 @@ async fn main() -> Result<(), anyhow::Error> {
7779
test_send_custom_notification,
7880
)
7981
.rpcmethod("test-log-levels", "send on all log levels", test_log_levels)
82+
.rpcmethod("test-error", "test error with context", test_error)
8083
.subscribe("connect", connect_handler)
8184
.subscribe("test_custom_notification", test_receive_custom_notification)
8285
.hook_from_builder(
@@ -179,3 +182,12 @@ async fn test_log_levels(
179182
log::error!("log::error! working");
180183
Ok(json!({}))
181184
}
185+
186+
async fn test_error(p: Plugin<()>, _v: serde_json::Value) -> Result<serde_json::Value, Error> {
187+
let mut rpc = ClnRpc::new(p.configuration().rpc_file).await?;
188+
let gi: serde_json::Value = rpc
189+
.call_raw("00000000000", &json!({}))
190+
.await
191+
.context("context given")?;
192+
Ok(gi)
193+
}

plugins/src/lib.rs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use crate::codec::{JsonCodec, JsonRpcCodec};
22
pub use anyhow::anyhow;
33
use anyhow::{Context, Result};
44
use futures::sink::SinkExt;
5-
use serde::de::DeserializeOwned;
65
use serde::Serialize;
6+
use serde::de::DeserializeOwned;
77
use tokio::io::{AsyncReadExt, AsyncWriteExt};
88
extern crate log;
99
use log::trace;
@@ -973,7 +973,7 @@ where
973973
.send(json!({
974974
"jsonrpc": "2.0",
975975
"id": id,
976-
"error": parse_error(e.to_string()),
976+
"error": parse_error(&e),
977977
}))
978978
.await
979979
.context("returning custom error"),
@@ -1112,14 +1112,20 @@ struct RpcError {
11121112
pub message: String,
11131113
pub data: Option<serde_json::Value>,
11141114
}
1115-
fn parse_error(error: String) -> RpcError {
1116-
match serde_json::from_str::<RpcError>(&error) {
1117-
Ok(o) => o,
1118-
Err(_) => RpcError {
1115+
fn parse_error(error: &anyhow::Error) -> RpcError {
1116+
if let Ok(o) = serde_json::from_str::<RpcError>(&error.to_string()) {
1117+
o
1118+
} else {
1119+
let message = error
1120+
.chain()
1121+
.map(std::string::ToString::to_string)
1122+
.collect::<Vec<_>>()
1123+
.join(": ");
1124+
RpcError {
11191125
code: Some(-32700),
1120-
message: error,
1126+
message,
11211127
data: None,
1122-
},
1128+
}
11231129
}
11241130
}
11251131

@@ -1133,4 +1139,19 @@ mod test {
11331139
let builder = Builder::new(tokio::io::stdin(), tokio::io::stdout());
11341140
let _ = builder.start(state);
11351141
}
1142+
#[test]
1143+
fn parse_error_includes_full_anyhow_chain() {
1144+
let err = anyhow!("No such file or directory (os error 2)")
1145+
.context("config file missing")
1146+
.context("failed to load config");
1147+
1148+
let rpc_error = parse_error(&err);
1149+
1150+
assert_eq!(rpc_error.code, Some(-32700));
1151+
assert_eq!(
1152+
rpc_error.message,
1153+
"failed to load config: config file missing: No such file or directory (os error 2)"
1154+
);
1155+
assert_eq!(rpc_error.data, None);
1156+
}
11361157
}

tests/test_cln_rs.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ def test_plugin_start(node_factory):
7878
assert not l1.rpc.listconfigs("test-dynamic-option")["configs"]["test-dynamic-option"]["value_bool"]
7979
wait_for(lambda: l1.daemon.is_in_log(r'cln-plugin-startup: Got dynamic option change: test-dynamic-option "false"'))
8080

81+
with pytest.raises(RpcError, match="context given: .* Unknown command"):
82+
l1.rpc.test_error()
83+
8184

8285
def test_plugin_options_handle_defaults(node_factory):
8386
"""Start a minimal plugin and ensure it is well-behaved

0 commit comments

Comments
 (0)