edgehog_device_runtime_forwarder/
astarte.rs1use std::hash::Hash;
9
10use astarte_device_sdk::{Error as SdkError, FromEvent, IntoAstarteObject};
11use url::{ParseError, Url};
12
13#[non_exhaustive]
15#[derive(displaydoc::Display, thiserror::Error, Debug)]
16pub enum AstarteError {
17 Sdk(#[from] SdkError),
19
20 MissingSessionToken,
22
23 ParseUrl(#[from] ParseError),
25}
26
27#[derive(Debug, Clone, Eq, PartialEq, Hash, FromEvent, IntoAstarteObject)]
29#[from_event(
30 interface = "io.edgehog.devicemanager.ForwarderSessionRequest",
31 path = "/request",
32 aggregation = "object"
33)]
34pub struct SessionInfo {
35 pub host: String,
37 pub port: i32,
39 pub session_token: String,
41 pub secure: bool,
43}
44
45impl TryFrom<&SessionInfo> for Url {
46 type Error = AstarteError;
47
48 fn try_from(value: &SessionInfo) -> Result<Self, Self::Error> {
49 if value.session_token.is_empty() {
50 return Err(AstarteError::MissingSessionToken);
51 }
52
53 let schema = if value.secure { "wss" } else { "ws" };
54
55 Url::parse_with_params(
56 &format!(
57 "{}://{}:{}/device/websocket",
58 schema, value.host, value.port
59 ),
60 &[("session", &value.session_token)],
61 )
62 .map_err(AstarteError::ParseUrl)
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use astarte_device_sdk::aggregate::AstarteObject;
70 use astarte_device_sdk::chrono::Utc;
71 use astarte_device_sdk::types::AstarteData;
72 use astarte_device_sdk::{DeviceEvent, Value};
73 use std::net::Ipv4Addr;
74 use url::Host;
75
76 fn create_sinfo(session_token: &str) -> SessionInfo {
77 SessionInfo {
78 host: Ipv4Addr::LOCALHOST.to_string(),
79 port: 8080,
80 session_token: session_token.to_string(),
81 secure: false,
82 }
83 }
84
85 fn create_astarte_event(
86 host: &str,
87 port: i32,
88 session_token: &str,
89 secure: bool,
90 ) -> DeviceEvent {
91 let mut data = AstarteObject::new();
92
93 data.insert("host".to_string(), AstarteData::String(host.to_string()));
94 data.insert("port".to_string(), AstarteData::Integer(port));
95 data.insert(
96 "session_token".to_string(),
97 AstarteData::String(session_token.to_string()),
98 );
99 data.insert("secure".to_string(), AstarteData::Boolean(secure));
100
101 DeviceEvent {
102 interface: "io.edgehog.devicemanager.ForwarderSessionRequest".to_string(),
103 path: "/request".to_string(),
104 data: Value::Object {
105 data,
106 timestamp: Utc::now(),
107 },
108 }
109 }
110
111 #[test]
112 fn test_astarte_aggregate() {
113 let sinfo = create_sinfo("test_token");
114
115 let expected = [
116 ("host", AstarteData::String("127.0.0.1".to_string())),
117 ("port", AstarteData::Integer(8080)),
118 (
119 "session_token",
120 AstarteData::String("test_token".to_string()),
121 ),
122 ];
123
124 let res: Result<AstarteObject, astarte_device_sdk::Error> = sinfo.try_into();
125
126 assert!(res.is_ok());
127
128 let res = res.unwrap();
129
130 for (key, exp_val) in expected {
131 assert_eq!(*res.get(key).unwrap(), exp_val);
132 }
133 }
134
135 #[test]
136 fn test_try_from_sinfo() {
137 let mut sinfo = create_sinfo("");
139
140 assert!(Url::try_from(&sinfo).is_err());
141
142 sinfo = create_sinfo("test_token");
144
145 let case = Url::try_from(&sinfo).unwrap();
146
147 assert_eq!(case.host(), Some(Host::Ipv4(Ipv4Addr::LOCALHOST)));
148 assert_eq!(case.port(), Some(8080));
149 assert_eq!(case.query(), Some("session=test_token"));
150 }
151
152 #[test]
153 fn test_retrieve_sinfo() {
154 let err_cases = [
155 create_astarte_event("", 8080, "test_token", false),
156 create_astarte_event("127.0.0.1", -1, "test_token", false),
157 create_astarte_event("127.0.0.1", 8080, "", false),
158 ];
159
160 for event in err_cases {
161 let sinfo = SessionInfo::from_event(event).unwrap();
162 assert!(Url::try_from(&sinfo).is_err());
163 }
164
165 let event = create_astarte_event("127.0.0.1", 8080, "test_token", false);
166 let sinfo = SessionInfo::from_event(event).unwrap();
167
168 assert_eq!(sinfo.host, Ipv4Addr::LOCALHOST.to_string());
169 assert_eq!(sinfo.port, 8080);
170 assert_eq!(sinfo.session_token, "test_token".to_string());
171 assert!(!sinfo.secure);
172
173 let url = Url::try_from(&sinfo).unwrap();
174 let exp = Url::try_from("ws://127.0.0.1:8080/device/websocket?session=test_token").unwrap();
175 assert_eq!(exp, url);
176 }
177}