1use snafu::{ensure, AsErrorSource, ResultExt, Snafu};
11use std::{
12 convert::TryFrom,
13 net::{SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs},
14 str::FromStr,
15};
16
17#[derive(Debug, Clone, Eq, Hash, PartialEq)]
49pub struct FullAeAddr<T> {
50 ae_title: String,
51 socket_addr: T,
52}
53
54impl<T> FullAeAddr<T> {
55 pub fn new(ae_title: impl Into<String>, socket_addr: T) -> Self {
57 FullAeAddr {
58 ae_title: ae_title.into(),
59 socket_addr,
60 }
61 }
62
63 pub fn ae_title(&self) -> &str {
65 &self.ae_title
66 }
67
68 pub fn socket_addr(&self) -> &T {
70 &self.socket_addr
71 }
72
73 pub fn into_parts(self) -> (String, T) {
75 (self.ae_title, self.socket_addr)
76 }
77}
78
79impl<T> From<(String, T)> for FullAeAddr<T> {
80 fn from((ae_title, socket_addr): (String, T)) -> Self {
81 Self::new(ae_title, socket_addr)
82 }
83}
84
85#[derive(Debug, Clone, Eq, PartialEq, Snafu)]
87pub enum ParseAeAddressError<E>
88where
89 E: std::fmt::Debug + AsErrorSource,
90{
91 MissingPart,
93
94 ParseSocketAddress { source: E },
96}
97
98impl<T> FromStr for FullAeAddr<T>
99where
100 T: FromStr,
101 T::Err: std::fmt::Debug + AsErrorSource,
102{
103 type Err = ParseAeAddressError<<T as FromStr>::Err>;
104
105 fn from_str(s: &str) -> Result<Self, Self::Err> {
106 if let Some((ae_title, addr)) = s.split_once('@') {
108 ensure!(!ae_title.is_empty(), MissingPartSnafu);
109 Ok(FullAeAddr {
110 ae_title: ae_title.to_string(),
111 socket_addr: addr.parse().context(ParseSocketAddressSnafu)?,
112 })
113 } else {
114 Err(ParseAeAddressError::MissingPart)
115 }
116 }
117}
118
119impl<T> ToSocketAddrs for FullAeAddr<T>
120where
121 T: ToSocketAddrs,
122{
123 type Iter = T::Iter;
124
125 fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
126 self.socket_addr.to_socket_addrs()
127 }
128}
129
130impl<T> std::fmt::Display for FullAeAddr<T>
131where
132 T: std::fmt::Display,
133{
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 f.write_str(&self.ae_title.replace('@', "\\@"))?;
136 f.write_str("@")?;
137 std::fmt::Display::fmt(&self.socket_addr, f)
138 }
139}
140
141#[derive(Debug, Clone, Eq, Hash, PartialEq)]
176pub struct AeAddr<T> {
177 ae_title: Option<String>,
178 socket_addr: T,
179}
180
181impl<T> AeAddr<T> {
182 pub fn new(ae_title: impl Into<String>, socket_addr: T) -> Self {
184 AeAddr {
185 ae_title: Some(ae_title.into()),
186 socket_addr,
187 }
188 }
189
190 pub fn new_socket_addr(socket_addr: T) -> Self {
192 AeAddr {
193 ae_title: None,
194 socket_addr,
195 }
196 }
197
198 pub fn ae_title(&self) -> Option<&str> {
200 self.ae_title.as_deref()
201 }
202
203 pub fn socket_addr(&self) -> &T {
205 &self.socket_addr
206 }
207
208 pub fn with_ae_title(self, ae_title: impl Into<String>) -> FullAeAddr<T> {
211 FullAeAddr {
212 ae_title: ae_title.into(),
213 socket_addr: self.socket_addr,
214 }
215 }
216
217 pub fn with_default_ae_title(self, ae_title: impl Into<String>) -> FullAeAddr<T> {
220 FullAeAddr {
221 ae_title: self.ae_title.unwrap_or_else(|| ae_title.into()),
222 socket_addr: self.socket_addr,
223 }
224 }
225
226 pub fn into_parts(self) -> (Option<String>, T) {
228 (self.ae_title, self.socket_addr)
229 }
230}
231
232impl From<SocketAddr> for AeAddr<SocketAddr> {
234 fn from(socket_addr: SocketAddr) -> Self {
235 AeAddr {
236 ae_title: None,
237 socket_addr,
238 }
239 }
240}
241
242impl From<SocketAddrV4> for AeAddr<SocketAddrV4> {
244 fn from(socket_addr: SocketAddrV4) -> Self {
245 AeAddr {
246 ae_title: None,
247 socket_addr,
248 }
249 }
250}
251
252impl From<SocketAddrV6> for AeAddr<SocketAddrV6> {
254 fn from(socket_addr: SocketAddrV6) -> Self {
255 AeAddr {
256 ae_title: None,
257 socket_addr,
258 }
259 }
260}
261
262impl<T> From<FullAeAddr<T>> for AeAddr<T> {
263 fn from(full: FullAeAddr<T>) -> Self {
264 AeAddr {
265 ae_title: Some(full.ae_title),
266 socket_addr: full.socket_addr,
267 }
268 }
269}
270
271impl<T> FromStr for AeAddr<T>
272where
273 T: FromStr,
274{
275 type Err = <T as FromStr>::Err;
276
277 fn from_str(s: &str) -> Result<Self, Self::Err> {
278 if let Some((ae_title, address)) = s.split_once('@') {
280 Ok(AeAddr {
281 ae_title: Some(ae_title)
282 .filter(|s| !s.is_empty())
283 .map(|s| s.to_string()),
284 socket_addr: address.parse()?,
285 })
286 } else {
287 Ok(AeAddr {
288 ae_title: None,
289 socket_addr: s.parse()?,
290 })
291 }
292 }
293}
294
295#[allow(unknown_lints)]
296#[allow(clippy::infallible_try_from)]
297impl<'a> TryFrom<&'a str> for AeAddr<String> {
298 type Error = <AeAddr<String> as FromStr>::Err;
299
300 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
301 s.parse()
302 }
303}
304
305impl<T> ToSocketAddrs for AeAddr<T>
306where
307 T: ToSocketAddrs,
308{
309 type Iter = T::Iter;
310
311 fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
312 self.socket_addr.to_socket_addrs()
313 }
314}
315
316impl<T> std::fmt::Display for AeAddr<T>
317where
318 T: std::fmt::Display,
319{
320 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
321 let socket_addr = self.socket_addr.to_string();
322 if let Some(ae_title) = &self.ae_title {
323 f.write_str(&ae_title.replace('@', "\\@"))?;
324 f.write_str("@")?;
325 } else if socket_addr.contains('@') {
326 f.write_str("@")?;
331 }
332
333 std::fmt::Display::fmt(&socket_addr, f)
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 #[test]
342 fn ae_addr_parse() {
343 let addr: FullAeAddr<String> = "SCP-STORAGE@127.0.0.1:104".parse().unwrap();
345 assert_eq!(addr.ae_title(), "SCP-STORAGE");
346 assert_eq!(addr.socket_addr(), "127.0.0.1:104");
347
348 let addr: FullAeAddr<SocketAddr> = "SCP_STORAGE@127.0.0.1:104".parse().unwrap();
350 assert_eq!(addr.ae_title(), "SCP_STORAGE");
351 assert_eq!(addr.socket_addr(), &SocketAddr::from(([127, 0, 0, 1], 104)));
352 assert_eq!(&addr.to_string(), "SCP_STORAGE@127.0.0.1:104");
353
354 let addr: FullAeAddr<SocketAddrV4> = "MAMMOSTORE@10.0.0.11:104".parse().unwrap();
356 assert_eq!(addr.ae_title(), "MAMMOSTORE");
357 assert_eq!(
358 addr.socket_addr(),
359 &SocketAddrV4::new([10, 0, 0, 11].into(), 104)
360 );
361 assert_eq!(&addr.to_string(), "MAMMOSTORE@10.0.0.11:104");
362 }
363
364 #[test]
366 fn ae_addr_parse_no_ae() {
367 let res = FullAeAddr::<String>::from_str("pacs.hospital.example.com:104");
369 assert!(matches!(res, Err(ParseAeAddressError::MissingPart)));
370 let res = FullAeAddr::<String>::from_str("@pacs.hospital.example.com:104");
372 assert!(matches!(res, Err(ParseAeAddressError::MissingPart)));
373
374 let addr: AeAddr<String> = "pacs.hospital.example.com:104".parse().unwrap();
376 assert_eq!(addr.ae_title(), None);
377 assert_eq!(addr.socket_addr(), "pacs.hospital.example.com:104");
378 let addr: AeAddr<String> = "@pacs.hospital.example.com:104".parse().unwrap();
380 assert_eq!(addr.ae_title(), None);
381 assert_eq!(addr.socket_addr(), "pacs.hospital.example.com:104");
382 }
383
384 #[test]
385 fn ae_addr_parse_weird_scenarios() {
386 let addr: FullAeAddr<String> = "ABC@DICOM@pacs.archive.example.com:104".parse().unwrap();
388 assert_eq!(addr.ae_title(), "ABC");
389 assert_eq!(addr.socket_addr(), "DICOM@pacs.archive.example.com:104");
390 assert_eq!(&addr.to_string(), "ABC@DICOM@pacs.archive.example.com:104");
391 }
392}