gel_stream/common/
target.rs

1use std::{
2    borrow::Cow,
3    hash::{Hash, Hasher},
4    net::{IpAddr, Ipv4Addr, SocketAddr},
5    path::Path,
6    sync::Arc,
7};
8
9use derive_more::Debug;
10use rustls_pki_types::ServerName;
11
12use crate::TlsParameters;
13
14#[derive(Clone)]
15/// A target name describes the TCP or Unix socket that a client will connect to.
16pub struct TargetName {
17    inner: MaybeResolvedTarget,
18}
19
20impl std::fmt::Debug for TargetName {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        write!(f, "{:?}", self.inner)
23    }
24}
25
26impl TargetName {
27    /// Create a new target for a Unix socket.
28    pub fn new_unix_path(path: impl AsRef<Path>) -> Result<Self, std::io::Error> {
29        #[cfg(unix)]
30        {
31            let path = ResolvedTarget::from(std::os::unix::net::SocketAddr::from_pathname(path)?);
32            Ok(Self {
33                inner: MaybeResolvedTarget::Resolved(path),
34            })
35        }
36        #[cfg(not(unix))]
37        {
38            Err(std::io::Error::new(
39                std::io::ErrorKind::Unsupported,
40                "Unix sockets are not supported on this platform",
41            ))
42        }
43    }
44
45    /// Create a new target for a Unix socket.
46    pub fn new_unix_domain(domain: impl AsRef<[u8]>) -> Result<Self, std::io::Error> {
47        #[cfg(any(target_os = "linux", target_os = "android"))]
48        {
49            use std::os::linux::net::SocketAddrExt;
50            let domain =
51                ResolvedTarget::from(std::os::unix::net::SocketAddr::from_abstract_name(domain)?);
52            Ok(Self {
53                inner: MaybeResolvedTarget::Resolved(domain),
54            })
55        }
56        #[cfg(not(any(target_os = "linux", target_os = "android")))]
57        {
58            Err(std::io::Error::new(
59                std::io::ErrorKind::Unsupported,
60                "Unix domain sockets are not supported on this platform",
61            ))
62        }
63    }
64
65    /// Create a new target for a TCP socket.
66    #[allow(private_bounds)]
67    pub fn new_tcp(host: impl TcpResolve) -> Self {
68        Self { inner: host.into() }
69    }
70
71    /// Resolves the target addresses for a given host.
72    pub fn to_addrs_sync(&self) -> Result<Vec<ResolvedTarget>, std::io::Error> {
73        use std::net::ToSocketAddrs;
74        let mut result = Vec::new();
75        match &self.inner {
76            MaybeResolvedTarget::Resolved(addr) => {
77                return Ok(vec![addr.clone()]);
78            }
79            MaybeResolvedTarget::Unresolved(host, port, _interface) => {
80                let addrs = format!("{}:{}", host, port).to_socket_addrs()?;
81                result.extend(addrs.map(ResolvedTarget::SocketAddr));
82            }
83        }
84        Ok(result)
85    }
86
87    pub(crate) fn maybe_resolved(&self) -> &MaybeResolvedTarget {
88        &self.inner
89    }
90
91    pub(crate) fn maybe_resolved_mut(&mut self) -> &mut MaybeResolvedTarget {
92        &mut self.inner
93    }
94
95    /// Check if the target is a TCP connection.
96    pub fn is_tcp(&self) -> bool {
97        self.maybe_resolved().port().is_some()
98    }
99
100    /// Get the port of the target. If the target type does not include a port,
101    /// this will return None.
102    pub fn port(&self) -> Option<u16> {
103        self.maybe_resolved().port()
104    }
105
106    /// Set the port of the target. If the target type does not include a port,
107    /// this will return None. Otherwise, it will return the old port.
108    pub fn try_set_port(&mut self, port: u16) -> Option<u16> {
109        self.maybe_resolved_mut().set_port(port)
110    }
111
112    /// Get the path of the target. If the target type does not include a path,
113    /// this will return None.
114    pub fn path(&self) -> Option<&Path> {
115        self.maybe_resolved().path()
116    }
117
118    /// Get the host of the target. For resolved IP addresses, this is the
119    /// string representation of the IP address. For unresolved hostnames, this
120    /// is the hostname. If the target type does not include a host, this will
121    /// return None.
122    pub fn host(&self) -> Option<Cow<str>> {
123        self.maybe_resolved().host()
124    }
125
126    /// Get the name of the target. For resolved IP addresses, this is the
127    /// string representation of the IP address. For unresolved hostnames, this
128    /// is the hostname.
129    pub fn name(&self) -> Option<ServerName> {
130        self.maybe_resolved().name()
131    }
132
133    /// Get the host and port of the target. If the target type does not include
134    /// a host or port, this will return None.
135    pub fn tcp(&self) -> Option<(Cow<str>, u16)> {
136        self.maybe_resolved().tcp()
137    }
138}
139
140/// A target describes the TCP or Unix socket that a client will connect to,
141/// along with any optional TLS parameters.
142#[derive(Clone)]
143pub struct Target {
144    inner: TargetInner,
145}
146
147impl std::fmt::Debug for Target {
148    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149        match &self.inner {
150            TargetInner::NoTls(target) => write!(f, "{:?}", target),
151            TargetInner::Tls(target, _) => write!(f, "{:?} (TLS)", target),
152            TargetInner::StartTls(target, _) => write!(f, "{:?} (STARTTLS)", target),
153        }
154    }
155}
156
157#[allow(private_bounds)]
158impl Target {
159    pub fn new(name: TargetName) -> Self {
160        Self {
161            inner: TargetInner::NoTls(name.inner),
162        }
163    }
164
165    pub fn new_tls(name: TargetName, params: TlsParameters) -> Self {
166        Self {
167            inner: TargetInner::Tls(name.inner, params.into()),
168        }
169    }
170
171    pub fn new_starttls(name: TargetName, params: TlsParameters) -> Self {
172        Self {
173            inner: TargetInner::StartTls(name.inner, params.into()),
174        }
175    }
176
177    pub fn new_resolved(target: ResolvedTarget) -> Self {
178        Self {
179            inner: TargetInner::NoTls(target.into()),
180        }
181    }
182
183    pub fn new_resolved_tls(target: ResolvedTarget, params: TlsParameters) -> Self {
184        Self {
185            inner: TargetInner::Tls(target.into(), params.into()),
186        }
187    }
188
189    pub fn new_resolved_starttls(target: ResolvedTarget, params: TlsParameters) -> Self {
190        Self {
191            inner: TargetInner::StartTls(target.into(), params.into()),
192        }
193    }
194
195    /// Create a new target for a Unix socket.
196    pub fn new_unix_path(path: impl AsRef<Path>) -> Result<Self, std::io::Error> {
197        #[cfg(unix)]
198        {
199            let path = ResolvedTarget::from(std::os::unix::net::SocketAddr::from_pathname(path)?);
200            Ok(Self {
201                inner: TargetInner::NoTls(path.into()),
202            })
203        }
204        #[cfg(not(unix))]
205        {
206            Err(std::io::Error::new(
207                std::io::ErrorKind::Unsupported,
208                "Unix sockets are not supported on this platform",
209            ))
210        }
211    }
212
213    /// Create a new target for a Unix socket.
214    pub fn new_unix_domain(domain: impl AsRef<[u8]>) -> Result<Self, std::io::Error> {
215        #[cfg(any(target_os = "linux", target_os = "android"))]
216        {
217            use std::os::linux::net::SocketAddrExt;
218            let domain =
219                ResolvedTarget::from(std::os::unix::net::SocketAddr::from_abstract_name(domain)?);
220            Ok(Self {
221                inner: TargetInner::NoTls(domain.into()),
222            })
223        }
224        #[cfg(not(any(target_os = "linux", target_os = "android")))]
225        {
226            Err(std::io::Error::new(
227                std::io::ErrorKind::Unsupported,
228                "Unix domain sockets are not supported on this platform",
229            ))
230        }
231    }
232
233    /// Create a new target for a TCP socket.
234    pub fn new_tcp(host: impl TcpResolve) -> Self {
235        Self {
236            inner: TargetInner::NoTls(host.into()),
237        }
238    }
239
240    /// Create a new target for a TCP socket with TLS.
241    pub fn new_tcp_tls(host: impl TcpResolve, params: TlsParameters) -> Self {
242        Self {
243            inner: TargetInner::Tls(host.into(), params.into()),
244        }
245    }
246
247    /// Create a new target for a TCP socket with STARTTLS.
248    pub fn new_tcp_starttls(host: impl TcpResolve, params: TlsParameters) -> Self {
249        Self {
250            inner: TargetInner::StartTls(host.into(), params.into()),
251        }
252    }
253
254    pub fn try_set_tls(&mut self, params: TlsParameters) -> Option<Option<Arc<TlsParameters>>> {
255        // Don't set TLS parameters on Unix sockets.
256        if self.maybe_resolved().path().is_some() {
257            return None;
258        }
259
260        let params = params.into();
261
262        // Temporary
263        let no_target = TargetInner::NoTls(MaybeResolvedTarget::Resolved(
264            ResolvedTarget::SocketAddr(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)),
265        ));
266
267        match std::mem::replace(&mut self.inner, no_target) {
268            TargetInner::NoTls(target) => {
269                self.inner = TargetInner::Tls(target, params);
270                Some(None)
271            }
272            TargetInner::Tls(target, old_params) => {
273                self.inner = TargetInner::Tls(target, params);
274                Some(Some(old_params))
275            }
276            TargetInner::StartTls(target, old_params) => {
277                self.inner = TargetInner::StartTls(target, params);
278                Some(Some(old_params))
279            }
280        }
281    }
282
283    pub fn try_remove_tls(&mut self) -> Option<Arc<TlsParameters>> {
284        // Temporary
285        let no_target = TargetInner::NoTls(MaybeResolvedTarget::Resolved(
286            ResolvedTarget::SocketAddr(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)),
287        ));
288
289        match std::mem::replace(&mut self.inner, no_target) {
290            TargetInner::NoTls(target) => {
291                self.inner = TargetInner::NoTls(target);
292                None
293            }
294            TargetInner::Tls(target, old_params) => {
295                self.inner = TargetInner::NoTls(target);
296                Some(old_params)
297            }
298            TargetInner::StartTls(target, old_params) => {
299                self.inner = TargetInner::NoTls(target);
300                Some(old_params)
301            }
302        }
303    }
304
305    /// Check if the target is a TCP connection.
306    pub fn is_tcp(&self) -> bool {
307        self.maybe_resolved().port().is_some()
308    }
309
310    /// Get the port of the target. If the target type does not include a port,
311    /// this will return None.
312    pub fn port(&self) -> Option<u16> {
313        self.maybe_resolved().port()
314    }
315
316    /// Set the port of the target. If the target type does not include a port,
317    /// this will return None. Otherwise, it will return the old port.
318    pub fn try_set_port(&mut self, port: u16) -> Option<u16> {
319        self.maybe_resolved_mut().set_port(port)
320    }
321
322    /// Get the path of the target. If the target type does not include a path,
323    /// this will return None.
324    pub fn path(&self) -> Option<&Path> {
325        self.maybe_resolved().path()
326    }
327
328    /// Get the host of the target. For resolved IP addresses, this is the
329    /// string representation of the IP address. For unresolved hostnames, this
330    /// is the hostname. If the target type does not include a host, this will
331    /// return None.
332    pub fn host(&self) -> Option<Cow<str>> {
333        self.maybe_resolved().host()
334    }
335
336    /// Get the name of the target. For resolved IP addresses, this is the
337    /// string representation of the IP address. For unresolved hostnames, this
338    /// is the hostname.
339    pub fn name(&self) -> Option<ServerName> {
340        self.maybe_resolved().name()
341    }
342
343    /// Get the host and port of the target. If the target type does not include
344    /// a host or port, this will return None.
345    pub fn tcp(&self) -> Option<(Cow<str>, u16)> {
346        self.maybe_resolved().tcp()
347    }
348
349    pub(crate) fn maybe_resolved(&self) -> &MaybeResolvedTarget {
350        match &self.inner {
351            TargetInner::NoTls(target) => target,
352            TargetInner::Tls(target, _) => target,
353            TargetInner::StartTls(target, _) => target,
354        }
355    }
356
357    pub(crate) fn maybe_resolved_mut(&mut self) -> &mut MaybeResolvedTarget {
358        match &mut self.inner {
359            TargetInner::NoTls(target) => target,
360            TargetInner::Tls(target, _) => target,
361            TargetInner::StartTls(target, _) => target,
362        }
363    }
364
365    pub(crate) fn is_starttls(&self) -> bool {
366        matches!(self.inner, TargetInner::StartTls(_, _))
367    }
368
369    pub(crate) fn maybe_ssl(&self) -> Option<&TlsParameters> {
370        match &self.inner {
371            TargetInner::NoTls(_) => None,
372            TargetInner::Tls(_, params) => Some(params),
373            TargetInner::StartTls(_, params) => Some(params),
374        }
375    }
376}
377
378#[derive(Clone, derive_more::From)]
379pub(crate) enum MaybeResolvedTarget {
380    Resolved(ResolvedTarget),
381    Unresolved(Cow<'static, str>, u16, Option<Cow<'static, str>>),
382}
383
384impl std::fmt::Debug for MaybeResolvedTarget {
385    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
386        match self {
387            MaybeResolvedTarget::Resolved(ResolvedTarget::SocketAddr(addr)) => {
388                if let SocketAddr::V6(addr) = addr {
389                    if addr.scope_id() != 0 {
390                        write!(f, "[{}%{}]:{}", addr.ip(), addr.scope_id(), addr.port())
391                    } else {
392                        write!(f, "[{}]:{}", addr.ip(), addr.port())
393                    }
394                } else {
395                    write!(f, "{}:{}", addr.ip(), addr.port())
396                }
397            }
398            #[cfg(unix)]
399            MaybeResolvedTarget::Resolved(ResolvedTarget::UnixSocketAddr(addr)) => {
400                if let Some(path) = addr.as_pathname() {
401                    return write!(f, "{}", path.to_string_lossy());
402                } else {
403                    #[cfg(any(target_os = "linux", target_os = "android"))]
404                    {
405                        use std::os::linux::net::SocketAddrExt;
406                        if let Some(name) = addr.as_abstract_name() {
407                            return write!(f, "@{}", String::from_utf8_lossy(name));
408                        }
409                    }
410                }
411                Ok(())
412            }
413            MaybeResolvedTarget::Unresolved(host, port, interface) => {
414                write!(f, "{}:{}", host, port)?;
415                if let Some(interface) = interface {
416                    write!(f, "%{}", interface)?;
417                }
418                Ok(())
419            }
420        }
421    }
422}
423
424impl MaybeResolvedTarget {
425    fn name(&self) -> Option<ServerName> {
426        match self {
427            MaybeResolvedTarget::Resolved(ResolvedTarget::SocketAddr(addr)) => {
428                Some(ServerName::IpAddress(addr.ip().into()))
429            }
430            MaybeResolvedTarget::Unresolved(host, _, _) => {
431                Some(ServerName::DnsName(host.to_string().try_into().ok()?))
432            }
433            #[cfg(unix)]
434            _ => None,
435        }
436    }
437
438    fn tcp(&self) -> Option<(Cow<str>, u16)> {
439        match self {
440            MaybeResolvedTarget::Resolved(ResolvedTarget::SocketAddr(addr)) => {
441                Some((Cow::Owned(addr.ip().to_string()), addr.port()))
442            }
443            MaybeResolvedTarget::Unresolved(host, port, _) => Some((Cow::Borrowed(host), *port)),
444            #[cfg(unix)]
445            _ => None,
446        }
447    }
448
449    fn path(&self) -> Option<&Path> {
450        match self {
451            #[cfg(unix)]
452            MaybeResolvedTarget::Resolved(ResolvedTarget::UnixSocketAddr(addr)) => {
453                addr.as_pathname()
454            }
455            _ => None,
456        }
457    }
458
459    fn host(&self) -> Option<Cow<str>> {
460        match self {
461            MaybeResolvedTarget::Resolved(ResolvedTarget::SocketAddr(addr)) => {
462                Some(Cow::Owned(addr.ip().to_string()))
463            }
464            MaybeResolvedTarget::Unresolved(host, _, _) => Some(Cow::Borrowed(host)),
465            #[cfg(unix)]
466            _ => None,
467        }
468    }
469
470    fn port(&self) -> Option<u16> {
471        match self {
472            MaybeResolvedTarget::Resolved(ResolvedTarget::SocketAddr(addr)) => Some(addr.port()),
473            MaybeResolvedTarget::Unresolved(_, port, _) => Some(*port),
474            #[cfg(unix)]
475            _ => None,
476        }
477    }
478
479    fn set_port(&mut self, new_port: u16) -> Option<u16> {
480        match self {
481            MaybeResolvedTarget::Resolved(ResolvedTarget::SocketAddr(addr)) => {
482                let old_port = addr.port();
483                addr.set_port(new_port);
484                Some(old_port)
485            }
486            MaybeResolvedTarget::Unresolved(_, port, _) => {
487                let old_port = *port;
488                *port = new_port;
489                Some(old_port)
490            }
491            #[cfg(unix)]
492            _ => None,
493        }
494    }
495}
496
497/// The type of connection.
498#[derive(Clone, Debug)]
499enum TargetInner {
500    NoTls(MaybeResolvedTarget),
501    Tls(MaybeResolvedTarget, Arc<TlsParameters>),
502    StartTls(MaybeResolvedTarget, Arc<TlsParameters>),
503}
504
505#[derive(Clone, Debug, derive_more::From, derive_more::TryFrom)]
506/// The resolved target of a connection attempt.
507#[from(forward)]
508pub enum ResolvedTarget {
509    SocketAddr(std::net::SocketAddr),
510    #[cfg(unix)]
511    UnixSocketAddr(std::os::unix::net::SocketAddr),
512}
513
514/// Because `std::os::unix::net::SocketAddr` does not implement many helper
515/// traits, we temporarily use this enum to represent the inner representation
516/// of the resolved target for easier operation.
517#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
518enum ResolvedTargetInner<'a> {
519    SocketAddr(std::net::SocketAddr),
520    #[cfg(unix)]
521    UnixSocketPath(&'a std::path::Path),
522    #[cfg(any(target_os = "linux", target_os = "android"))]
523    UnixSocketAbstract(&'a [u8]),
524    /// Windows doesn't need the lifetime, so we create a fake enum variant
525    /// to use it.
526    #[allow(dead_code)]
527    Phantom(std::marker::PhantomData<&'a ()>),
528}
529
530#[cfg(unix)]
531impl TryFrom<std::path::PathBuf> for ResolvedTarget {
532    type Error = std::io::Error;
533
534    fn try_from(value: std::path::PathBuf) -> Result<Self, Self::Error> {
535        Ok(ResolvedTarget::UnixSocketAddr(
536            std::os::unix::net::SocketAddr::from_pathname(value)?,
537        ))
538    }
539}
540
541impl Eq for ResolvedTarget {}
542
543impl PartialEq for ResolvedTarget {
544    fn eq(&self, other: &Self) -> bool {
545        self.inner() == other.inner()
546    }
547}
548
549impl Hash for ResolvedTarget {
550    fn hash<H: Hasher>(&self, state: &mut H) {
551        self.inner().hash(state);
552    }
553}
554
555impl PartialOrd for ResolvedTarget {
556    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
557        self.inner().partial_cmp(&other.inner())
558    }
559}
560
561impl ResolvedTarget {
562    pub fn tcp(&self) -> Option<SocketAddr> {
563        match self {
564            ResolvedTarget::SocketAddr(addr) => Some(*addr),
565            _ => None,
566        }
567    }
568
569    pub fn is_tcp(&self) -> bool {
570        self.tcp().is_some()
571    }
572
573    pub fn transport(&self) -> Transport {
574        match self {
575            ResolvedTarget::SocketAddr(_) => Transport::Tcp,
576            #[cfg(unix)]
577            ResolvedTarget::UnixSocketAddr(_) => Transport::Unix,
578        }
579    }
580
581    /// Get the inner representation of the resolved target.
582    #[allow(unreachable_code)]
583    fn inner(&self) -> ResolvedTargetInner {
584        match self {
585            ResolvedTarget::SocketAddr(addr) => ResolvedTargetInner::SocketAddr(*addr),
586            #[cfg(unix)]
587            ResolvedTarget::UnixSocketAddr(addr) => {
588                if let Some(path) = addr.as_pathname() {
589                    return ResolvedTargetInner::UnixSocketPath(path);
590                } else {
591                    #[cfg(any(target_os = "linux", target_os = "android"))]
592                    {
593                        use std::os::linux::net::SocketAddrExt;
594                        return ResolvedTargetInner::UnixSocketAbstract(
595                            addr.as_abstract_name().expect("abstract socket address"),
596                        );
597                    }
598                }
599                unreachable!()
600            }
601        }
602    }
603}
604
605/// A trait for types that have a local address.
606pub trait LocalAddress {
607    fn local_address(&self) -> std::io::Result<ResolvedTarget>;
608}
609
610/// A trait for types that have a local address.
611pub trait RemoteAddress {
612    fn remote_address(&self) -> std::io::Result<ResolvedTarget>;
613}
614
615pub trait PeerCred {
616    #[cfg(all(unix, feature = "tokio"))]
617    fn peer_cred(&self) -> std::io::Result<tokio::net::unix::UCred>;
618}
619
620/// The transport of a stream.
621#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
622pub enum Transport {
623    Tcp,
624    Unix,
625}
626
627/// A trait for stream metadata.
628pub trait StreamMetadata: LocalAddress + RemoteAddress + PeerCred + Send {
629    fn transport(&self) -> Transport;
630}
631
632pub(crate) trait TcpResolve {
633    fn into(self) -> MaybeResolvedTarget;
634}
635
636impl<S: AsRef<str>> TcpResolve for (S, u16) {
637    fn into(self) -> MaybeResolvedTarget {
638        if let Ok(addr) = self.0.as_ref().parse::<IpAddr>() {
639            MaybeResolvedTarget::Resolved(ResolvedTarget::SocketAddr(SocketAddr::new(addr, self.1)))
640        } else {
641            MaybeResolvedTarget::Unresolved(Cow::Owned(self.0.as_ref().to_owned()), self.1, None)
642        }
643    }
644}
645
646impl TcpResolve for SocketAddr {
647    fn into(self) -> MaybeResolvedTarget {
648        MaybeResolvedTarget::Resolved(ResolvedTarget::SocketAddr(self))
649    }
650}
651
652#[cfg(test)]
653mod tests {
654    use std::net::SocketAddrV6;
655
656    use super::*;
657
658    #[test]
659    fn test_target() {
660        let target = Target::new_tcp(("localhost", 5432));
661        assert_eq!(
662            target.name(),
663            Some(ServerName::DnsName("localhost".try_into().unwrap()))
664        );
665    }
666
667    #[test]
668    fn test_target_name() {
669        let target = TargetName::new_tcp(("localhost", 5432));
670        assert_eq!(format!("{target:?}"), "localhost:5432");
671
672        let target = TargetName::new_tcp(("127.0.0.1", 5432));
673        assert_eq!(format!("{target:?}"), "127.0.0.1:5432");
674
675        let target = TargetName::new_tcp(("::1", 5432));
676        assert_eq!(format!("{target:?}"), "[::1]:5432");
677
678        let target = TargetName::new_tcp(SocketAddr::V6(SocketAddrV6::new(
679            "fe80::1ff:fe23:4567:890a".parse().unwrap(),
680            5432,
681            0,
682            2,
683        )));
684        assert_eq!(format!("{target:?}"), "[fe80::1ff:fe23:4567:890a%2]:5432");
685
686        #[cfg(unix)]
687        {
688            let target = TargetName::new_unix_path("/tmp/test.sock").unwrap();
689            assert_eq!(format!("{target:?}"), "/tmp/test.sock");
690        }
691
692        #[cfg(any(target_os = "linux", target_os = "android"))]
693        {
694            let target = TargetName::new_unix_domain("test").unwrap();
695            assert_eq!(format!("{target:?}"), "@test");
696        }
697    }
698
699    #[test]
700    fn test_target_debug() {
701        let target = Target::new_tcp(("localhost", 5432));
702        assert_eq!(format!("{target:?}"), "localhost:5432");
703
704        let target = Target::new_tcp_tls(("localhost", 5432), TlsParameters::default());
705        assert_eq!(format!("{target:?}"), "localhost:5432 (TLS)");
706
707        let target = Target::new_tcp_starttls(("localhost", 5432), TlsParameters::default());
708        assert_eq!(format!("{target:?}"), "localhost:5432 (STARTTLS)");
709
710        let target = Target::new_tcp(("127.0.0.1", 5432));
711        assert_eq!(format!("{target:?}"), "127.0.0.1:5432");
712
713        let target = Target::new_tcp(("::1", 5432));
714        assert_eq!(format!("{target:?}"), "[::1]:5432");
715
716        #[cfg(unix)]
717        {
718            let target = Target::new_unix_path("/tmp/test.sock").unwrap();
719            assert_eq!(format!("{target:?}"), "/tmp/test.sock");
720        }
721
722        #[cfg(any(target_os = "linux", target_os = "android"))]
723        {
724            let target = Target::new_unix_domain("test").unwrap();
725            assert_eq!(format!("{target:?}"), "@test");
726        }
727    }
728}