use std::{rc::Rc, str::FromStr};
use actix_service::{IntoServiceFactory, ServiceFactory, ServiceFactoryExt, boxed};
use actix_web::{
Error, HttpResponse,
dev::{ServiceRequest, ServiceResponse},
guard::{Guard, GuardContext},
http::{StatusCode, Uri, header, uri::PathAndQuery},
mime,
};
use crate::{
next::{IsStatus, Next},
service::{HttpNewService, HttpService},
};
#[derive(Clone)]
pub struct Link {
prefix: String,
guards: Vec<Rc<dyn Guard>>,
next: Vec<Rc<dyn Next>>,
service: Rc<HttpNewService>,
}
impl Link {
pub fn new<F, U>(service: F) -> Self
where
F: IntoServiceFactory<U, ServiceRequest>,
U: ServiceFactory<ServiceRequest, Config = (), Response = ServiceResponse, Error = Error>
+ 'static,
{
Self {
prefix: String::new(),
guards: Vec::new(),
next: Vec::new(),
service: Rc::new(boxed::factory(service.into_factory().map_init_err(|_| ()))),
}
}
pub fn prefix<S: Into<String>>(mut self, prefix: S) -> Self {
self.prefix = prefix.into();
self
}
pub fn guard<G: Guard + 'static>(mut self, guards: G) -> Self {
self.guards.push(Rc::new(guards));
self
}
pub fn next<N>(mut self, next: N) -> Self
where
N: Next + 'static,
{
self.next.push(Rc::new(next));
self
}
pub(crate) async fn into_inner(&self) -> Result<LinkInner, ()> {
let guard = match self.guards.is_empty() {
true => None,
false => Some(AllGuard(self.guards.clone())),
};
let next: Vec<Rc<dyn Next>> = match self.next.is_empty() {
true => vec![Rc::new(IsStatus::new(StatusCode::NOT_FOUND))],
false => self.next.clone(),
};
Ok(LinkInner {
guard,
next,
prefix: self.prefix.clone(),
service: Rc::new(self.service.new_service(()).await?),
})
}
}
struct AllGuard(Vec<Rc<dyn Guard>>);
impl Guard for AllGuard {
#[inline]
fn check(&self, ctx: &actix_web::guard::GuardContext<'_>) -> bool {
self.0.iter().all(|g| g.check(ctx))
}
}
#[inline]
pub(crate) fn default_response(req: ServiceRequest) -> ServiceResponse {
req.into_response(
HttpResponse::NotFound()
.insert_header(header::ContentType(mime::TEXT_PLAIN_UTF_8))
.body("Not Found"),
)
}
pub(crate) struct LinkInner {
prefix: String,
guard: Option<AllGuard>,
pub(crate) service: Rc<HttpService>,
pub(crate) next: Vec<Rc<dyn Next>>,
}
impl LinkInner {
pub(crate) fn new_uri(&self, uri: &Uri) -> Option<Uri> {
if self.prefix.is_empty() {
return None;
}
let mut parts = uri.clone().into_parts();
parts.path_and_query = parts
.path_and_query
.and_then(|pq| PathAndQuery::from_str(pq.as_str().strip_prefix(&self.prefix)?).ok());
Uri::from_parts(parts).ok()
}
#[inline]
pub(crate) fn matches(&self, path: &str, ctx: &GuardContext) -> bool {
path.starts_with(&self.prefix) && self.guard.as_ref().map(|g| !g.check(ctx)).unwrap_or(true)
}
#[inline]
pub(crate) fn go_next(&self, res: &HttpResponse) -> bool {
self.next.iter().any(|next| next.next(res))
}
#[inline]
pub(crate) async fn call_once(
&self,
mut req: ServiceRequest,
) -> Result<ServiceResponse, Error> {
if !self.matches(req.uri().path(), &req.guard_ctx()) {
return Ok(default_response(req));
}
if let Some(uri) = self.new_uri(req.uri()) {
req.head_mut().uri = uri;
}
self.service.call(req).await
}
}