1use std::convert::TryFrom;
2use std::io::Cursor;
3use std::net::{AddrParseError, Ipv4Addr, SocketAddr};
4use std::path::PathBuf;
5
6use serde::{Deserialize, Serialize};
7
8#[derive(Serialize, Deserialize, Debug)]
9#[serde(rename_all = "camelCase")]
10pub struct DeployResult {
11 pub valid: Result<String, String>,
12 #[serde(default)]
13 pub vols: Vec<ContainerVolume>,
14 #[serde(default)]
15 pub start_mode: StartMode,
16}
17
18#[derive(Serialize, Deserialize, Debug, Clone)]
19#[serde(rename_all = "camelCase")]
20pub struct ContainerVolume {
21 pub name: String,
22 pub path: String,
23}
24
25#[non_exhaustive]
26#[derive(Serialize, Deserialize, Debug, Clone)]
27pub enum ContainerEndpoint {
28 UnixStream(PathBuf),
29 UnixDatagram(PathBuf),
30 UdpDatagram(SocketAddr),
31 TcpListener(SocketAddr),
32 TcpStream(SocketAddr),
33}
34
35impl std::fmt::Display for ContainerEndpoint {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 match self {
38 Self::UnixStream(path) | Self::UnixDatagram(path) => write!(f, "{}", path.display()),
39 Self::UdpDatagram(addr) | Self::TcpListener(addr) | Self::TcpStream(addr) => {
40 write!(f, "{}", addr)
41 }
42 }
43 }
44}
45
46impl From<ContainerEndpoint> for PathBuf {
47 fn from(e: ContainerEndpoint) -> Self {
48 match e {
49 ContainerEndpoint::UnixStream(p) | ContainerEndpoint::UnixDatagram(p) => p,
50 ContainerEndpoint::UdpDatagram(a) => PathBuf::from(format!("udp://{}", a)),
51 ContainerEndpoint::TcpListener(a) => PathBuf::from(format!("tcp-connect://{}", a)),
52 ContainerEndpoint::TcpStream(a) => PathBuf::from(format!("tcp-listen://{}", a)),
53 }
54 }
55}
56
57impl<'a> TryFrom<&'a crate::server::NetworkEndpoint> for ContainerEndpoint {
58 type Error = String;
59
60 fn try_from(endpoint: &'a crate::server::NetworkEndpoint) -> Result<Self, Self::Error> {
61 match endpoint {
62 crate::server::NetworkEndpoint::UnixStream(s) => Ok(Self::UnixStream(PathBuf::from(s))),
63 crate::server::NetworkEndpoint::UnixDatagram(s) => {
64 Ok(Self::UnixDatagram(PathBuf::from(s)))
65 }
66 crate::server::NetworkEndpoint::UdpDatagram(s) => {
67 Ok(Self::UdpDatagram(to_socket_addr(s)?))
68 }
69 crate::server::NetworkEndpoint::TcpListener(s) => {
70 Ok(Self::TcpListener(to_socket_addr(s)?))
71 }
72 crate::server::NetworkEndpoint::TcpStream(s) => Ok(Self::TcpStream(to_socket_addr(s)?)),
73 }
74 }
75}
76
77impl TryFrom<url::Url> for ContainerEndpoint {
78 type Error = String;
79
80 fn try_from(url: url::Url) -> Result<Self, Self::Error> {
81 match url.scheme() {
82 "unix" => {
83 let url = url.to_string().replacen("unix://", "", 1);
84 Ok(Self::UnixStream(PathBuf::from(url)))
85 }
86 "udp" => {
87 let url = url.to_string().replacen("udp://", "", 1);
88 let addr: SocketAddr = url.parse().map_err(|e: AddrParseError| e.to_string())?;
89 Ok(Self::UdpDatagram(addr))
90 }
91 "tcp-connect" => {
92 let url = url.to_string().replacen("tcp-connect://", "", 1);
93 let addr: SocketAddr = url.parse().map_err(|e: AddrParseError| e.to_string())?;
94 Ok(Self::TcpStream(addr))
95 }
96 "tcp-listen" => Ok(Self::TcpListener(SocketAddr::new(
97 Ipv4Addr::new(127, 0, 0, 1).into(),
98 0,
99 ))),
100 scheme => Err(format!("Unknown scheme: {scheme}")),
101 }
102 }
103}
104
105fn to_socket_addr(s: &str) -> Result<SocketAddr, String> {
106 s.parse().map_err(|e: AddrParseError| e.to_string())
107}
108
109#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
110#[serde(rename_all = "camelCase")]
111pub enum StartMode {
112 Empty,
113 Blocking,
114}
115
116impl Default for StartMode {
117 fn default() -> Self {
118 StartMode::Empty
119 }
120}
121
122impl DeployResult {
123 pub fn from_bytes(bytes: impl AsRef<[u8]>) -> anyhow::Result<DeployResult> {
124 let b: &[u8] = bytes.as_ref();
125 if b.is_empty() {
126 log::warn!("empty descriptor");
127 let vols = if cfg!(feature = "compat-deployment") {
128 vec![ContainerVolume {
129 name: ".".to_string(),
130 path: "".to_string(),
131 }]
132 } else {
133 Default::default()
134 };
135
136 return Ok(DeployResult {
137 valid: Ok(Default::default()),
138 vols,
139 start_mode: Default::default(),
140 });
141 }
142 if let Some(idx) = b.iter().position(|&ch| ch == b'{') {
143 let b = &b[idx..];
144 Ok(serde_json::from_reader(Cursor::new(b))?)
145 } else {
146 let text = String::from_utf8_lossy(b);
147 anyhow::bail!("invalid deploy response: {}", text);
148 }
149 }
150}
151
152#[cfg(test)]
153mod test {
154 use super::DeployResult;
155
156 fn parse_bytes<T: AsRef<[u8]>>(b: T) -> DeployResult {
157 let result = DeployResult::from_bytes(b).unwrap();
158 eprintln!("result={:?}", result);
159 result
160 }
161
162 #[test]
163 fn test_wasi_deploy() {
164 parse_bytes(
165 r#"{
166 "valid": {"Ok": "success"}
167 }"#,
168 );
169 parse_bytes(
170 r#"{
171 "valid": {"Err": "bad image format"}
172 }"#,
173 );
174 parse_bytes(
175 r#"{
176 "valid": {"Ok": "success"},
177 "vols": [
178 {"name": "vol-9a0c1c4a", "path": "/in"},
179 {"name": "vol-a68672e0", "path": "/out"}
180 ]
181 }"#,
182 );
183 }
184}