mod multi;
mod single;
use multi::*;
use single::*;
use crate::{async_trait, RouteError, Url};
#[async_trait]
pub trait ApiRouter: 'static + Sync + std::fmt::Debug {
fn rewrite_host(&self) -> bool {
false
}
async fn next_endpoint(&self) -> Result<Box<dyn ApiEndpoint>, RouteError>;
}
pub struct ApiRouters;
impl ApiRouters {
pub fn fixed(endpoint: impl Into<DefaultApiEndpoint>) -> impl ApiRouter {
SingleApiRouter::new(endpoint.into())
}
pub fn round_robin(endpoints: &[DefaultApiEndpoint]) -> impl ApiRouter {
MultiApiRouter::new_round_robin(endpoints)
}
pub fn random(endpoints: &[DefaultApiEndpoint]) -> impl ApiRouter {
MultiApiRouter::new_random(endpoints)
}
}
pub trait ApiEndpoint {
fn build_url(&self, base: &Url, path: &str) -> Result<Url, RouteError>;
fn merge_path(&self, base: &mut Url, path: &str) {
let base_path = base.path();
let new_path = match (base_path.ends_with('/'), path.starts_with('/')) {
(true, true) => format!("{}{}", base_path, &path[1..]),
(true, false) | (false, true) => format!("{}{}", base_path, path),
(false, false) => format!("{}/{}", base_path, path),
};
base.set_path(&new_path);
}
}
pub struct OriginalEndpoint;
impl ApiEndpoint for OriginalEndpoint {
fn build_url(&self, base: &Url, path: &str) -> Result<Url, RouteError> {
let mut url = base.clone();
self.merge_path(&mut url, path);
Ok(url)
}
}
#[derive(Debug, Clone)]
pub struct DefaultApiEndpoint {
scheme: Option<String>,
host: String,
port: u16,
}
impl DefaultApiEndpoint {
pub fn new_default(host: impl ToString, port: u16) -> Self {
Self::new(None::<&str>, host, port)
}
pub fn new_http(host: impl ToString, port: u16) -> Self {
Self::new(Some("http"), host, port)
}
pub fn new_https(host: impl ToString, port: u16) -> Self {
Self::new(Some("https"), host, port)
}
pub fn new(scheme: Option<impl ToString>, host: impl ToString, port: u16) -> Self {
Self {
scheme: scheme.map(|s| s.to_string()),
host: host.to_string(),
port,
}
}
}
impl<T> From<(T, u16)> for DefaultApiEndpoint
where
T: ToString,
{
fn from((host, port): (T, u16)) -> Self {
Self::new_default(host, port)
}
}
impl ApiEndpoint for DefaultApiEndpoint {
fn build_url(&self, base: &Url, path: &str) -> Result<Url, RouteError> {
let mut url = base.clone();
if let Some(scheme) = self.scheme.as_ref() {
url.set_scheme(scheme.as_str())
.map_err(|_| RouteError::UpdateScheme(base.clone(), scheme.clone()))?;
}
url.set_host(Some(self.host.as_str()))
.map_err(|e| RouteError::UpdateHost(base.clone(), self.host.clone(), e))?;
url.set_port(Some(self.port))
.map_err(|_| RouteError::UpdatePort(base.clone(), self.port))?;
self.merge_path(&mut url, path);
Ok(url)
}
}