c-lightning-pruning-plugin 0.1.1

A plugin for c-lightning that manages pruning of bitcoind such that it can always sync.
use std::borrow::Cow;

use serde_json::Value;

fn deserialize_some<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where
    T: serde::Deserialize<'de>,
    D: serde::Deserializer<'de>,
{
    serde::Deserialize::deserialize(deserializer).map(Some)
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[serde(untagged)]
pub enum JsonRpcV2Id {
    Num(serde_json::Number),
    Str(String),
    Null,
}

#[derive(Clone, Debug)]
pub struct JsonRpcV2;
impl Default for JsonRpcV2 {
    fn default() -> Self {
        JsonRpcV2
    }
}
impl serde::Serialize for JsonRpcV2 {
    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        serializer.serialize_str("2.0")
    }
}
impl<'de> serde::Deserialize<'de> for JsonRpcV2 {
    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        let version: String = serde::Deserialize::deserialize(deserializer)?;
        match version.as_str() {
            "2.0" => (),
            a => {
                return Err(serde::de::Error::custom(format!(
                    "invalid RPC version: {}",
                    a
                )))
            }
        }
        Ok(JsonRpcV2)
    }
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[serde(untagged)]
pub enum RpcParams {
    ByPosition(Vec<Value>),
    ByName(serde_json::Map<String, Value>),
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct RpcReq {
    #[serde(default, deserialize_with = "deserialize_some")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id: Option<JsonRpcV2Id>,
    #[serde(default)]
    pub jsonrpc: JsonRpcV2,
    // a 🐄 is being used for a union type between
    // an owned and borrowed string this is necessary
    // to use a &'static str for serialization and an
    // owned String for deserialization
    pub method: Cow<'static, str>,
    pub params: RpcParams,
}
impl AsRef<RpcReq> for RpcReq {
    fn as_ref(&self) -> &RpcReq {
        &self
    }
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct RpcRes {
    pub id: JsonRpcV2Id,
    pub jsonrpc: JsonRpcV2,
    #[serde(flatten)]
    pub result: RpcResult,
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "lowercase")]
pub enum RpcResult {
    Result(Value),
    Error(RpcError),
}
impl RpcResult {
    pub fn res(self) -> Result<Value, RpcError> {
        self.into()
    }
}
impl From<RpcResult> for Result<Value, RpcError> {
    fn from(r: RpcResult) -> Self {
        match r {
            RpcResult::Result(a) => Ok(a),
            RpcResult::Error(e) => Err(e),
        }
    }
}
impl From<Result<Value, RpcError>> for RpcResult {
    fn from(r: Result<Value, RpcError>) -> Self {
        match r {
            Ok(a) => RpcResult::Result(a),
            Err(e) => RpcResult::Error(e),
        }
    }
}

pub trait IntoRpcResult<T> {
    fn with_info<N: Into<serde_json::Number>>(
        self,
        code: N,
        msg: &'static str,
    ) -> Result<T, RpcError>;
}
impl<T, E> IntoRpcResult<T> for Result<T, E>
where
    E: serde::Serialize,
{
    fn with_info<N: Into<serde_json::Number>>(
        self,
        code: N,
        msg: &'static str,
    ) -> Result<T, RpcError> {
        match self {
            Ok(a) => Ok(a),
            Err(e) => Err(RpcError {
                code: code.into(),
                message: Cow::Borrowed(msg),
                data: Some(
                    serde_json::to_value(e)
                        .map_err(|e| format!("{}", e))
                        .with_info(2, "serialization error")?,
                ),
            }),
        }
    }
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct RpcError {
    pub code: serde_json::Number,
    // a 🐄 is being used for a union type between
    // an owned and borrowed string this is necessary
    // to use a &'static str for serialization and an
    // owned String for deserialization
    pub message: Cow<'static, str>,
    #[serde(
        default,
        deserialize_with = "deserialize_some",
        skip_serializing_if = "Option::is_none"
    )]
    pub data: Option<Value>,
}
impl std::fmt::Display for RpcError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}: {}", self.code, self.message)?;
        if let Some(data) = &self.data {
            write!(f, ": {}", data)?;
        }
        Ok(())
    }
}
impl std::error::Error for RpcError {}

pub async fn make_socket_req(
    socket: &mut tokio::net::UnixStream,
    req: RpcReq,
) -> Result<RpcRes, failure::Error> {
    use tokio::io::AsyncWriteExt;
    use tokio::stream::StreamExt;

    socket.write_all(&serde_json::to_vec(&req)?).await?;
    let res = crate::async_io::RpcResponseStream::new(socket)
        .next()
        .await
        .ok_or_else(|| {
            tokio::io::Error::new(tokio::io::ErrorKind::UnexpectedEof, "socket closed")
        })??;
    let res = serde_json::from_slice(&res)?;

    Ok(res)
}