northstar_runtime/common/
container.rs

1use anyhow::{anyhow, Context, Result};
2use serde::{Deserialize, Serialize};
3use std::{
4    convert::{TryFrom, TryInto},
5    fmt::{self, Display},
6    sync::Arc,
7};
8use thiserror::Error;
9
10use crate::common::{name::Name, version::Version};
11
12/// Container identification
13#[derive(Clone, Eq, PartialOrd, Ord, PartialEq, Debug, Hash)]
14pub struct Container {
15    inner: Arc<Inner>,
16}
17
18impl Container {
19    /// Construct a new container
20    pub fn new(name: Name, version: Version) -> Container {
21        Container {
22            inner: Arc::new(Inner { name, version }),
23        }
24    }
25
26    /// Container name
27    pub fn name(&self) -> &Name {
28        &self.inner.name
29    }
30
31    /// Container version
32    pub fn version(&self) -> &Version {
33        &self.inner.version
34    }
35}
36
37/// Parsing error for container identifier
38#[derive(Error, Debug)]
39#[error(transparent)]
40pub struct InvalidContainerError(#[from] anyhow::Error);
41
42impl Display for Container {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        write!(f, "{}:{}", self.inner.name, self.inner.version,)
45    }
46}
47
48impl TryFrom<&str> for Container {
49    type Error = InvalidContainerError;
50
51    fn try_from(value: &str) -> Result<Self, Self::Error> {
52        let (name, version) = value
53            .split_once(':')
54            .ok_or_else(|| anyhow!("missing container version"))?;
55        let name = Name::try_from(name).context("invalid name")?;
56        let version = Version::parse(version).context("invalid container version")?;
57        Ok(Container::new(name, version))
58    }
59}
60
61impl<N, V> TryFrom<(N, V)> for Container
62where
63    N: TryInto<Name>,
64    N::Error: Into<anyhow::Error>,
65    V: TryInto<Version>,
66    V::Error: Into<anyhow::Error>,
67{
68    type Error = InvalidContainerError;
69
70    fn try_from((name, version): (N, V)) -> Result<Self, Self::Error> {
71        let name = name
72            .try_into()
73            .map_err(Into::into)
74            .context("invalid name")?;
75        let version = version
76            .try_into()
77            .map_err(Into::into)
78            .context("invalid version")?;
79        Ok(Container::new(name, version))
80    }
81}
82
83impl Serialize for Container {
84    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
85    where
86        S: serde::Serializer,
87    {
88        serializer.serialize_str(&format!("{}:{}", self.inner.name, self.inner.version))
89    }
90}
91
92impl<'de> Deserialize<'de> for Container {
93    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
94    where
95        D: serde::Deserializer<'de>,
96    {
97        let value = String::deserialize(deserializer)?;
98        Container::try_from(value.as_str()).map_err(serde::de::Error::custom)
99    }
100}
101
102#[derive(Eq, PartialOrd, PartialEq, Ord, Debug, Hash, Serialize, Deserialize)]
103struct Inner {
104    name: Name,
105    version: Version,
106}
107
108#[test]
109#[allow(clippy::unwrap_used)]
110fn try_from() {
111    assert_eq!(
112        Container::new("test".try_into().unwrap(), Version::parse("0.0.1").unwrap()),
113        "test:0.0.1".try_into().unwrap()
114    );
115}
116
117#[test]
118fn invalid_name() {
119    assert!(Container::try_from("test\0:0.0.1").is_err());
120    assert!(Container::try_from("tes%t:0.0.1").is_err());
121}