use snafu::{ensure, AsErrorSource, ResultExt, Snafu};
use std::{
convert::TryFrom,
net::{SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs},
str::FromStr,
};
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub struct FullAeAddr<T> {
ae_title: String,
socket_addr: T,
}
impl<T> FullAeAddr<T> {
pub fn new(ae_title: impl Into<String>, socket_addr: T) -> Self {
FullAeAddr {
ae_title: ae_title.into(),
socket_addr,
}
}
pub fn ae_title(&self) -> &str {
&self.ae_title
}
pub fn socket_addr(&self) -> &T {
&self.socket_addr
}
pub fn into_parts(self) -> (String, T) {
(self.ae_title, self.socket_addr)
}
}
impl<T> From<(String, T)> for FullAeAddr<T> {
fn from((ae_title, socket_addr): (String, T)) -> Self {
Self::new(ae_title, socket_addr)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Snafu)]
pub enum ParseAeAddressError<E>
where
E: std::fmt::Debug + AsErrorSource,
{
MissingPart,
ParseSocketAddress { source: E },
}
impl<T> FromStr for FullAeAddr<T>
where
T: FromStr,
T::Err: std::fmt::Debug + AsErrorSource,
{
type Err = ParseAeAddressError<<T as FromStr>::Err>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some((ae_title, addr)) = s.split_once('@') {
ensure!(!ae_title.is_empty(), MissingPartSnafu);
Ok(FullAeAddr {
ae_title: ae_title.to_string(),
socket_addr: addr.parse().context(ParseSocketAddressSnafu)?,
})
} else {
Err(ParseAeAddressError::MissingPart)
}
}
}
impl<T> ToSocketAddrs for FullAeAddr<T>
where
T: ToSocketAddrs,
{
type Iter = T::Iter;
fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
self.socket_addr.to_socket_addrs()
}
}
impl<T> std::fmt::Display for FullAeAddr<T>
where
T: std::fmt::Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.ae_title.replace('@', "\\@"))?;
f.write_str("@")?;
std::fmt::Display::fmt(&self.socket_addr, f)
}
}
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub struct AeAddr<T> {
ae_title: Option<String>,
socket_addr: T,
}
impl<T> AeAddr<T> {
pub fn new(ae_title: impl Into<String>, socket_addr: T) -> Self {
AeAddr {
ae_title: Some(ae_title.into()),
socket_addr,
}
}
pub fn new_socket_addr(socket_addr: T) -> Self {
AeAddr {
ae_title: None,
socket_addr,
}
}
pub fn ae_title(&self) -> Option<&str> {
self.ae_title.as_deref()
}
pub fn socket_addr(&self) -> &T {
&self.socket_addr
}
pub fn with_ae_title(self, ae_title: impl Into<String>) -> FullAeAddr<T> {
FullAeAddr {
ae_title: ae_title.into(),
socket_addr: self.socket_addr,
}
}
pub fn with_default_ae_title(self, ae_title: impl Into<String>) -> FullAeAddr<T> {
FullAeAddr {
ae_title: self.ae_title.unwrap_or_else(|| ae_title.into()),
socket_addr: self.socket_addr,
}
}
pub fn into_parts(self) -> (Option<String>, T) {
(self.ae_title, self.socket_addr)
}
}
impl From<SocketAddr> for AeAddr<SocketAddr> {
fn from(socket_addr: SocketAddr) -> Self {
AeAddr {
ae_title: None,
socket_addr,
}
}
}
impl From<SocketAddrV4> for AeAddr<SocketAddrV4> {
fn from(socket_addr: SocketAddrV4) -> Self {
AeAddr {
ae_title: None,
socket_addr,
}
}
}
impl From<SocketAddrV6> for AeAddr<SocketAddrV6> {
fn from(socket_addr: SocketAddrV6) -> Self {
AeAddr {
ae_title: None,
socket_addr,
}
}
}
impl<T> From<FullAeAddr<T>> for AeAddr<T> {
fn from(full: FullAeAddr<T>) -> Self {
AeAddr {
ae_title: Some(full.ae_title),
socket_addr: full.socket_addr,
}
}
}
impl<T> FromStr for AeAddr<T>
where
T: FromStr,
{
type Err = <T as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some((ae_title, address)) = s.split_once('@') {
Ok(AeAddr {
ae_title: Some(ae_title)
.filter(|s| !s.is_empty())
.map(|s| s.to_string()),
socket_addr: address.parse()?,
})
} else {
Ok(AeAddr {
ae_title: None,
socket_addr: s.parse()?,
})
}
}
}
#[allow(unknown_lints)]
#[allow(clippy::infallible_try_from)]
impl<'a> TryFrom<&'a str> for AeAddr<String> {
type Error = <AeAddr<String> as FromStr>::Err;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
s.parse()
}
}
impl<T> ToSocketAddrs for AeAddr<T>
where
T: ToSocketAddrs,
{
type Iter = T::Iter;
fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
self.socket_addr.to_socket_addrs()
}
}
impl<T> std::fmt::Display for AeAddr<T>
where
T: std::fmt::Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let socket_addr = self.socket_addr.to_string();
if let Some(ae_title) = &self.ae_title {
f.write_str(&ae_title.replace('@', "\\@"))?;
f.write_str("@")?;
} else if socket_addr.contains('@') {
f.write_str("@")?;
}
std::fmt::Display::fmt(&socket_addr, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ae_addr_parse() {
let addr: FullAeAddr<String> = "SCP-STORAGE@127.0.0.1:104".parse().unwrap();
assert_eq!(addr.ae_title(), "SCP-STORAGE");
assert_eq!(addr.socket_addr(), "127.0.0.1:104");
let addr: FullAeAddr<SocketAddr> = "SCP_STORAGE@127.0.0.1:104".parse().unwrap();
assert_eq!(addr.ae_title(), "SCP_STORAGE");
assert_eq!(addr.socket_addr(), &SocketAddr::from(([127, 0, 0, 1], 104)));
assert_eq!(&addr.to_string(), "SCP_STORAGE@127.0.0.1:104");
let addr: FullAeAddr<SocketAddrV4> = "MAMMOSTORE@10.0.0.11:104".parse().unwrap();
assert_eq!(addr.ae_title(), "MAMMOSTORE");
assert_eq!(
addr.socket_addr(),
&SocketAddrV4::new([10, 0, 0, 11].into(), 104)
);
assert_eq!(&addr.to_string(), "MAMMOSTORE@10.0.0.11:104");
}
#[test]
fn ae_addr_parse_no_ae() {
let res = FullAeAddr::<String>::from_str("pacs.hospital.example.com:104");
assert!(matches!(res, Err(ParseAeAddressError::MissingPart)));
let res = FullAeAddr::<String>::from_str("@pacs.hospital.example.com:104");
assert!(matches!(res, Err(ParseAeAddressError::MissingPart)));
let addr: AeAddr<String> = "pacs.hospital.example.com:104".parse().unwrap();
assert_eq!(addr.ae_title(), None);
assert_eq!(addr.socket_addr(), "pacs.hospital.example.com:104");
let addr: AeAddr<String> = "@pacs.hospital.example.com:104".parse().unwrap();
assert_eq!(addr.ae_title(), None);
assert_eq!(addr.socket_addr(), "pacs.hospital.example.com:104");
}
#[test]
fn ae_addr_parse_weird_scenarios() {
let addr: FullAeAddr<String> = "ABC@DICOM@pacs.archive.example.com:104".parse().unwrap();
assert_eq!(addr.ae_title(), "ABC");
assert_eq!(addr.socket_addr(), "DICOM@pacs.archive.example.com:104");
assert_eq!(&addr.to_string(), "ABC@DICOM@pacs.archive.example.com:104");
}
}