feat: Use url::Host instead of IpOrUrl

Signed-off-by: Awiteb <a@4rs.nl>
This commit is contained in:
Awiteb 2024-07-22 12:41:58 +03:00
parent 1c273d746b
commit daef92207e
Signed by: awiteb
GPG key ID: 3F6B55640AA6682F
6 changed files with 59 additions and 86 deletions

View file

@ -36,11 +36,7 @@ use crate::nonce::NonceCache;
pub(crate) fn postgres_url(db_config: &Postgres) -> String { pub(crate) fn postgres_url(db_config: &Postgres) -> String {
format!( format!(
"postgres://{}:{}@{}:{}/{}", "postgres://{}:{}@{}:{}/{}",
db_config.user, db_config.user, db_config.password, db_config.host, db_config.port, db_config.name
db_config.password,
db_config.host.as_str(),
db_config.port,
db_config.name
) )
} }

View file

@ -26,7 +26,7 @@ use std::{net::IpAddr, path::PathBuf};
use clap::Parser; use clap::Parser;
use oxidetalis_core::types::Size; use oxidetalis_core::types::Size;
use crate::{types::OpenApiViewer, IpOrUrl}; use crate::types::{Host, OpenApiViewer};
/// Header message, used in the help message /// Header message, used in the help message
const HEADER: &str = r#"Copyright (C) 2024 Awiteb <a@4rs.nl>, OxideTalis Contributors const HEADER: &str = r#"Copyright (C) 2024 Awiteb <a@4rs.nl>, OxideTalis Contributors
@ -74,7 +74,7 @@ pub struct CliArgs {
pub register_enable: Option<bool>, pub register_enable: Option<bool>,
/// Hostname or IP address of the PostgreSQL database. /// Hostname or IP address of the PostgreSQL database.
#[clap(long, env = "OXIDETALIS_DB_HOST")] #[clap(long, env = "OXIDETALIS_DB_HOST")]
pub postgres_host: Option<IpOrUrl>, pub postgres_host: Option<Host>,
/// Port number of the PostgreSQL database. /// Port number of the PostgreSQL database.
#[clap(long, env = "OXIDETALIS_DB_PORT")] #[clap(long, env = "OXIDETALIS_DB_PORT")]
pub postgres_port: Option<u16>, pub postgres_port: Option<u16>,

View file

@ -81,7 +81,6 @@ pub(crate) mod openapi {
/// Postgres default configs /// Postgres default configs
pub(crate) mod postgres { pub(crate) mod postgres {
use std::str::FromStr;
pub fn user() -> String { pub fn user() -> String {
"oxidetalis".to_owned() "oxidetalis".to_owned()
@ -89,8 +88,9 @@ pub(crate) mod postgres {
pub fn password() -> String { pub fn password() -> String {
"oxidetalis".to_owned() "oxidetalis".to_owned()
} }
pub fn host() -> crate::IpOrUrl { pub const fn host() -> crate::Host {
crate::IpOrUrl::from_str("localhost").expect("Is a valid localhost") #[allow(clippy::absolute_paths)]
crate::Host(url::Host::Ipv4(std::net::Ipv4Addr::new(127, 0, 0, 1)))
} }
pub fn name() -> String { pub fn name() -> String {
"oxidetalis_db".to_owned() "oxidetalis_db".to_owned()

View file

@ -95,7 +95,8 @@ pub struct Postgres {
pub password: String, pub password: String,
/// Database host /// Database host
#[derivative(Default(value = "defaults::postgres::host()"))] #[derivative(Default(value = "defaults::postgres::host()"))]
pub host: IpOrUrl, #[serde(with = "serde_with::host")]
pub host: Host,
/// Database port /// Database port
#[derivative(Default(value = "defaults::postgres::port()"))] #[derivative(Default(value = "defaults::postgres::port()"))]
pub port: u16, pub port: u16,

View file

@ -21,34 +21,10 @@
//! Serialize and deserialize some oxidetalis configurations //! Serialize and deserialize some oxidetalis configurations
use serde::{de::Error as DeError, Deserialize, Deserializer};
/// Serialize and deserialze the string of IpOrUrl struct
pub(crate) mod ip_or_url {
use std::str::FromStr; use std::str::FromStr;
use serde::{de::Error as DeError, Deserialize, Deserializer, Serializer}; use serde::{de::Error as DeError, Deserialize, Deserializer, Serializer};
use crate::IpOrUrl;
pub fn serialize<S>(value: &str, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(value)
}
pub fn deserialize<'de, D>(de: D) -> Result<String, D::Error>
where
D: Deserializer<'de>,
{
Ok(IpOrUrl::from_str(&String::deserialize(de)?)
.map_err(DeError::custom)?
.as_str()
.to_owned())
}
}
pub fn deserialize_url_path<'de, D>(de: D) -> Result<String, D::Error> pub fn deserialize_url_path<'de, D>(de: D) -> Result<String, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
@ -61,3 +37,24 @@ where
} }
Ok(url_path) Ok(url_path)
} }
pub mod host {
#[allow(clippy::wildcard_imports)]
use super::*;
use crate::Host;
pub fn deserialize<'de, D>(de: D) -> Result<Host, D::Error>
where
D: Deserializer<'de>,
{
Host::from_str(&String::deserialize(de)?)
.map_err(|_| DeError::custom("Invalid host name, invalid IPv4, IPv6 or domain name"))
}
pub fn serialize<S>(host: &Host, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&host.to_string())
}
}

View file

@ -21,7 +21,7 @@
//! Oxidetalis config types //! Oxidetalis config types
use std::{net::IpAddr, str::FromStr}; use std::{fmt, str::FromStr};
use salvo_oapi::{rapidoc::RapiDoc, redoc::ReDoc, scalar::Scalar, swagger_ui::SwaggerUi}; use salvo_oapi::{rapidoc::RapiDoc, redoc::ReDoc, scalar::Scalar, swagger_ui::SwaggerUi};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -41,6 +41,33 @@ pub enum OpenApiViewer {
SwaggerUi, SwaggerUi,
} }
/// Host type, a wrapper around `url::Host`
///
/// Because `url::Host` does not implement `FromStr`, we need to wrap it
/// in a newtype to implement `FromStr` for it.
#[derive(Debug, Clone)]
pub struct Host(pub url::Host);
impl FromStr for Host {
type Err = url::ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// It appears that @SimonSapin prefers not to use the `FromStr` trait.
// Instead, he is implementing `parse` without utilizing `FromStr`.
//
// - <https://github.com/servo/rust-url/pull/18#issuecomment-53467026>
// - <https://github.com/servo/rust-url/pull/107#issuecomment-100611345>
// - <https://github.com/servo/rust-url/issues/286#issuecomment-284193315>
Ok(Self(url::Host::parse(s)?))
}
}
impl fmt::Display for Host {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl OpenApiViewer { impl OpenApiViewer {
/// Create a router for the viewer /// Create a router for the viewer
pub fn into_router(&self, config: &crate::Config) -> salvo_core::Router { pub fn into_router(&self, config: &crate::Config) -> salvo_core::Router {
@ -76,51 +103,3 @@ impl OpenApiViewer {
} }
} }
} }
/// Type hold url or ip (used for database host)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IpOrUrl(#[serde(with = "crate::serde_with::ip_or_url")] String);
impl Default for IpOrUrl {
fn default() -> Self {
IpOrUrl("localhost".to_owned())
}
}
impl IpOrUrl {
/// Returns &str ip or url
pub fn as_str(&self) -> &str {
&self.0
}
}
impl FromStr for IpOrUrl {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(IpOrUrl(
if let Ok(res) = IpAddr::from_str(s).map(|i| i.to_string()) {
res
} else {
validate_domain(s)?
},
))
}
}
fn validate_domain(domain: &str) -> Result<String, String> {
if domain != "localhost" {
let subs = domain.split('.');
for sub in subs {
let length = sub.chars().count();
if !sub.chars().all(|c| c.is_alphanumeric() || c == '-')
|| sub.starts_with('-')
|| sub.ends_with('-')
|| (length > 0 && length <= 64)
{
return Err("Invalid domain name".to_owned());
}
}
}
Ok(domain.to_owned())
}