fundamentum_edge_mcu_http_client/models/api_response/
mod.rs

1use failed_data::ApiResponseFailedData;
2use failed_empty::ApiResponseFailedEmpty;
3use serde::Deserialize;
4
5pub mod failed_data;
6pub mod failed_empty;
7
8/// Status returned from the Fundamentum API.
9#[derive(Deserialize)]
10#[cfg_attr(feature = "log", derive(defmt::Format))]
11#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
12#[serde(rename_all = "lowercase")]
13pub enum ApiStatus {
14    /// The request was a Success
15    Success,
16    /// There was an Error while processing the request
17    Error,
18}
19
20/// The basic Fundamentum API response scheme.
21#[derive(Deserialize)]
22#[cfg_attr(feature = "log", derive(defmt::Format))]
23#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
24#[serde(bound(deserialize = "T: Deserialize<'de>"))]
25pub struct ApiResponse<'a, T> {
26    /// The response status
27    #[serde(rename = "status")]
28    pub status: Option<ApiStatus>,
29    /// The error message
30    #[serde(rename = "message")]
31    pub message: Option<&'a str>,
32    /// The successful response's data or some errors. For responses with errors,
33    /// see [`ApiResponseFailedData`](failed_data::ApiResponseFailedData)
34    /// and [`ApiResponseFailedEmpty`](failed_empty::ApiResponseFailedEmpty)
35    /// structures.
36    #[serde(rename = "data")]
37    pub data: Option<T>,
38}
39
40impl<'a, T: Deserialize<'a>> From<ApiResponseFailedData<'a>> for ApiResponse<'a, T> {
41    fn from(response: ApiResponseFailedData<'a>) -> Self {
42        ApiResponse {
43            status: response.status,
44            message: response.message,
45            data: None,
46        }
47    }
48}
49
50impl<'a, T: Deserialize<'a>> From<ApiResponseFailedEmpty<'a>> for ApiResponse<'a, T> {
51    fn from(response: ApiResponseFailedEmpty<'a>) -> Self {
52        ApiResponse {
53            status: response.status,
54            message: response.message,
55            data: None,
56        }
57    }
58}
59
60#[cfg(test)]
61mod tests {
62
63    use failed_data::{ApiError, ApiErrors, MAX_NUMBER_OF_ERRORS};
64    use failed_empty::Empty;
65    use indoc::indoc;
66
67    use crate::models::provision_response::{MqttBroker, ProvisionResponse};
68
69    use super::*;
70
71    #[test]
72    fn given_successful_body_when_deserializing_api_response_then_successfully_deserialize() {
73        let body = indoc! {r#"
74        {
75            "status": "success",
76            "data": {
77                "configuration": null,
78                "mqtt": {
79                    "host": "mqtts.fundamentum-iot-dev.com",
80                    "port": 8883
81                }
82            }
83        }"#};
84
85        let (api_response, _size): (ApiResponse<ProvisionResponse<Empty>>, usize) =
86            serde_json_core::from_slice(body.as_bytes()).unwrap();
87
88        let expected_response = ApiResponse {
89            status: Some(ApiStatus::Success),
90            message: None,
91            data: Some(ProvisionResponse {
92                configuration: None,
93                mqtt: MqttBroker {
94                    host: "mqtts.fundamentum-iot-dev.com",
95                    port: 8883,
96                },
97            }),
98        };
99
100        assert_eq!(api_response, expected_response);
101    }
102
103    #[test]
104    fn given_failed_body_with_errors_when_deserializing_api_response_failed_data_then_successfully_deserialize(
105    ) {
106        let body = indoc! {r#"
107        {
108            "status": "error",
109            "message": "There is an error with the request's content",
110            "data": {
111                "errors": [
112                    {
113                        "type": "field",
114                        "value": "serial_number_1S",
115                        "msg": "Invalid value",
116                        "path": "serial_number",
117                        "location": "body"
118                    }
119                ]
120            }
121        }"#};
122
123        let (api_response, _size): (ApiResponseFailedData, usize) =
124            serde_json_core::from_slice(body.as_bytes()).unwrap();
125
126        let mut array = [None; MAX_NUMBER_OF_ERRORS];
127        array[0] = Some(ApiError {
128            r#type: "field",
129            value: "serial_number_1S",
130            msg: "Invalid value",
131            path: "serial_number",
132            location: "body",
133        });
134        let expected_response = ApiResponseFailedData {
135            status: Some(ApiStatus::Error),
136            message: Some("There is an error with the request's content"),
137            data: Some(ApiErrors {
138                errors: Some(array),
139            }),
140        };
141
142        assert_eq!(api_response, expected_response);
143    }
144
145    #[test]
146    fn given_failed_body_with_no_errors_when_deserializing_api_response_failed_empty_then_successfully_deserialize(
147    ) {
148        let body = indoc! {r#"
149        {
150            "status": "error",
151            "message": "No Registry to provision device on this project",
152            "data": {}
153        }"#};
154
155        let (api_response, _size): (ApiResponseFailedEmpty, usize) =
156            serde_json_core::from_slice(body.as_bytes()).unwrap();
157
158        let expected_response = ApiResponseFailedEmpty {
159            status: Some(ApiStatus::Error),
160            message: Some("No Registry to provision device on this project"),
161            data: Some(Empty {}),
162        };
163
164        assert_eq!(api_response, expected_response);
165    }
166}