tosca/
response.rs

1use alloc::borrow::Cow;
2
3use serde::Serialize;
4
5use crate::device::DeviceInfo;
6
7/// The header value associated with a response sent by a device which had
8/// failed to serialize its values.
9///
10/// This constant signals to the controller to discard the invalid response
11/// because a serialization error occurred on a device.
12pub const SERIALIZATION_ERROR: &str = "Serialization-Error";
13
14/// Response kinds.
15#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize)]
16#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
17pub enum ResponseKind {
18    /// This response transmits a concise JSON message over the network to
19    /// notify a controller that an operation completed successfully.
20    #[default]
21    Ok,
22    /// This response transmits a JSON message over the network containing
23    /// the data produced during a device operation.
24    Serial,
25    /// This response transmits a JSON message over the network containing
26    /// a device energy and economy information.
27    Info,
28    /// This response transmits a byte stream of data over the network.
29    #[cfg(feature = "stream")]
30    Stream,
31}
32
33impl core::fmt::Display for ResponseKind {
34    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
35        match self {
36            Self::Ok => "Ok",
37            Self::Serial => "Serial",
38            Self::Info => "Info",
39            #[cfg(feature = "stream")]
40            Self::Stream => "Stream",
41        }
42        .fmt(f)
43    }
44}
45
46/// A response which transmits a concise JSON message over the network to notify
47/// a controller that an operation completed successfully.
48#[derive(Debug, PartialEq, Serialize)]
49#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
50pub struct OkResponse {
51    action_terminated_correctly: bool,
52}
53
54impl OkResponse {
55    /// Generates an [`OkResponse`].
56    #[must_use]
57    pub const fn ok() -> Self {
58        Self {
59            action_terminated_correctly: true,
60        }
61    }
62}
63
64/// A response which transmits a JSON message over the network containing
65/// the data produced during a device operation.
66#[derive(Debug, PartialEq, Serialize)]
67#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
68pub struct SerialResponse<T: Serialize>(T);
69
70impl<T: Serialize> SerialResponse<T> {
71    /// Generates a [`SerialResponse`].
72    #[must_use]
73    pub const fn new(data: T) -> Self {
74        Self(data)
75    }
76}
77
78/// A response which transmits a JSON message over the network containing
79/// a device's energy and economy information.
80#[derive(Debug, PartialEq, Serialize)]
81#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
82pub struct InfoResponse(DeviceInfo);
83
84impl InfoResponse {
85    /// Generates an [`InfoResponse`].
86    #[must_use]
87    pub const fn new(info: DeviceInfo) -> Self {
88        Self(info)
89    }
90}
91
92/// All possible errors that may cause a device operation to fail.
93#[derive(Debug, PartialEq, Serialize)]
94#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
95pub enum ErrorKind {
96    /// Some data encountered during a device operation is invalid or malformed.
97    InvalidData,
98    /// An internal error has occurred during the execution of a device
99    /// operation.
100    Internal,
101}
102
103/// A response providing details about an error encountered during a
104/// device operation.
105///
106/// Contains the [`ErrorKind`], a general error description,
107/// and optional information about the encountered error.
108#[derive(Debug, PartialEq, Serialize)]
109#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
110pub struct ErrorResponse<'a> {
111    /// Error kind.
112    pub error: ErrorKind,
113    /// Error description.
114    pub description: Cow<'a, str>,
115    /// Information describing the encountered error.
116    pub info: Option<Cow<'a, str>>,
117}
118
119impl<'a> ErrorResponse<'a> {
120    /// Generates an [`ErrorResponse`].
121    ///
122    /// Requires specifying the [`ErrorKind`] and a general error description.
123    #[must_use]
124    #[inline]
125    pub fn with_description(error: ErrorKind, description: &'a str) -> Self {
126        Self {
127            error,
128            description: Cow::Borrowed(description),
129            info: None,
130        }
131    }
132
133    /// Generates an [`ErrorResponse`].
134    ///
135    /// Requires specifying the [`ErrorKind`], a general error
136    /// description, and optional information about the encountered error.
137    #[must_use]
138    #[inline]
139    pub fn with_description_error(error: ErrorKind, description: &'a str, info: &'a str) -> Self {
140        Self {
141            error,
142            description: Cow::Borrowed(description),
143            info: Some(Cow::Borrowed(info)),
144        }
145    }
146
147    /// Generates an [`ErrorResponse`] for invalid data.
148    ///
149    /// Requires specifying a general error description.
150    #[must_use]
151    #[inline]
152    pub fn invalid_data(description: &'a str) -> Self {
153        Self::with_description(ErrorKind::InvalidData, description)
154    }
155
156    /// Generates an [`ErrorResponse`] for invalid data.
157    ///
158    ///
159    /// Requires specifying a general error description and optional
160    /// information about the encountered error.
161    #[must_use]
162    #[inline]
163    pub fn invalid_data_with_error(description: &'a str, info: &'a str) -> Self {
164        Self::with_description_error(ErrorKind::InvalidData, description, info)
165    }
166
167    /// Generates an [`ErrorResponse`] for an internal error.
168    ///
169    /// Requires specifying a general error description.
170    #[must_use]
171    #[inline]
172    pub fn internal(description: &'a str) -> Self {
173        Self::with_description(ErrorKind::Internal, description)
174    }
175
176    /// Generates an [`ErrorResponse`] for an internal error.
177    ///
178    ///
179    /// Requires specifying a general error description and optional
180    /// information about the encountered error.
181    #[must_use]
182    #[inline]
183    pub fn internal_with_error(description: &'a str, info: &'a str) -> Self {
184        Self::with_description_error(ErrorKind::Internal, description, info)
185    }
186}
187
188#[cfg(test)]
189#[cfg(feature = "deserialize")]
190mod tests {
191    use serde::Deserialize;
192
193    use crate::{deserialize, serialize};
194
195    use super::{OkResponse, SerialResponse, Serialize};
196
197    use super::{Cow, DeviceInfo, ErrorKind, ErrorResponse, InfoResponse};
198
199    #[test]
200    fn test_ok_response() {
201        assert_eq!(
202            deserialize::<OkResponse>(serialize(OkResponse::ok())),
203            OkResponse {
204                action_terminated_correctly: true,
205            }
206        );
207    }
208
209    #[test]
210    fn test_serial_value_response() {
211        #[derive(Debug, PartialEq, Serialize, Deserialize)]
212        struct SerialValue {
213            value: u32,
214        }
215
216        assert_eq!(
217            deserialize::<SerialValue>(serialize(SerialResponse::new(SerialValue { value: 42 }))),
218            SerialValue { value: 42 },
219        );
220    }
221
222    #[test]
223    fn test_serial_cow_response() {
224        #[derive(Debug, PartialEq, Serialize, Deserialize)]
225        struct SerialCow<'a> {
226            value: Cow<'a, str>,
227        }
228
229        assert_eq!(
230            deserialize::<SerialCow>(serialize(SerialResponse::new(SerialCow {
231                value: Cow::Borrowed("hi")
232            }))),
233            SerialCow {
234                value: Cow::Owned("hi".into())
235            },
236        );
237    }
238
239    #[test]
240    fn test_info_response() {
241        let energy = crate::energy::Energy::init_with_water_use_efficiency(
242            crate::energy::WaterUseEfficiency::init_with_gpp(42.0),
243        );
244
245        assert_eq!(
246            deserialize::<DeviceInfo>(serialize(InfoResponse::new(
247                DeviceInfo::empty().add_energy(energy)
248            ))),
249            DeviceInfo {
250                energy: crate::energy::Energy {
251                    energy_efficiencies: None,
252                    carbon_footprints: None,
253                    water_use_efficiency: Some(crate::energy::WaterUseEfficiency {
254                        gpp: Some(42.0),
255                        penman_monteith_equation: None,
256                        wer: None,
257                    }),
258                },
259                economy: crate::economy::Economy::empty(),
260            }
261        );
262    }
263
264    #[test]
265    fn test_error_response() {
266        let error = ErrorResponse::with_description(
267            ErrorKind::InvalidData,
268            "Invalid data error description",
269        );
270
271        assert_eq!(
272            deserialize::<ErrorResponse>(serialize(error)),
273            ErrorResponse {
274                error: ErrorKind::InvalidData,
275                description: Cow::Borrowed("Invalid data error description"),
276                info: None,
277            }
278        );
279    }
280}