northstar_runtime/common/
container.rs1use 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#[derive(Clone, Eq, PartialOrd, Ord, PartialEq, Debug, Hash)]
14pub struct Container {
15 inner: Arc<Inner>,
16}
17
18impl Container {
19 pub fn new(name: Name, version: Version) -> Container {
21 Container {
22 inner: Arc::new(Inner { name, version }),
23 }
24 }
25
26 pub fn name(&self) -> &Name {
28 &self.inner.name
29 }
30
31 pub fn version(&self) -> &Version {
33 &self.inner.version
34 }
35}
36
37#[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}