diff options
| author | mat <27899617+mat-1@users.noreply.github.com> | 2025-12-12 01:29:49 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-12-12 01:29:49 -0600 |
| commit | f4a3c53eee7d29bade0c074f402c4a45aa98eca8 (patch) | |
| tree | f25ec1d1390c5e96aba858141206a05812135b95 /azalea-protocol/src/address.rs | |
| parent | 7f761df3e7b72ce75be21ab9b3a533d0a5a938a5 (diff) | |
| download | azalea-drasl-f4a3c53eee7d29bade0c074f402c4a45aa98eca8.tar.xz | |
Delete `StartError` and `JoinError` (#296)
* delete StartError and JoinError
* update changelog
Diffstat (limited to 'azalea-protocol/src/address.rs')
| -rw-r--r-- | azalea-protocol/src/address.rs | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/azalea-protocol/src/address.rs b/azalea-protocol/src/address.rs new file mode 100644 index 00000000..a3e487a1 --- /dev/null +++ b/azalea-protocol/src/address.rs @@ -0,0 +1,157 @@ +use std::{ + fmt::{self, Debug, Display}, + net::SocketAddr, + str::FromStr, +}; + +use hickory_resolver::ResolveError; + +use crate::resolve::resolve_address; + +/// Something that might be able to be parsed and looked up as a server address. +/// +/// This is typically used by Azalea as a generic argument, so the user can +/// choose to pass either a string or an already-resolved address. +pub trait ResolvableAddr: Debug + Clone { + fn server_addr(self) -> Result<ServerAddr, ResolveError>; + fn resolve(self) -> impl Future<Output = Result<ResolvedAddr, ResolveError>> + Send; +} +impl<T: TryInto<ServerAddr, Error = ServerAddrParseError> + Debug + Send + Clone> ResolvableAddr + for T +{ + fn server_addr(self) -> Result<ServerAddr, ResolveError> { + self.try_into() + .map_err(|_| "failed to parse address".into()) + } + + async fn resolve(self) -> Result<ResolvedAddr, ResolveError> { + ResolvedAddr::new(self.server_addr()?).await + } +} + +impl ResolvableAddr for &ResolvedAddr { + fn server_addr(self) -> Result<ServerAddr, ResolveError> { + Ok(self.server.clone()) + } + + async fn resolve(self) -> Result<ResolvedAddr, ResolveError> { + Ok(self.clone()) + } +} + +/// A host and port. It's possible that the port doesn't resolve to anything. +/// +/// # Examples +/// +/// `ServerAddr` implements TryFrom<&str>, so you can use it like this: +/// ``` +/// use azalea_protocol::address::ServerAddr; +/// +/// let addr = ServerAddr::try_from("localhost:25565").unwrap(); +/// assert_eq!(addr.host, "localhost"); +/// assert_eq!(addr.port, 25565); +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ServerAddr { + pub host: String, + pub port: u16, +} + +/// An empty error type that's used when we fail to convert a type to a +/// `ServerAddr`. +/// +/// You usually want to use the [`ResolvableAddr`] type instead, which works +/// with [`ResolveError`]s. +#[derive(Debug)] +pub struct ServerAddrParseError; + +impl TryFrom<&str> for ServerAddr { + type Error = ServerAddrParseError; + + /// Convert a Minecraft server address (`host:port`, the port is optional) + /// to a `ServerAddress` + fn try_from(string: &str) -> Result<Self, Self::Error> { + if string.is_empty() { + return Err(ServerAddrParseError); + } + let mut parts = string.split(':'); + let host = parts.next().ok_or(ServerAddrParseError)?.to_string(); + // default the port to 25565 + let port = parts.next().unwrap_or("25565"); + let port = u16::from_str(port).ok().ok_or(ServerAddrParseError)?; + Ok(ServerAddr { host, port }) + } +} +impl TryFrom<String> for ServerAddr { + type Error = ServerAddrParseError; + + fn try_from(string: String) -> Result<Self, Self::Error> { + ServerAddr::try_from(string.as_str()) + } +} + +impl From<SocketAddr> for ServerAddr { + /// Convert an existing `SocketAddr` into a `ServerAddress`. + /// + /// This just converts the IP to a string and passes along the port. The + /// resolver will realize it's already an IP address and not do any DNS + /// requests. + fn from(addr: SocketAddr) -> Self { + ServerAddr { + host: addr.ip().to_string(), + port: addr.port(), + } + } +} + +impl Display for ServerAddr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}", self.host, self.port) + } +} + +/// Serde deserialization for ServerAddress. +/// +/// This is useful if you're storing the server address in a config file. +impl<'de> serde::Deserialize<'de> for ServerAddr { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + let string = String::deserialize(deserializer)?; + ServerAddr::try_from(string.as_str()) + .map_err(|_| serde::de::Error::custom("failed to parse address")) + } +} + +/// Serde serialization for ServerAddress. +/// +/// This uses the Display impl, so it will serialize to a string. +impl serde::Serialize for ServerAddr { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +/// An address that may be used to connect to a Minecraft server. +#[derive(Debug, Clone)] +pub struct ResolvedAddr { + /// The initial address that we passed when trying to connect. + /// + /// This is necessary because clients send this to the server when they + /// connect. + pub server: ServerAddr, + /// The IP and port that we will actually connect to. + pub socket: SocketAddr, +} + +impl ResolvedAddr { + pub async fn new(server: impl Into<ServerAddr>) -> Result<Self, ResolveError> { + let server = server.into(); + let socket = resolve_address(&server).await?; + Ok(Self { server, socket }) + } +} |
