mod multi;
mod single;
use std::{any::type_name, str::FromStr};
use multi::*;
use single::*;
use crate::{async_trait, RouteError, Url};
#[async_trait]
pub trait ApiRouter: 'static + Sync + Send {
fn type_name(&self) -> &str {
type_name::<Self>()
}
fn rewrite_host(&self) -> bool {
false
}
async fn next_endpoint(&self) -> Result<Box<dyn ApiEndpoint>, RouteError>;
}
#[async_trait]
impl ApiRouter for Box<dyn ApiRouter> {
fn rewrite_host(&self) -> bool {
self.as_ref().rewrite_host()
}
async fn next_endpoint(&self) -> Result<Box<dyn ApiEndpoint>, RouteError> {
self.as_ref().next_endpoint().await
}
}
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 reserve_original_host(&self) -> bool {
false
}
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);
}
}
#[derive(Debug, Default)]
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 FromStr for DefaultApiEndpoint {
type Err = RouteError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some((host, port)) = s.split_once(':') {
if host.is_empty() {
return Err(RouteError::Custom("Invalid host".to_string()));
}
let port = match port.parse::<u16>() {
Ok(port) => port,
Err(e) => {
return Err(RouteError::Custom(format!("Invalid port: {}", e)));
}
};
return Ok(DefaultApiEndpoint::new_default(host, port));
}
Err(RouteError::Custom(format!("Invalid endpoint: {}", s)))
}
}
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)
}
}
#[cfg(test)]
mod tests {
use crate::{ApiRouter, ApiRouters};
fn dump_router<T>(router: T)
where
T: ApiRouter,
{
println!("router = {}", router.type_name());
}
#[test]
fn test_box_router() {
let boxed: Box<dyn ApiRouter> = Box::new(ApiRouters::fixed(("127.0.0.1", 80)));
dump_router(boxed);
}
}