ya_client_model/market/
reason.rs

1use derive_more::Display;
2use serde::de::DeserializeOwned;
3use serde::{Deserialize, Serialize};
4use std::fmt::Debug;
5
6/// Generic Event reason information structure.
7#[derive(Clone, Display, Debug, PartialEq, Serialize, Deserialize)]
8#[display(fmt = "'{}'", message)]
9pub struct Reason {
10    pub message: String,
11    #[serde(flatten)]
12    pub extra: serde_json::Value,
13}
14
15impl Reason {
16    /// Generic Event reason information structure.
17    pub fn new(message: impl ToString) -> Reason {
18        Reason {
19            message: message.to_string(),
20            extra: serde_json::json!({}),
21        }
22    }
23}
24
25impl<T: Into<String>> From<T> for Reason {
26    fn from(m: T) -> Self {
27        Reason::new(m.into())
28    }
29}
30
31#[derive(thiserror::Error, Clone, Debug, PartialEq)]
32#[error("Error converting `{0}` to Reason: {1}")]
33pub struct ReasonConversionError(String, String);
34
35impl Reason {
36    pub fn from_value<T: Serialize + Debug>(value: &T) -> Result<Self, ReasonConversionError> {
37        serde_json::to_value(value)
38            .and_then(serde_json::from_value)
39            .map_err(|e| ReasonConversionError(format!("{:?}", value), e.to_string()))
40    }
41
42    pub fn to_value<T: DeserializeOwned>(&self) -> Result<T, ReasonConversionError> {
43        serde_json::to_value(self)
44            .and_then(serde_json::from_value)
45            .map_err(|e| ReasonConversionError(format!("{:?}", self), e.to_string()))
46    }
47}
48
49#[cfg(test)]
50mod test {
51    use super::*;
52
53    #[test]
54    fn test_try_convert_self() {
55        let reason = Reason::new("coś");
56        assert_eq!(reason, Reason::from_value(&reason).unwrap());
57    }
58
59    #[test]
60    // check if message field in extra will not overwrite top-level message
61    fn test_try_convert_self_with_extra_message() {
62        let reason = Reason {
63            message: "coś".to_string(),
64            extra: serde_json::json!({"ala":"ma kota","message": "coś innego"}),
65        };
66        assert_eq!(
67            Reason {
68                message: "coś innego".to_string(),
69                extra: serde_json::json!({"ala":"ma kota"}),
70            },
71            Reason::from_value(&reason).unwrap()
72        );
73
74        assert_ne!(
75            reason,
76            Reason::from_value(&reason).unwrap().to_value().unwrap()
77        )
78    }
79
80    #[test]
81    fn test_try_convert_custom_reason_wo_message_field() {
82        #[derive(Serialize, Deserialize, Debug)]
83        struct CustomReason {}
84        let custom = CustomReason {};
85
86        assert_eq!(
87            "Error converting `CustomReason` to Reason: missing field `message`",
88            &Reason::from_value(&custom).unwrap_err().to_string()
89        )
90    }
91
92    #[test]
93    fn test_try_convert_custom_reason_with_message_field() {
94        #[derive(Serialize, Deserialize, Debug, PartialEq)]
95        struct CustomReason {
96            message: String,
97            other: bool,
98        }
99        let custom = CustomReason {
100            message: "coś".to_string(),
101            other: false,
102        };
103
104        assert_eq!(
105            Reason {
106                message: "coś".to_string(),
107                extra: serde_json::json!({ "other": false }),
108            },
109            Reason::from_value(&custom).unwrap()
110        );
111
112        assert_eq!(
113            custom,
114            Reason::from_value(&custom).unwrap().to_value().unwrap()
115        )
116    }
117
118    #[test]
119    fn test_try_convert_custom_reason_wrong_message_type() {
120        #[derive(Serialize, Deserialize, Debug)]
121        struct CustomReason {
122            message: u8,
123        }
124        let custom = CustomReason { message: 37 };
125
126        assert_eq!(
127            "Error converting `CustomReason { message: 37 }` to Reason: invalid \
128            type: integer `37`, expected a string",
129            &Reason::from_value(&custom).unwrap_err().to_string()
130        )
131    }
132
133    #[test]
134    fn test_try_convert_custom_reason_wrong_fancy_message_type() {
135        #[derive(Serialize, Deserialize, Debug)]
136        struct CustomReason {
137            i: u8,
138            b: bool,
139            s: String,
140            #[serde(flatten)]
141            extra: serde_json::Value,
142        }
143
144        let custom = CustomReason {
145            i: 0,
146            b: false,
147            s: "".to_string(),
148            extra: serde_json::json!({"message": true}),
149        };
150
151        assert_eq!(
152            format!(
153                "Error converting `{:?}` to Reason: invalid type: \
154                boolean `true`, expected a string",
155                custom
156            ),
157            Reason::from_value(&custom).unwrap_err().to_string()
158        )
159    }
160
161    #[test]
162    fn test_try_convert_custom_reason_with_fancy_message() {
163        #[derive(Serialize, Deserialize, Debug)]
164        struct CustomReason {
165            i: u8,
166            b: bool,
167            s: String,
168            #[serde(flatten)]
169            extra: serde_json::Value,
170        }
171
172        let custom = CustomReason {
173            i: 0,
174            b: false,
175            s: "".to_string(),
176            extra: serde_json::json!({"message": "coś"}),
177        };
178
179        assert_eq!(
180            Reason {
181                message: "coś".to_string(),
182                extra: serde_json::json!({ "i": 0, "b": false, "s": "" }),
183            },
184            Reason::from_value(&custom).unwrap()
185        )
186    }
187
188    #[test]
189    fn test_try_convert_json_reason_with_message() {
190        let json_reason = serde_json::json!({"message": "coś"});
191
192        assert_eq!(
193            Reason::new("coś"),
194            Reason::from_value(&json_reason).unwrap()
195        )
196    }
197}