matroskin 0.1.0

whatsminer api client lib
Documentation
//! Implement `set.log.upload` command
//!
//! Configures the device to stream system logs in real-time to a remote server.
//! This feature is equivalent to running `logread -f` and piping the output to a remote address.
//!
//! - Command: [SetLogUpload]
//! - ApiDoc: <https://apidoc.whatsminer.com/#api-Log-btminer_set_log_upload>
use core::str;

use serde::Serialize;

use crate::{
    command::{Command, CommandLevel},
    error::Result,
};

/// This command represents the `set.log.upload` operation.
///
/// Configures the device to stream system logs in real-time to a remote server.
/// This feature is equivalent to running `logread -f` and piping the output to a remote address.
///
/// - ApiDoc: <https://apidoc.whatsminer.com/#api-Log-btminer_set_log_upload>
///
/// # Example
/// ```rust,no_run
/// use matroskin::actor::Actor;
/// use matroskin::command::set_log_upload::SetLogUpload;
/// use matroskin::account::Account;
/// use matroskin::password::Password;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
///     let addr = "10.10.10.10:4433";
///     let username = Account::Super;
///     let password = Password::Super;
///
///     let actor = Actor::new(addr, username, password);
///     actor.connect()?;
///
///     let cmd = SetLogUpload::new("192.168.2.22", "4001", "udp");
///     let response = actor.send(cmd).await?;
///     println!("Response: {:#?}", response);
///
///     Ok(())
/// }
/// ```
#[derive(Debug, Serialize, Clone, PartialEq, Eq)]
pub struct SetLogUpload {
    /// The IP address of the remote log server.
    pub ip: String,
    /// The port number on the remote server.
    pub port: String,
    /// The protocol to use for log transmission. Valid values: `tcp`, `udp`.
    pub proto: String,
}

impl SetLogUpload {
    /// Create a new `set.log.upload` command.
    ///
    /// # Arguments
    /// * `ip` — The IP address of the remote log server.
    /// * `port` — The port number on the remote server.
    /// * `proto` — The protocol to use (`tcp` or `udp`).
    pub fn new(ip: impl Into<String>, port: impl Into<String>, proto: impl Into<String>) -> Self {
        Self {
            ip: ip.into(),
            port: port.into(),
            proto: proto.into(),
        }
    }
}

impl Command for SetLogUpload {
    type Response = String;
    const CMD_NAME: &'static str = "set.log.upload";
    const LEVEL: CommandLevel = CommandLevel::Secured;

    fn params(&self) -> Result<Option<impl serde::Serialize>> {
        Ok(Some(self))
    }
}

#[cfg(test)]
mod tests {
    use crate::account::Account;
    use crate::auth_data::AuthData;

    use super::*;

    const RESPONSE_JSON: &str =
        r#"{"code":0,"when":1692685496,"msg":"ok","desc":"set.log.upload"}"#;

    #[test]
    fn request_has_correct_structure() {
        let cmd = SetLogUpload::new("192.168.2.22", "4001", "udp");
        assert!(cmd.is_secured());
        assert!(!cmd.is_encrypted());

        let auth = AuthData::new::<SetLogUpload>(Account::Super, "pass", "salt").unwrap();
        let encoded = cmd.encode(Some(auth)).unwrap();
        let json: serde_json::Value = serde_json::from_slice(&encoded.0).unwrap();

        assert_eq!(json["cmd"], "set.log.upload");
        assert_eq!(json["account"], "super");
        assert!(json["ts"].as_u64().unwrap() > 0);
        assert_eq!(json["token"].as_str().unwrap().len(), 8);
        assert_eq!(json["param"]["ip"], "192.168.2.22");
        assert_eq!(json["param"]["port"], "4001");
        assert_eq!(json["param"]["proto"], "udp");
    }

    #[test]
    fn deserialize_response_fixture() {
        let response = SetLogUpload::parse_response(RESPONSE_JSON.as_bytes()).unwrap();
        assert!(response.code.is_success());
        assert_eq!(response.desc, "set.log.upload");
        assert_eq!(response.when, 1692685496);
        assert_eq!(response.msg, "ok");
    }
}