ckb-dev 0.1.1

A simple tool for CKB DevOps.
Documentation
use std::{
    sync::atomic::{AtomicU64, Ordering},
    time::Duration,
};

use ckb_jsonrpc_types as rpc;
use jsonrpc_core::response::Output;
use reqwest::{blocking::Client as InnerClient, Url};

use crate::error::{Error, Result};

#[derive(Debug)]
struct IdGenerator {
    state: AtomicU64,
}

impl Default for IdGenerator {
    fn default() -> Self {
        IdGenerator {
            state: AtomicU64::new(1),
        }
    }
}

impl IdGenerator {
    fn new() -> IdGenerator {
        IdGenerator::default()
    }

    fn next(&self) -> u64 {
        self.state.fetch_add(1, Ordering::SeqCst)
    }
}

macro_rules! jsonrpc {
    (
        $(#[$struct_attr:meta])*
        trait $struct_name:ident {$(
            $(#[$attr:meta])*
            fn $method:ident(&$self:ident $(, $arg_name:ident: $arg_ty:ty)*)
                -> $return_ty:ty;
        )*}
    ) => (
        $(#[$struct_attr])*
        struct $struct_name {
            client: InnerClient,
            url: Url,
            id_generator: IdGenerator,
        }

        impl $struct_name {
            fn new(url: &Url) -> Result<Self> {
                let url = url.clone();
                let client = InnerClient::builder()
                        .timeout(Duration::from_secs(30))
                        .build()
                        .map_err(|err| {
                            let msg = format!("failed to build rpc client since {}", err);
                            Error::Rpc(msg)
                        })?;
                let id_generator = IdGenerator::new();
                Ok(Self { url, client, id_generator })
            }

            $(
                $(#[$attr])*
                fn $method(&$self $(, $arg_name: $arg_ty)*) -> Result<$return_ty> {
                    let method = String::from(stringify!($method));
                    let params = serialize_parameters!($($arg_name,)*);
                    let id = $self.id_generator.next();

                    let mut req_json = serde_json::Map::new();
                    req_json.insert("id".to_owned(), serde_json::json!(id));
                    req_json.insert("jsonrpc".to_owned(), serde_json::json!("2.0"));
                    req_json.insert("method".to_owned(), serde_json::json!(method));
                    req_json.insert("params".to_owned(), params);

                    let output = $self
                        .client
                        .post($self.url.clone())
                        .json(&req_json)
                        .send()
                        .map_err(|err| {
                            let msg = format!("failed to send request since {}", err);
                            Error::Rpc(msg)
                        })?
                        .json::<Output>()
                        .map_err(|err| {
                            let msg = format!("failed to parse rpc output since {}", err);
                            Error::Rpc(msg)
                        })?;
                    match output {
                        Output::Success(success) => {
                            serde_json::from_value(success.result)
                                .map_err(|err| {
                                    let msg = format!("failed to parse rpc return since {}", err);
                                    Error::Rpc(msg)
                                })
                        },
                        Output::Failure(failure) => {
                            let msg = format!(
                                "failed to call \"{}\" since {}",
                                &method,
                                serde_json::to_string(&failure).expect("rpc failure to string"));
                            Err(Error::Rpc(msg))
                        }
                    }
                }
            )*
        }
    )
}

macro_rules! serialize_parameters {
    () => ( serde_json::Value::Null );
    ($($arg_name:ident,)+) => ( serde_json::to_value(($($arg_name,)+))?)
}

jsonrpc!(
    trait Client {
        fn get_peers(&self) -> Vec<rpc::RemoteNode>;
    }
);

pub(crate) struct RpcClient {
    inner: Client,
}

impl RpcClient {
    pub(crate) fn new(url: &Url) -> Result<Self> {
        let inner = Client::new(url)?;
        Ok(Self { inner })
    }

    pub(crate) fn get_peers(&self) -> Result<Vec<rpc::RemoteNode>> {
        self.inner.get_peers()
    }
}