1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use derive_setters::Setters;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::schema::api_errors::XtbErrorCode;


/// Message sent to XTB servers
#[derive(Clone, Default, Debug, Serialize, Setters)]
#[serde(rename_all = "camelCase")]
#[setters(into, prefix = "with_", strip_option)]
pub struct Request {
    /// The command name
    pub command: String,
    /// Data (payload) send with a command
    #[serde(skip_serializing_if = "Option::is_none")]
    pub arguments: Option<Value>,
    /// Custom tag for message identification
    pub custom_tag: Option<String>,
}


impl Request {

    /// Correctly set arguments.
    ///
    /// The arguments can be:
    ///
    /// * None - set payload to None
    /// * Some(Value::Null) - set payload to None
    /// * Some(Value::Object) - set payload to given value
    ///
    /// # Panics
    ///
    /// Any other payload configuration than supported one
    pub fn with_maybe_arguments(mut self, arguments: Option<Value>) -> Self {
        match arguments {
            None | Some(Value::Null) => self.arguments = None,
            Some(Value::Object(obj)) => self.arguments = Some(Value::Object(obj)),
            _ => panic!("Unsupported argument type. RTFM")
        }
        self
    }
}


/// Response message returned from server when operation succeeds.
#[derive(Clone, Default, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Response {
    /// Response status is always TRUE
    pub status: bool,
    /// This is set only for response to the "login" request
    pub stream_session_id: Option<String>,
    /// Returning data.
    pub return_data: Option<Value>,
    /// Custom tag from original message
    pub custom_tag: Option<String>,
}


/// Subscribe for stream of data
///
/// # Note
///
/// This struct does not carry any arguments itself. The arguments are merged into serialized object
/// before the send operation is done.
#[derive(Clone, Default, Debug, Serialize, Setters)]
#[serde(rename_all = "camelCase")]
#[setters(into, prefix = "with_", strip_option)]
pub struct SubscribeRequest {
    /// The command name
    pub command: String,
    /// The stream session id (identify the connection)
    pub stream_session_id: String
}


/// Unsubscribe from stream of data
///
/// # Note
///
/// This struct does not carry any arguments itself. The arguments are merged into serialized object
/// before the send operation is done.
#[derive(Clone, Default, Debug, Serialize, Setters)]
#[serde(rename_all = "camelCase")]
#[setters(into, prefix = "with_", strip_option)]
pub struct UnsubscribeRequest {
    /// Command to be unsubscribed from
    pub command: String,
}


/// Data stream item representation
#[derive(Clone, Default, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StreamDataMessage {
    /// Source command
    pub command: String,
    /// Payload
    pub data: Value
}


/// Response message returned from server when operation fails.
#[derive(Clone, Default, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ErrorResponse {
    /// Error response status is always FALSE
    pub status: bool,
    /// The machine-readable error code
    pub error_code: XtbErrorCode,
    /// The human-readable error code
    pub error_descr: Option<String>,
    /// Custom tag from original message
    pub custom_tag: Option<String>,
}


#[cfg(test)]
mod tests {
    use rstest::rstest;
    use serde_json::{from_str, to_value, Value};
    use crate::schema::Request;

    #[rstest]
    #[case(Request::default().with_command("command").with_arguments("argument").with_custom_tag("tag"), "{\"command\": \"command\", \"arguments\": \"argument\", \"customTag\": \"tag\"}")]
    fn serialize_request(#[case] request: Request, #[case] expected_json: &str) {
        let request_value = to_value(request).unwrap();
        let expected_value: Value = from_str(expected_json).unwrap();
        assert_eq!(request_value, expected_value)
    }
}