1#![warn(clippy::pedantic)]
25#![allow(clippy::missing_panics_doc)]
26#![cfg_attr(docsrs, feature(doc_cfg))]
27
28use core::{
29 fmt::{self, Display},
30 str::FromStr,
31};
32use std::io;
33
34use enumflags2::{BitFlags, bitflags};
35use thiserror::Error;
36
37#[bitflags]
39#[repr(u8)]
40#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
41pub enum Capability {
42 NatTesting,
44 TcpSupport,
46}
47
48pub type Capabilities = BitFlags<Capability>;
50
51#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct Server {
54 pub host: String,
56 pub port: u16,
58}
59
60impl Display for Server {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 write!(f, "{}:{}", self.host, self.port)
63 }
64}
65
66impl std::net::ToSocketAddrs for Server {
67 type Iter = <(String, u16) as std::net::ToSocketAddrs>::Iter;
68 fn to_socket_addrs(&self) -> io::Result<Self::Iter> {
69 (self.host.as_str(), self.port).to_socket_addrs()
70 }
71}
72
73#[derive(Debug, Error)]
75pub enum InvalidFormat {
76 #[error("missing ':' in source")]
77 MissingDelimiter,
78 #[error("port number is not a valid u16")]
79 InvalidPort,
80}
81
82impl FromStr for Server {
83 type Err = InvalidFormat;
84
85 fn from_str(s: &str) -> Result<Self, Self::Err> {
86 let Some((host, port)) = s.split_once(':') else {
87 return Err(InvalidFormat::MissingDelimiter);
88 };
89 let host = host.to_string();
90 let Ok(port) = port.parse() else {
91 return Err(InvalidFormat::InvalidPort);
92 };
93 Ok(Self { host, port })
94 }
95}
96
97#[cfg(any(feature = "packaged", feature = "fetched"))]
98pub(crate) fn parse_raw_server_list(text: &str) -> Result<Vec<Server>, InvalidFormat> {
99 text.lines().map(|line| line.trim().parse()).collect()
100}
101
102#[cfg(feature = "packaged")]
104pub mod packaged;
105
106#[cfg(feature = "fetched")]
108pub mod fetched;