diff --git a/Cargo.lock b/Cargo.lock index 1f87d148a..bf4678ef1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -553,6 +553,7 @@ dependencies = [ "cln-rpc", "hex", "log", + "paste", "rand 0.9.2", "serde", "serde_json", @@ -1763,6 +1764,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pem" version = "3.0.5" diff --git a/plugins/lsps-plugin/Cargo.toml b/plugins/lsps-plugin/Cargo.toml index 5ef5fa421..028aee20a 100644 --- a/plugins/lsps-plugin/Cargo.toml +++ b/plugins/lsps-plugin/Cargo.toml @@ -20,6 +20,7 @@ cln-plugin = { workspace = true } cln-rpc = { workspace = true } hex = "0.4" log = "0.4" +paste = "1.0.15" rand = "0.9" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/plugins/lsps-plugin/src/jsonrpc/server.rs b/plugins/lsps-plugin/src/jsonrpc/server.rs index a9ada7669..a012f6fad 100644 --- a/plugins/lsps-plugin/src/jsonrpc/server.rs +++ b/plugins/lsps-plugin/src/jsonrpc/server.rs @@ -1,4 +1,4 @@ -use crate::proto::jsonrpc::{Result, RpcError}; +use crate::proto::jsonrpc::{Result, RpcError, RpcErrorExt as _}; use async_trait::async_trait; use log::{debug, trace}; use std::{collections::HashMap, sync::Arc}; diff --git a/plugins/lsps-plugin/src/lsps2/handler.rs b/plugins/lsps-plugin/src/lsps2/handler.rs index 4177c695c..4624bbc3f 100644 --- a/plugins/lsps-plugin/src/lsps2/handler.rs +++ b/plugins/lsps-plugin/src/lsps2/handler.rs @@ -5,7 +5,7 @@ use crate::{ DS_MAIN_KEY, DS_SUB_KEY, }, proto::{ - jsonrpc::{JsonRpcResponse as _, RequestObject, RpcError}, + jsonrpc::{JsonRpcResponse, RequestObject, RpcError, RpcErrorExt as _}, lsps0::{Msat, ShortChannelId}, lsps2::{ compute_opening_fee, diff --git a/plugins/lsps-plugin/src/proto/jsonrpc.rs b/plugins/lsps-plugin/src/proto/jsonrpc.rs index 79f1d4ac5..afc211409 100644 --- a/plugins/lsps-plugin/src/proto/jsonrpc.rs +++ b/plugins/lsps-plugin/src/proto/jsonrpc.rs @@ -205,6 +205,38 @@ where } } +/// Macro to generate RpcError helper methods for protocol-specific error codes +/// +/// This generates two methods for each error code: +/// - `method_name(message)` - Creates error without data +/// - `method_name_with_data(message, data)` - Creates error with data +macro_rules! rpc_error_methods { + ($($method:ident => $code:expr),* $(,)?) => { + $( + paste::paste! { + fn $method(message: T) -> $crate::proto::jsonrpc::RpcError { + $crate::proto::jsonrpc::RpcError { + code: $code, + message: message.to_string(), + data: None, + } + } + + fn [<$method _with_data>]( + message: T, + data: serde_json::Value, + ) -> $crate::proto::jsonrpc::RpcError { + $crate::proto::jsonrpc::RpcError { + code: $code, + message: message.to_string(), + data: Some(data), + } + } + } + )* + }; +} + /// # RpcError /// /// Represents an error object in a JSON-RPC 2.0 Response object (section 5.1). @@ -255,75 +287,20 @@ impl RpcError { data: Some(data), } } +} - /// Invalid JSON was received by the server. - /// An error occurred on the server while parsing the JSON text. - pub fn parse_error(message: T) -> Self { - Self::custom_error(PARSE_ERROR, message) - } - - /// Invalid JSON was received by the server. - /// An error occurred on the server while parsing the JSON text. - pub fn parse_error_with_data( - message: T, - data: serde_json::Value, - ) -> Self { - Self::custom_error_with_data(PARSE_ERROR, message, data) - } - - /// The JSON sent is not a valid Request object. - pub fn invalid_request(message: T) -> Self { - Self::custom_error(INVALID_REQUEST, message) - } - - /// The JSON sent is not a valid Request object. - pub fn invalid_request_with_data( - message: T, - data: serde_json::Value, - ) -> Self { - Self::custom_error_with_data(INVALID_REQUEST, message, data) - } - - /// The method does not exist / is not available. - pub fn method_not_found(message: T) -> Self { - Self::custom_error(METHOD_NOT_FOUND, message) - } - - /// The method does not exist / is not available. - pub fn method_not_found_with_data( - message: T, - data: serde_json::Value, - ) -> Self { - Self::custom_error_with_data(METHOD_NOT_FOUND, message, data) - } - - /// Invalid method parameter(s). - pub fn invalid_params(message: T) -> Self { - Self::custom_error(INVALID_PARAMS, message) - } - - /// Invalid method parameter(s). - pub fn invalid_params_with_data( - message: T, - data: serde_json::Value, - ) -> Self { - Self::custom_error_with_data(INVALID_PARAMS, message, data) - } - - /// Internal JSON-RPC error. - pub fn internal_error(message: T) -> Self { - Self::custom_error(INTERNAL_ERROR, message) - } - - /// Internal JSON-RPC error. - pub fn internal_error_with_data( - message: T, - data: serde_json::Value, - ) -> Self { - Self::custom_error_with_data(INTERNAL_ERROR, message, data) +pub trait RpcErrorExt { + rpc_error_methods! { + parse_error => PARSE_ERROR, + internal_error => INTERNAL_ERROR, + invalid_params => INVALID_PARAMS, + method_not_found => METHOD_NOT_FOUND, + invalid_request => INVALID_REQUEST, } } +impl RpcErrorExt for RpcError {} + impl fmt::Display for RpcError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( diff --git a/plugins/lsps-plugin/src/proto/lsps0.rs b/plugins/lsps-plugin/src/proto/lsps0.rs index a315c5635..831a8c2e6 100644 --- a/plugins/lsps-plugin/src/proto/lsps0.rs +++ b/plugins/lsps-plugin/src/proto/lsps0.rs @@ -1,4 +1,4 @@ -use crate::proto::jsonrpc::JsonRpcRequest; +use crate::proto::jsonrpc::{JsonRpcRequest, RpcError}; use core::fmt; use serde::{ de::{self}, @@ -13,19 +13,20 @@ pub const LSP_FEATURE_BIT: usize = 729; // Required message type for BOLT8 transport. pub const LSPS0_MESSAGE_TYPE: u16 = 37913; -// Lsps0 error definitions. Are in the range 00000 to 00099. -pub const CLIENT_REJECTED: i64 = 1; - -pub enum Error { - ClientRejected(String), +// Lsps0 specific error codes defined in BLIP-50. +// Are in the range 00000 to 00099. +pub mod error_codes { + pub const CLIENT_REJECTED: i64 = 001; } -impl Error { - pub fn client_rejected(msg: String) -> Error { - Self::ClientRejected(msg) +pub trait LSPS0RpcErrorExt { + rpc_error_methods! { + client_rejected => error_codes::CLIENT_REJECTED, } } +impl LSPS0RpcErrorExt for RpcError {} + /// Represents a monetary amount as defined in LSPS0.msat. Is converted to a /// `String` in json messages with a suffix `_msat` or `_sat` and internally /// represented as Millisatoshi `u64`. diff --git a/plugins/lsps-plugin/src/proto/lsps2.rs b/plugins/lsps-plugin/src/proto/lsps2.rs index 194c1ff49..888e93015 100644 --- a/plugins/lsps-plugin/src/proto/lsps2.rs +++ b/plugins/lsps-plugin/src/proto/lsps2.rs @@ -1,6 +1,6 @@ -use crate::{ - proto::jsonrpc::{JsonRpcRequest, RpcError}, - proto::lsps0::{DateTime, Msat, Ppm, ShortChannelId}, +use crate::proto::{ + jsonrpc::{JsonRpcRequest, RpcError}, + lsps0::{DateTime, Msat, Ppm, ShortChannelId}, }; use bitcoin::hashes::{sha256, Hash, HashEngine, Hmac, HmacEngine}; use chrono::Utc; @@ -12,55 +12,54 @@ pub mod failure_codes { pub const UNKNOWN_NEXT_PEER: &'static str = "4010"; } +// Lsps2 specific error codes defined in BLIP-52. +// Are in the range 00200 to 00299. +pub mod error_codes { + pub const INVALID_OPENING_FEE_PARAMS: i64 = 201; + pub const PAYMENT_SIZE_TOO_SMALL: i64 = 202; + pub const PAYMENT_SIZE_TOO_LARGE: i64 = 203; +} + #[derive(Clone, Debug, PartialEq)] pub enum Error { InvalidOpeningFeeParams, PaymentSizeTooSmall, PaymentSizeTooLarge, - ClientRejected, } impl core::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let err_str = match self { - Error::InvalidOpeningFeeParams => "invalid opening fee params", - Error::PaymentSizeTooSmall => "payment size too small", - Error::PaymentSizeTooLarge => "payment size too large", - Error::ClientRejected => "client rejected", + Error::InvalidOpeningFeeParams => "Invalid opening fee params", + Error::PaymentSizeTooSmall => "Payment size too small", + Error::PaymentSizeTooLarge => "Payment size too large", }; write!(f, "{}", &err_str) } } impl From for RpcError { - fn from(value: Error) -> Self { - match value { - Error::InvalidOpeningFeeParams => RpcError { - code: 201, - message: "invalid opening fee params".to_string(), - data: None, - }, - Error::PaymentSizeTooSmall => RpcError { - code: 202, - message: "payment size too small".to_string(), - data: None, - }, - Error::PaymentSizeTooLarge => RpcError { - code: 203, - message: "payment size too large".to_string(), - data: None, - }, - Error::ClientRejected => RpcError { - code: 001, - message: "client rejected".to_string(), - data: None, - }, + fn from(error: Error) -> Self { + match error { + Error::InvalidOpeningFeeParams => RpcError::invalid_opening_fee_params(error), + Error::PaymentSizeTooSmall => RpcError::payment_size_too_small(error), + Error::PaymentSizeTooLarge => RpcError::payment_size_too_large(error), } } } impl core::error::Error for Error {} +pub trait LSPS2RpcErrorExt { + rpc_error_methods! { + invalid_opening_fee_params => error_codes::INVALID_OPENING_FEE_PARAMS, + payment_size_too_small => error_codes::PAYMENT_SIZE_TOO_SMALL, + payment_size_too_large => error_codes::PAYMENT_SIZE_TOO_LARGE + } +} + +impl LSPS2RpcErrorExt for RpcError {} + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Lsps2GetInfoRequest { #[serde(skip_serializing_if = "Option::is_none")] diff --git a/plugins/lsps-plugin/src/proto/mod.rs b/plugins/lsps-plugin/src/proto/mod.rs index e2bdc1610..cd9677939 100644 --- a/plugins/lsps-plugin/src/proto/mod.rs +++ b/plugins/lsps-plugin/src/proto/mod.rs @@ -1,3 +1,4 @@ +#[macro_use] pub mod jsonrpc; pub mod lsps0; pub mod lsps2;