Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions plugins/examples/cln-plugin-startup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
//! plugins using the Rust API against Core Lightning.
#[macro_use]
extern crate serde_json;
use anyhow::Context;
use cln_plugin::options::{
self, BooleanConfigOption, DefaultIntegerArrayConfigOption, DefaultIntegerConfigOption,
DefaultStringArrayConfigOption, IntegerArrayConfigOption, IntegerConfigOption,
StringArrayConfigOption,
};
use cln_plugin::{Builder, Error, HookBuilder, Plugin, messages};
use cln_rpc::ClnRpc;

const TEST_NOTIF_TAG: &str = "test_custom_notification";

Expand Down Expand Up @@ -77,6 +79,7 @@ async fn main() -> Result<(), anyhow::Error> {
test_send_custom_notification,
)
.rpcmethod("test-log-levels", "send on all log levels", test_log_levels)
.rpcmethod("test-error", "test error with context", test_error)
.subscribe("connect", connect_handler)
.subscribe("test_custom_notification", test_receive_custom_notification)
.hook_from_builder(
Expand Down Expand Up @@ -179,3 +182,12 @@ async fn test_log_levels(
log::error!("log::error! working");
Ok(json!({}))
}

async fn test_error(p: Plugin<()>, _v: serde_json::Value) -> Result<serde_json::Value, Error> {
let mut rpc = ClnRpc::new(p.configuration().rpc_file).await?;
let gi: serde_json::Value = rpc
.call_raw("00000000000", &json!({}))
.await
.context("context given")?;
Ok(gi)
}
6 changes: 3 additions & 3 deletions plugins/lsps-plugin/src/core/lsps2/htlc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ impl RejectReason {

#[derive(Debug, Error)]
pub enum HtlcError {
#[error("failed to query channel capacity: {0}")]
#[error("failed to query channel capacity: {0:#}")]
CapacityQuery(#[source] anyhow::Error),
#[error("failed to fund channel: {0}")]
#[error("failed to fund channel: {0:#}")]
FundChannel(#[source] anyhow::Error),
#[error("channel ready check failed: {0}")]
#[error("channel ready check failed: {0:#}")]
ChannelReadyCheck(#[source] anyhow::Error),
}

Expand Down
37 changes: 29 additions & 8 deletions plugins/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use crate::codec::{JsonCodec, JsonRpcCodec};
pub use anyhow::anyhow;
use anyhow::{Context, Result};
use futures::sink::SinkExt;
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde::de::DeserializeOwned;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
extern crate log;
use log::trace;
Expand Down Expand Up @@ -973,7 +973,7 @@ where
.send(json!({
"jsonrpc": "2.0",
"id": id,
"error": parse_error(e.to_string()),
"error": parse_error(&e),
}))
.await
.context("returning custom error"),
Expand Down Expand Up @@ -1112,14 +1112,20 @@ struct RpcError {
pub message: String,
pub data: Option<serde_json::Value>,
}
fn parse_error(error: String) -> RpcError {
match serde_json::from_str::<RpcError>(&error) {
Ok(o) => o,
Err(_) => RpcError {
fn parse_error(error: &anyhow::Error) -> RpcError {
if let Ok(o) = serde_json::from_str::<RpcError>(&error.to_string()) {
o
} else {
let message = error
.chain()
.map(std::string::ToString::to_string)
.collect::<Vec<_>>()
.join(": ");
RpcError {
code: Some(-32700),
message: error,
message,
data: None,
},
}
}
}

Expand All @@ -1133,4 +1139,19 @@ mod test {
let builder = Builder::new(tokio::io::stdin(), tokio::io::stdout());
let _ = builder.start(state);
}
#[test]
fn parse_error_includes_full_anyhow_chain() {
let err = anyhow!("No such file or directory (os error 2)")
.context("config file missing")
.context("failed to load config");

let rpc_error = parse_error(&err);

assert_eq!(rpc_error.code, Some(-32700));
assert_eq!(
rpc_error.message,
"failed to load config: config file missing: No such file or directory (os error 2)"
);
assert_eq!(rpc_error.data, None);
}
}
3 changes: 3 additions & 0 deletions tests/test_cln_rs.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ def test_plugin_start(node_factory):
assert not l1.rpc.listconfigs("test-dynamic-option")["configs"]["test-dynamic-option"]["value_bool"]
wait_for(lambda: l1.daemon.is_in_log(r'cln-plugin-startup: Got dynamic option change: test-dynamic-option "false"'))

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


def test_plugin_options_handle_defaults(node_factory):
"""Start a minimal plugin and ensure it is well-behaved
Expand Down
Loading