use serde::{Deserialize, Serialize};
use crate::method::ControlMethod;
use crate::results;
use crate::traits::ControlCall;
macro_rules! control_call {
($ty:ty => $method:expr, $out:ty) => {
impl ControlCall for $ty {
const METHOD: ControlMethod = $method;
type Output = $out;
}
};
}
macro_rules! no_params {
($(#[$doc:meta])* $name:ident => $method:expr, $out:ty) => {
$(#[$doc])*
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct $name {}
control_call!($name => $method, $out);
};
}
no_params!(
StatusParams => ControlMethod::Status, results::StatusResult
);
no_params!(
ConfigGetParams => ControlMethod::ConfigGet, results::ConfigResult
);
no_params!(
CacheGetParams => ControlMethod::CacheGet, results::CacheView
);
no_params!(
CacheClearParams => ControlMethod::CacheClear, results::CacheClearResult
);
no_params!(
HostedStoresListParams => ControlMethod::HostedStoresList, results::HostedStoresListResult
);
no_params!(
SyncStatusParams => ControlMethod::SyncStatus, results::SyncStatusResult
);
no_params!(
UpdaterStatusParams => ControlMethod::UpdaterStatus, serde_json::Value
);
no_params!(
UpdaterResumeParams => ControlMethod::UpdaterResume, serde_json::Value
);
no_params!(
UpdaterCheckNowParams => ControlMethod::UpdaterCheckNow, serde_json::Value
);
no_params!(
PairingListParams => ControlMethod::PairingList, serde_json::Value
);
no_params!(
PeerStatusParams => ControlMethod::PeerStatus, serde_json::Value
);
no_params!(
ListSubscriptionsParams => ControlMethod::ListSubscriptions, results::ListSubscriptionsResult
);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SetUpstreamParams {
pub upstream: String,
}
control_call!(SetUpstreamParams => ControlMethod::ConfigSetUpstream, results::SetUpstreamResult);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SetLevelParams {
pub filter: String,
}
control_call!(SetLevelParams => ControlMethod::LogSetLevel, results::SetLevelResult);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SetCapParams {
pub cap_bytes: u64,
}
control_call!(SetCapParams => ControlMethod::CacheSetCap, results::SetCapResult);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PinParams {
pub store: String,
}
control_call!(PinParams => ControlMethod::HostedStoresPin, results::PinResult);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UnpinParams {
pub store: String,
}
control_call!(UnpinParams => ControlMethod::HostedStoresUnpin, results::UnpinResult);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HostedStoreStatusParams {
pub store: String,
}
control_call!(HostedStoreStatusParams => ControlMethod::HostedStoresStatus, results::HostedStoreStatusResult);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SyncTriggerParams {
pub store: String,
}
control_call!(SyncTriggerParams => ControlMethod::SyncTrigger, results::SyncTriggerResult);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SetChannelParams {
pub channel: String,
}
control_call!(SetChannelParams => ControlMethod::UpdaterSetChannel, serde_json::Value);
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct PauseParams {
#[serde(skip_serializing_if = "Option::is_none", default)]
pub until: Option<u64>,
}
control_call!(PauseParams => ControlMethod::UpdaterPause, serde_json::Value);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ApproveParams {
pub pairing_id: String,
}
control_call!(ApproveParams => ControlMethod::PairingApprove, results::PairingApproveResult);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RevokeParams {
pub token_id: String,
}
control_call!(RevokeParams => ControlMethod::PairingRevoke, results::PairingRevokeResult);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PeersConnectParams {
pub peer: String,
}
control_call!(PeersConnectParams => ControlMethod::PeersConnect, results::PeersConnectResult);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PeersDisconnectParams {
pub peer: String,
}
control_call!(PeersDisconnectParams => ControlMethod::PeersDisconnect, results::PeersDisconnectResult);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SubscribeParams {
pub store_id: String,
}
control_call!(SubscribeParams => ControlMethod::Subscribe, results::SubscribeResult);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UnsubscribeParams {
pub store_id: String,
}
control_call!(UnsubscribeParams => ControlMethod::Unsubscribe, results::UnsubscribeResult);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Asset {
Xch,
Dig,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WalletBalanceParams {
pub address: String,
pub asset: Asset,
}
control_call!(WalletBalanceParams => ControlMethod::WalletBalance, results::WalletBalanceResult);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WalletCoinsParams {
pub address: String,
pub asset: Asset,
}
control_call!(WalletCoinsParams => ControlMethod::WalletCoins, results::WalletCoinsResult);
const COIN_ID_HEX_LEN: usize = 64;
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct WalletCoinByIdParams {
pub coin_id: String,
}
control_call!(WalletCoinByIdParams => ControlMethod::WalletCoinById, results::WalletCoinByIdResult);
const COIN_ID_ERROR: &str = "coin_id must be lowercase 64-hex, optionally 0x-prefixed";
fn normalize_coin_id(coin_id: &str) -> Option<&str> {
let normalized = coin_id.strip_prefix("0x").unwrap_or(coin_id);
let well_formed = normalized.len() == COIN_ID_HEX_LEN
&& normalized
.bytes()
.all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b));
well_formed.then_some(normalized)
}
impl<'de> Deserialize<'de> for WalletCoinByIdParams {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct RawWalletCoinByIdParams {
coin_id: String,
}
let raw = RawWalletCoinByIdParams::deserialize(deserializer)?;
let coin_id = normalize_coin_id(&raw.coin_id)
.ok_or_else(|| serde::de::Error::custom(COIN_ID_ERROR))?
.to_owned();
Ok(Self { coin_id })
}
}
impl WalletCoinByIdParams {
pub fn validated(self) -> Result<Self, crate::error::ControlError> {
let normalized = normalize_coin_id(&self.coin_id).ok_or_else(|| {
crate::error::ControlError::of(
crate::error::ControlErrorCode::InvalidParams,
COIN_ID_ERROR,
)
})?;
Ok(WalletCoinByIdParams {
coin_id: normalized.to_owned(),
})
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct WalletPeakParams {}
control_call!(WalletPeakParams => ControlMethod::WalletPeak, results::WalletPeakResult);
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct PeerCountsParams {}
control_call!(PeerCountsParams => ControlMethod::PeerCounts, results::PeerCountsResult);
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct WalletSyncStatusParams {}
control_call!(WalletSyncStatusParams => ControlMethod::WalletSyncStatus, results::WalletSyncStatusResult);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WalletBroadcastParams {
pub signed_bundle_hex: String,
}
control_call!(WalletBroadcastParams => ControlMethod::WalletBroadcast, results::WalletBroadcastResult);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RequestParams {
pub client_name: String,
}
control_call!(RequestParams => ControlMethod::PairingRequest, results::PairingRequestResult);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PollParams {
pub pairing_id: String,
}
control_call!(PollParams => ControlMethod::PairingPoll, results::PairingPollResult);
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::build_request;
use serde_json::json;
#[test]
fn no_param_call_serializes_params_to_empty_object() {
let req = build_request(1.into(), &StatusParams {});
assert_eq!(req.method, "control.status");
assert_eq!(req.params, json!({}));
}
#[test]
fn data_param_call_carries_its_fields() {
let req = build_request(2.into(), &SetCapParams { cap_bytes: 128 });
assert_eq!(req.method, "control.cache.setCap");
assert_eq!(req.params, json!({ "cap_bytes": 128 }));
}
#[test]
fn pause_omits_until_when_indefinite() {
assert_eq!(
serde_json::to_value(PauseParams { until: None }).unwrap(),
json!({})
);
assert_eq!(
serde_json::to_value(PauseParams { until: Some(99) }).unwrap(),
json!({ "until": 99 })
);
}
#[test]
fn method_binding_matches_the_catalog_name() {
assert_eq!(
SetUpstreamParams::METHOD.name(),
"control.config.setUpstream"
);
assert_eq!(RequestParams::METHOD.name(), "pairing.request");
assert_eq!(PollParams::METHOD.name(), "pairing.poll");
}
}