1#![deny(missing_docs)]
2#![doc = include_str!("../README.md")]
3#![cfg_attr(test, deny(warnings))]
4
5use std::net::TcpListener;
6
7#[cfg(feature = "fd")]
8use std::os::raw::c_int;
9#[cfg(feature = "fd")]
10use std::os::unix::io::FromRawFd;
11
12#[derive(clap::Args, Debug)]
25pub struct Port {
26 #[cfg(feature = "addr_with_port")]
28 #[clap(short = 'a', long = "address", default_value = "127.0.0.1:80")]
29 address: std::net::SocketAddr,
30
31 #[cfg(not(feature = "addr_with_port"))]
33 #[clap(short = 'a', long = "address", default_value = "127.0.0.1")]
34 address: String,
35
36 #[cfg(not(features = "addr_with_port"))]
38 #[clap(short = 'p', long = "port", env = "PORT", group = "bind")]
39 port: Option<u16>,
40
41 #[cfg(feature = "fd")]
43 #[clap(long = "listen-fd", env = "LISTEN_FD", group = "bind")]
44 fd: Option<c_int>,
45}
46
47impl Port {
55 pub fn bind(&self) -> std::io::Result<TcpListener> {
57 #[cfg(feature = "fd")]
58 if let Some(fd) = self.fd {
59 return unsafe { Ok(TcpListener::from_raw_fd(fd)) };
60 }
61
62 #[cfg(feature = "addr_with_port")]
63 {
64 let addr: std::net::SocketAddr = self.address;
65 TcpListener::bind(addr)
66 }
67 #[cfg(not(feature = "addr_with_port"))]
68 {
69 let addr: &str = self.address.as_str();
70 if let Some(port) = self.port {
71 TcpListener::bind((addr, port))
72 } else {
73 Err(std::io::Error::new(
74 std::io::ErrorKind::Other,
75 "No port supplied.",
76 ))
77 }
78 }
79 }
80
81 #[cfg(feature = "addr_with_port")]
86 pub fn bind_or(&self, port: u16) -> std::io::Result<TcpListener> {
87 let mut addr = self.address;
88 addr.set_port(port);
89
90 self.bind().or_else(|_| TcpListener::bind(addr))
91 }
92
93 #[cfg(not(feature = "addr_with_port"))]
98 pub fn bind_or(&self, port: u16) -> std::io::Result<TcpListener> {
99 self.bind()
100 .or_else(|_| TcpListener::bind((self.address.as_str(), port)))
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 use clap::Parser;
109
110 #[derive(Debug, Parser)]
111 struct Cli {
112 #[clap(flatten)]
113 port: Port,
114 }
115
116 #[cfg(not(feature = "addr_with_port"))]
117 #[test]
118 fn test_cli() {
119 let args = Cli::try_parse_from(&["test", "--address", "1.2.3.4", "--port", "1234"]);
120 assert!(args.is_ok(), "Not ok: {:?}", args.unwrap_err());
121 let args = args.unwrap();
122 assert_eq!(args.port.address, "1.2.3.4");
123 assert_eq!(args.port.port, Some(1234));
124 }
125
126 #[cfg(feature = "addr_with_port")]
127 #[test]
128 fn test_cli() {
129 let args = Cli::try_parse_from(&["test", "--address", "1.2.3.4:8080"]);
130 assert!(args.is_ok(), "Not ok: {:?}", args.unwrap_err());
131 let args = args.unwrap();
132 let exp = std::net::SocketAddr::V4(std::net::SocketAddrV4::new(
133 std::net::Ipv4Addr::new(1, 2, 3, 4),
134 8080,
135 ));
136 assert_eq!(args.port.address, exp);
137 }
138}