edgehog_device_runtime_containers/requests/
deployment.rs1use astarte_device_sdk::{event::FromEventError, types::TypeError, AstarteData, FromEvent};
22use tracing::error;
23use uuid::Uuid;
24
25use super::{ReqUuid, VecReqUuid};
26
27#[derive(Debug, Clone, FromEvent, PartialEq, Eq, PartialOrd, Ord)]
29#[from_event(
30 interface = "io.edgehog.devicemanager.apps.CreateDeploymentRequest",
31 path = "/deployment",
32 rename_all = "camelCase",
33 aggregation = "object"
34)]
35pub struct CreateDeployment {
36 pub(crate) id: ReqUuid,
37 pub(crate) containers: VecReqUuid,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub struct DeploymentCommand {
43 pub(crate) id: Uuid,
44 pub(crate) command: CommandValue,
45}
46
47impl FromEvent for DeploymentCommand {
48 type Err = FromEventError;
49
50 fn from_event(event: astarte_device_sdk::DeviceEvent) -> Result<Self, Self::Err> {
51 let id = event
52 .path
53 .strip_prefix('/')
54 .and_then(|path| path.strip_suffix("/command"))
55 .and_then(|id| Uuid::parse_str(id).ok())
56 .ok_or_else(|| FromEventError::Path {
57 interface: "io.edgehog.devicemanager.apps.DeploymentCommand",
58 base_path: event.path.clone(),
59 })?;
60
61 let event = DeploymentCommandEvent::from_event(event)?;
62
63 match event {
64 DeploymentCommandEvent::Command(command) => Ok(Self { id, command }),
65 }
66 }
67}
68
69#[derive(Debug, Clone, FromEvent)]
70#[from_event(
71 interface = "io.edgehog.devicemanager.apps.DeploymentCommand",
72 aggregation = "individual"
73)]
74enum DeploymentCommandEvent {
75 #[mapping(endpoint = "/%{deployment_id}/command")]
76 Command(CommandValue),
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub(crate) enum CommandValue {
81 Start,
82 Stop,
83 Delete,
84}
85
86impl TryFrom<AstarteData> for CommandValue {
87 type Error = TypeError;
88
89 fn try_from(value: AstarteData) -> Result<Self, Self::Error> {
90 let value = String::try_from(value)?;
91
92 match value.as_str() {
93 "Start" => Ok(Self::Start),
94 "Stop" => Ok(Self::Stop),
95 "Delete" => Ok(Self::Delete),
96 _ => {
97 error!("unrecognize DeploymentCommand command value {value}");
98
99 Err(TypeError::Conversion {
100 ctx: format!("unrecognize DeploymentCommand command value {value}"),
101 })
102 }
103 }
104 }
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
109pub struct DeploymentUpdate {
110 pub(crate) from: Uuid,
111 pub(crate) to: Uuid,
112}
113
114impl FromEvent for DeploymentUpdate {
115 type Err = FromEventError;
116
117 fn from_event(event: astarte_device_sdk::DeviceEvent) -> Result<Self, Self::Err> {
118 let event = DeploymentUpdateEvent::from_event(event)?;
119
120 let from = Uuid::parse_str(&event.from).map_err(|err| {
121 error!(
122 error = format!("{:#}", eyre::Report::new(err)),
123 from = event.from,
124 "invalid deployment update 'from' uuid"
125 );
126
127 FromEventError::Conversion(TypeError::Conversion {
128 ctx: "invalid deployment update 'from' uuid".to_string(),
129 })
130 })?;
131
132 let to = Uuid::parse_str(&event.to).map_err(|err| {
133 error!(
134 error = format!("{:#}", eyre::Report::new(err)),
135 to = event.to,
136 "invalid deployment update 'to' uuid"
137 );
138
139 FromEventError::Conversion(TypeError::Conversion {
140 ctx: "invalid deployment update 'to' uuid".to_string(),
141 })
142 })?;
143
144 Ok(Self { from, to })
145 }
146}
147
148#[derive(Debug, Clone, FromEvent, PartialEq, Eq, PartialOrd, Ord)]
149#[from_event(
150 interface = "io.edgehog.devicemanager.apps.DeploymentUpdate",
151 path = "/deployment",
152 rename_all = "camelCase",
153 aggregation = "object"
154)]
155struct DeploymentUpdateEvent {
156 from: String,
157 to: String,
158}
159
160#[cfg(test)]
161pub(crate) mod tests {
162
163 use std::fmt::Display;
164
165 use astarte_device_sdk::aggregate::AstarteObject;
166 use astarte_device_sdk::chrono::Utc;
167 use astarte_device_sdk::{AstarteData, DeviceEvent, Value};
168 use pretty_assertions::assert_eq;
169
170 use super::*;
171
172 pub fn create_deployment_request_event<S: Display>(id: &str, containers: &[S]) -> DeviceEvent {
173 let fields = [
174 ("id", AstarteData::String(id.to_string())),
175 (
176 "containers",
177 AstarteData::StringArray(containers.iter().map(|s| s.to_string()).collect()),
178 ),
179 ]
180 .into_iter()
181 .map(|(k, v)| (k.to_string(), v))
182 .collect();
183
184 DeviceEvent {
185 interface: "io.edgehog.devicemanager.apps.CreateDeploymentRequest".to_string(),
186 path: "/deployment".to_string(),
187 data: Value::Object {
188 data: fields,
189 timestamp: Utc::now(),
190 },
191 }
192 }
193
194 #[test]
195 fn create_deployment_request() {
196 let id = ReqUuid(Uuid::new_v4());
197 let containers = VecReqUuid(vec![ReqUuid(Uuid::new_v4())]);
198 let event = create_deployment_request_event(&id.to_string(), &containers);
199
200 let request = CreateDeployment::from_event(event).unwrap();
201
202 let expect = CreateDeployment { id, containers };
203
204 assert_eq!(request, expect);
205 }
206
207 #[test]
208 fn deployment_command() {
209 let id = Uuid::new_v4();
210
211 let event = DeviceEvent {
212 interface: "io.edgehog.devicemanager.apps.DeploymentCommand".to_string(),
213 path: format!("/{id}/command"),
214 data: Value::Individual {
215 data: "Start".into(),
216 timestamp: Utc::now(),
217 },
218 };
219
220 let exp = DeploymentCommand {
221 id,
222 command: CommandValue::Start,
223 };
224
225 let cmd = DeploymentCommand::from_event(event).unwrap();
226 assert_eq!(cmd, exp);
227
228 let event = DeviceEvent {
229 interface: "io.edgehog.devicemanager.apps.DeploymentCommand".to_string(),
230 path: format!("/{id}/command"),
231 data: Value::Individual {
232 data: "Stop".into(),
233 timestamp: Utc::now(),
234 },
235 };
236
237 let exp = DeploymentCommand {
238 id,
239 command: CommandValue::Stop,
240 };
241
242 let cmd = DeploymentCommand::from_event(event).unwrap();
243 assert_eq!(cmd, exp);
244 }
245
246 #[test]
247 fn deployment_update() {
248 let from = Uuid::new_v4();
249 let to = Uuid::new_v4();
250
251 let data = AstarteObject::from_iter([
252 ("from".to_string(), AstarteData::String(from.to_string())),
253 ("to".to_string(), AstarteData::String(to.to_string())),
254 ]);
255
256 let event = DeviceEvent {
257 interface: "io.edgehog.devicemanager.apps.DeploymentUpdate".to_string(),
258 path: "/deployment".to_string(),
259 data: Value::Object {
260 data,
261 timestamp: Utc::now(),
262 },
263 };
264
265 let exp = DeploymentUpdate { from, to };
266
267 let cmd = DeploymentUpdate::from_event(event).unwrap();
268 assert_eq!(cmd, exp);
269 }
270}