edgehog_device_runtime_containers/requests/
mod.rs1use std::{borrow::Borrow, fmt::Display, num::ParseIntError, ops::Deref};
22
23use astarte_device_sdk::{
24 event::FromEventError, types::TypeError, AstarteData, DeviceEvent, FromEvent,
25};
26use container::{CreateContainer, RestartPolicyError};
27use deployment::{CreateDeployment, DeploymentCommand, DeploymentUpdate};
28use tracing::error;
29use uuid::Uuid;
30
31use self::device_mapping::CreateDeviceMapping;
32use self::{image::CreateImage, network::CreateNetwork, volume::CreateVolume};
33
34pub mod container;
35pub mod deployment;
36pub mod device_mapping;
37pub mod image;
38pub mod network;
39pub mod volume;
40
41#[non_exhaustive]
43#[derive(Debug, displaydoc::Display, thiserror::Error)]
44pub enum ReqError {
45 Option(String),
47 RestartPolicy(#[from] RestartPolicyError),
49 PortBinding(#[from] BindingError),
51}
52
53#[non_exhaustive]
55#[derive(Debug, displaydoc::Display, thiserror::Error)]
56pub enum BindingError {
57 Port {
59 binding: &'static str,
61 value: String,
63 #[source]
65 source: ParseIntError,
66 },
67 Idx,
69}
70
71#[derive(Debug, Clone, PartialEq)]
73pub enum ContainerRequest {
74 Image(CreateImage),
76 Volume(CreateVolume),
78 Network(CreateNetwork),
80 DeviceMapping(CreateDeviceMapping),
82 Container(Box<CreateContainer>),
84 Deployment(CreateDeployment),
86 DeploymentCommand(DeploymentCommand),
88 DeploymentUpdate(DeploymentUpdate),
90}
91
92impl ContainerRequest {
93 pub(crate) fn deployment_id(&self) -> Uuid {
94 match self {
95 ContainerRequest::Image(value) => value.deployment_id.0,
96 ContainerRequest::Volume(value) => value.deployment_id.0,
97 ContainerRequest::Network(value) => value.deployment_id.0,
98 ContainerRequest::Container(value) => value.deployment_id.0,
99 ContainerRequest::DeviceMapping(dm) => dm.deployment_id.0,
100 ContainerRequest::Deployment(create_deployment) => create_deployment.id.0,
101 ContainerRequest::DeploymentCommand(deployment_command) => deployment_command.id,
102 ContainerRequest::DeploymentUpdate(deployment_update) => deployment_update.from,
103 }
104 }
105}
106
107impl FromEvent for ContainerRequest {
108 type Err = FromEventError;
109
110 fn from_event(value: DeviceEvent) -> Result<Self, Self::Err> {
111 match value.interface.as_str() {
112 "io.edgehog.devicemanager.apps.CreateImageRequest" => {
113 CreateImage::from_event(value).map(ContainerRequest::Image)
114 }
115 "io.edgehog.devicemanager.apps.CreateVolumeRequest" => {
116 CreateVolume::from_event(value).map(ContainerRequest::Volume)
117 }
118 "io.edgehog.devicemanager.apps.CreateNetworkRequest" => {
119 CreateNetwork::from_event(value).map(ContainerRequest::Network)
120 }
121 "io.edgehog.devicemanager.apps.CreateDeviceMappingRequest" => {
122 CreateDeviceMapping::from_event(value).map(ContainerRequest::DeviceMapping)
123 }
124 "io.edgehog.devicemanager.apps.CreateContainerRequest" => {
125 CreateContainer::from_event(value)
126 .map(|value| ContainerRequest::Container(Box::new(value)))
127 }
128 "io.edgehog.devicemanager.apps.CreateDeploymentRequest" => {
129 CreateDeployment::from_event(value).map(ContainerRequest::Deployment)
130 }
131 "io.edgehog.devicemanager.apps.DeploymentCommand" => {
132 DeploymentCommand::from_event(value).map(ContainerRequest::DeploymentCommand)
133 }
134 "io.edgehog.devicemanager.apps.DeploymentUpdate" => {
135 DeploymentUpdate::from_event(value).map(ContainerRequest::DeploymentUpdate)
136 }
137 _ => Err(FromEventError::Interface(value.interface.clone())),
138 }
139 }
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
144pub(crate) struct ReqUuid(pub(crate) Uuid);
145
146impl Display for ReqUuid {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 write!(f, "{}", self.0)
149 }
150}
151
152impl Borrow<Uuid> for ReqUuid {
153 fn borrow(&self) -> &Uuid {
154 &self.0
155 }
156}
157
158impl Deref for ReqUuid {
159 type Target = Uuid;
160
161 fn deref(&self) -> &Self::Target {
162 &self.0
163 }
164}
165
166impl TryFrom<&str> for ReqUuid {
167 type Error = TypeError;
168
169 fn try_from(value: &str) -> Result<Self, Self::Error> {
170 Uuid::parse_str(value).map(ReqUuid).map_err(|err| {
171 error!(
172 error = format!("{:#}", eyre::Report::new(err)),
173 value, "couldn't parse uuid value"
174 );
175
176 TypeError::Conversion {
177 ctx: format!("couldn't parse uuid value: {value}"),
178 }
179 })
180 }
181}
182
183impl TryFrom<AstarteData> for ReqUuid {
184 type Error = TypeError;
185
186 fn try_from(value: AstarteData) -> Result<Self, Self::Error> {
187 let value = String::try_from(value)?;
188
189 Self::try_from(value.as_str())
190 }
191}
192
193impl From<ReqUuid> for Uuid {
194 fn from(value: ReqUuid) -> Self {
195 value.0
196 }
197}
198
199impl From<&ReqUuid> for Uuid {
200 fn from(value: &ReqUuid) -> Self {
201 value.0
202 }
203}
204
205#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
210pub(crate) struct VecReqUuid(pub(crate) Vec<ReqUuid>);
211
212impl Deref for VecReqUuid {
213 type Target = Vec<ReqUuid>;
214
215 fn deref(&self) -> &Self::Target {
216 &self.0
217 }
218}
219
220impl TryFrom<AstarteData> for VecReqUuid {
221 type Error = TypeError;
222
223 fn try_from(value: AstarteData) -> Result<Self, Self::Error> {
224 let value = Vec::<String>::try_from(value)?;
225
226 value
227 .iter()
228 .map(|v| ReqUuid::try_from(v.as_str()))
229 .collect::<Result<Vec<ReqUuid>, TypeError>>()
230 .map(VecReqUuid)
231 }
232}
233
234#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
236pub struct OptString(Option<String>);
237
238impl TryFrom<AstarteData> for OptString {
239 type Error = TypeError;
240
241 fn try_from(value: AstarteData) -> Result<Self, Self::Error> {
242 let value = String::try_from(value)?;
243
244 Ok(Self::from(value))
245 }
246}
247
248impl From<String> for OptString {
249 fn from(value: String) -> Self {
250 if value.is_empty() {
251 OptString(None)
252 } else {
253 OptString(Some(value))
254 }
255 }
256}
257
258impl From<OptString> for Option<String> {
259 fn from(value: OptString) -> Self {
260 value.0
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267
268 use image::tests::create_image_request_event;
269 use network::{tests::create_network_request_event, CreateNetwork};
270 use pretty_assertions::assert_eq;
271
272 use crate::requests::ContainerRequest;
273
274 #[test]
275 fn from_event_image() {
276 let id = Uuid::new_v4();
277 let deployment_id = Uuid::new_v4();
278
279 let event = create_image_request_event(id, deployment_id, "reference", "registry_auth");
280
281 let request = ContainerRequest::from_event(event).unwrap();
282
283 let expect = ContainerRequest::Image(CreateImage {
284 id: ReqUuid(id),
285 deployment_id: ReqUuid(deployment_id),
286 reference: "reference".to_string(),
287 registry_auth: "registry_auth".to_string(),
288 });
289
290 assert_eq!(request, expect);
291 }
292
293 #[test]
294 fn from_event_network() {
295 let id = Uuid::new_v4();
296 let deployment_id = Uuid::new_v4();
297 let event = create_network_request_event(id, deployment_id, "driver", &[]);
298
299 let request = ContainerRequest::from_event(event).unwrap();
300
301 let expect = CreateNetwork {
302 id: ReqUuid(id),
303 deployment_id: ReqUuid(deployment_id),
304 driver: "driver".to_string(),
305 internal: false,
306 enable_ipv6: false,
307 options: Vec::new(),
308 };
309
310 assert_eq!(request, ContainerRequest::Network(expect));
311 }
312
313 #[test]
314 fn optional_string() {
315 let cases = [
316 ("", OptString(None)),
317 ("some", OptString(Some("some".to_string()))),
318 ];
319
320 for (case, exp) in cases {
321 let res = OptString::try_from(AstarteData::from(case)).unwrap();
322
323 assert_eq!(res, exp);
324 }
325 }
326}