use std::str::FromStr; use tarantool::tlua; pub const DEFAULT_USERNAME: &str = "guest"; pub const DEFAULT_LISTEN_HOST: &str = "127.0.0.1"; pub const DEFAULT_HTTP_PORT: &str = "8080"; pub const DEFAULT_IPROTO_PORT: &str = "3301"; pub const DEFAULT_PGPROTO_PORT: &str = "4327"; //////////////////// // IPROTO ADDRESS // //////////////////// #[derive(Debug, Clone, PartialEq, Eq, tlua::Push, tlua::PushInto)] pub struct IprotoAddress { pub user: Option<String>, pub host: String, pub port: String, } impl Default for IprotoAddress { fn default() -> Self { Self { user: None, host: DEFAULT_LISTEN_HOST.into(), port: DEFAULT_IPROTO_PORT.into(), } } } impl IprotoAddress { #[inline(always)] pub const fn default_host_port() -> &'static str { // TODO: "only literals can be passed to `concat!()`" // concat!(DEFAULT_LISTEN_HOST, ":", DEFAULT_IPROTO_PORT) "127.0.0.1:3301" } #[inline(always)] pub fn to_host_port(&self) -> String { format!("{}:{}", self.host, self.port) } fn parse_address(addr: &str) -> Result<Self, String> { let format_err = || Err("valid format: [user@][host][:port]".to_string()); let (user, host_port) = match addr.rsplit_once('@') { Some((user, host_port)) => { if user.contains(':') || user.contains('@') { return format_err(); } if user.is_empty() { return format_err(); } (Some(user), host_port) } None => (None, addr), }; let (host, port) = match host_port.rsplit_once(':') { Some((host, port)) => { if host.contains(':') || port.is_empty() { return format_err(); } let host = if host.is_empty() { None } else { Some(host) }; (host, Some(port)) } None => (Some(host_port), None), }; Ok(Self { user: user.map(Into::into), host: host.unwrap_or(DEFAULT_LISTEN_HOST).into(), port: port.unwrap_or(DEFAULT_IPROTO_PORT).into(), }) } } impl std::fmt::Display for IprotoAddress { #[inline(always)] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(user) = &self.user { write!(f, "{user}@{}:{}", self.host, self.port) } else { write!(f, "{}:{}", self.host, self.port) } } } impl FromStr for IprotoAddress { type Err = String; fn from_str(addr: &str) -> Result<Self, Self::Err> { Self::parse_address(addr) } } impl serde::Serialize for IprotoAddress { #[inline(always)] fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer, { self.to_string().serialize(serializer) } } impl<'de> serde::Deserialize<'de> for IprotoAddress { #[inline(always)] fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de>, { let s: &str = serde::Deserialize::deserialize(deserializer)?; Self::from_str(s).map_err(serde::de::Error::custom) } } ////////////////// // HTTP ADDRESS // ////////////////// #[derive(Debug, Clone, PartialEq, Eq, tlua::Push, tlua::PushInto)] pub struct HttpAddress { pub host: String, pub port: String, } impl Default for HttpAddress { fn default() -> Self { Self { host: DEFAULT_LISTEN_HOST.into(), port: DEFAULT_HTTP_PORT.into(), } } } impl HttpAddress { #[inline(always)] pub const fn default_host_port() -> &'static str { // TODO: "only literals can be passed to `concat!()`" // concat!(DEFAULT_LISTEN_HOST, ":", DEFAULT_HTTP_PORT) "127.0.0.1:8080" } #[inline(always)] pub fn to_host_port(&self) -> String { format!("{}:{}", self.host, self.port) } fn parse_address(addr: &str) -> Result<Self, String> { let format_err = || Err("valid format: [host][:port]".to_string()); let (host, port) = match addr.rsplit_once(':') { Some((host, port)) => { if host.contains(':') || port.is_empty() { return format_err(); } let host = if host.is_empty() { None } else { Some(host) }; (host, Some(port)) } None => (Some(addr), None), }; Ok(Self { host: host.unwrap_or(DEFAULT_LISTEN_HOST).into(), port: port.unwrap_or(DEFAULT_HTTP_PORT).into(), }) } } impl std::fmt::Display for HttpAddress { #[inline(always)] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}:{}", self.host, self.port) } } impl FromStr for HttpAddress { type Err = String; fn from_str(addr: &str) -> Result<Self, Self::Err> { Self::parse_address(addr) } } impl serde::Serialize for HttpAddress { #[inline(always)] fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer, { self.to_string().serialize(serializer) } } impl<'de> serde::Deserialize<'de> for HttpAddress { #[inline(always)] fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de>, { let s: &str = serde::Deserialize::deserialize(deserializer)?; Self::from_str(s).map_err(serde::de::Error::custom) } } ///////////////////// // PGPROTO ADDRESS // ///////////////////// #[derive(Debug, Clone, PartialEq, Eq, tlua::Push, tlua::PushInto)] pub struct PgprotoAddress { pub host: String, pub port: String, } impl Default for PgprotoAddress { fn default() -> Self { Self { host: DEFAULT_LISTEN_HOST.into(), port: DEFAULT_PGPROTO_PORT.into(), } } } impl PgprotoAddress { #[inline(always)] pub const fn default_host_port() -> &'static str { // TODO: "only literals can be passed to `concat!()`" // concat!(DEFAULT_LISTEN_HOST, ":", DEFAULT_PGPROTO_PORT) "127.0.0.1:4327" } #[inline(always)] pub fn to_host_port(&self) -> String { format!("{}:{}", self.host, self.port) } fn parse_address(addr: &str) -> Result<Self, String> { let format_err = || Err("valid format: [host][:port]".to_string()); let (host, port) = match addr.rsplit_once(':') { Some((host, port)) => { if host.contains(':') || port.is_empty() { return format_err(); } let host = if host.is_empty() { None } else { Some(host) }; (host, Some(port)) } None => (Some(addr), None), }; Ok(Self { host: host.unwrap_or(DEFAULT_LISTEN_HOST).into(), port: port.unwrap_or(DEFAULT_PGPROTO_PORT).into(), }) } } impl std::fmt::Display for PgprotoAddress { #[inline(always)] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}:{}", self.host, self.port) } } impl FromStr for PgprotoAddress { type Err = String; fn from_str(addr: &str) -> Result<Self, Self::Err> { Self::parse_address(addr) } } impl serde::Serialize for PgprotoAddress { #[inline(always)] fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer, { self.to_string().serialize(serializer) } } impl<'de> serde::Deserialize<'de> for PgprotoAddress { #[inline(always)] fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de>, { let s: &str = serde::Deserialize::deserialize(deserializer)?; Self::from_str(s).map_err(serde::de::Error::custom) } } /////////// // TESTS // /////////// #[cfg(test)] mod tests { use super::*; #[test] fn try_parse_address() { // // check parsing of correct addresses // assert_eq!( "1234".parse(), Ok(IprotoAddress { user: None, host: "1234".into(), port: "3301".into() }) ); assert_eq!( ":1234".parse(), Ok(IprotoAddress { user: None, host: "127.0.0.1".into(), port: "1234".into() }) ); assert_eq!( "example".parse(), Ok(IprotoAddress { user: None, host: "example".into(), port: "3301".into() }) ); assert_eq!( "127.0.0.1:1234".parse(), Ok(IprotoAddress { user: None, host: "127.0.0.1".into(), port: "1234".into() }) ); assert_eq!( "1.2.3.4:1234".parse(), Ok(IprotoAddress { user: None, host: "1.2.3.4".into(), port: "1234".into() }) ); assert_eq!( "example:1234".parse(), Ok(IprotoAddress { user: None, host: "example".into(), port: "1234".into() }) ); assert_eq!( "user@host:port".parse(), Ok(IprotoAddress { user: Some("user".into()), host: "host".into(), port: "port".into() }) ); assert_eq!( "user@:port".parse(), Ok(IprotoAddress { user: Some("user".into()), host: "127.0.0.1".into(), port: "port".into() }) ); assert_eq!( "user@host".parse(), Ok(IprotoAddress { user: Some("user".into()), host: "host".into(), port: "3301".into() }) ); // // check parsing of incorrect addresses // assert!("example::1234".parse::<IprotoAddress>().is_err()); assert!("user@@example".parse::<IprotoAddress>().is_err()); assert!("user:pass@host:port".parse::<IprotoAddress>().is_err()); assert!("a:b@c".parse::<IprotoAddress>().is_err()); assert!("user:pass@host".parse::<IprotoAddress>().is_err()); assert!("@host".parse::<IprotoAddress>().is_err()); assert!("host:".parse::<IprotoAddress>().is_err()); // // check conflicting ports in address parsing // let iproto_conflict_with_http = format!("host:{DEFAULT_HTTP_PORT}"); assert!(iproto_conflict_with_http.parse::<IprotoAddress>().is_ok(),); let iproto_conflict_with_pg = format!("host:{DEFAULT_PGPROTO_PORT}"); assert!(iproto_conflict_with_pg.parse::<IprotoAddress>().is_ok()); let http_conflict_with_iproto = format!("host:{DEFAULT_IPROTO_PORT}"); assert!(http_conflict_with_iproto.parse::<HttpAddress>().is_ok(),); let http_conflict_with_pg = format!("host:{DEFAULT_PGPROTO_PORT}"); assert!(http_conflict_with_pg.parse::<HttpAddress>().is_ok(),); let pg_conflict_with_iproto = format!("host:{DEFAULT_IPROTO_PORT}"); assert!(pg_conflict_with_iproto.parse::<PgprotoAddress>().is_ok(),); let pg_conflict_with_http = format!("host:{DEFAULT_HTTP_PORT}"); assert!(pg_conflict_with_http.parse::<PgprotoAddress>().is_ok(),); // // check correctness of default values to avoid human factor // let default_iproto_addr = DEFAULT_LISTEN_HOST.parse::<IprotoAddress>().unwrap(); assert!(default_iproto_addr.host == DEFAULT_LISTEN_HOST); assert!(default_iproto_addr.port == DEFAULT_IPROTO_PORT); let default_http_addr = DEFAULT_LISTEN_HOST.parse::<HttpAddress>().unwrap(); assert!(default_http_addr.host == DEFAULT_LISTEN_HOST); assert!(default_http_addr.port == DEFAULT_HTTP_PORT); let default_pgproto_addr = DEFAULT_LISTEN_HOST.parse::<PgprotoAddress>().unwrap(); assert!(default_pgproto_addr.host == DEFAULT_LISTEN_HOST); assert!(default_pgproto_addr.port == DEFAULT_PGPROTO_PORT); } }