use std::{marker::PhantomData, rc::Rc};
use crate::error::{Failure, IntoFailure};
use crate::router::{IntoPattern, ResourceDef};
use crate::service::{IntoServiceFactory, ServiceFactory, boxed};
use super::error::{WebError, WebResponseError};
use super::guard::{AllGuard, Guard};
use super::{AppState, HttpService, WebRequest, WebResponse, dev::insert_slash, rmap::ResourceMap};
pub trait WebServiceFactory<St: AppState, In>: 'static {
fn register(self, config: &mut WebServiceConfig<St, In>);
}
pub(super) trait AppServiceFactory<St: AppState, In> {
fn register(&mut self, config: &mut WebServiceConfig<St, In>);
}
pub(super) struct ServiceFactoryWrapper<T> {
factory: Option<T>,
}
impl<T> ServiceFactoryWrapper<T> {
pub(super) fn new(factory: T) -> Self {
Self {
factory: Some(factory),
}
}
}
impl<T, St, In> AppServiceFactory<St, In> for ServiceFactoryWrapper<T>
where
T: WebServiceFactory<St, In>,
St: AppState,
{
fn register(&mut self, config: &mut WebServiceConfig<St, In>) {
if let Some(item) = self.factory.take() {
item.register(config);
}
}
}
type Guards = Vec<Box<dyn Guard>>;
#[derive(derive_more::Debug)]
#[debug("WebServiceConfig")]
pub struct WebServiceConfig<St: AppState, In = ()> {
root: bool,
services: Vec<(
ResourceDef,
HttpService<St, In>,
Option<Guards>,
Option<Rc<ResourceMap>>,
)>,
}
impl<St: AppState, In: 'static> WebServiceConfig<St, In> {
pub(crate) fn new() -> Self {
WebServiceConfig {
root: true,
services: Vec::new(),
}
}
pub fn is_root(&self) -> bool {
self.root
}
pub(crate) fn into_services(
self,
) -> Vec<(
ResourceDef,
HttpService<St, In>,
Option<Guards>,
Option<Rc<ResourceMap>>,
)> {
self.services
}
pub fn register_service<S>(
&mut self,
rdef: ResourceDef,
guards: Option<Vec<Box<dyn Guard>>>,
nested: Option<Rc<ResourceMap>>,
factory: impl IntoServiceFactory<S, St, WebRequest<In>>,
) where
S: ServiceFactory<
St,
WebRequest<In>,
Res = WebResponse,
Error = WebError<St, St::Error>,
InitError = Failure,
> + 'static,
{
self.services
.push((rdef, boxed::factory(factory.into_factory()), guards, nested));
}
}
#[derive(Debug)]
pub struct WebServiceAdapter {
rdef: Vec<String>,
name: Option<String>,
guards: AllGuard,
}
impl WebServiceAdapter {
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn new<T: IntoPattern>(path: T) -> Self {
WebServiceAdapter {
rdef: path.patterns(),
name: None,
guards: AllGuard::default(),
}
}
#[must_use]
pub fn name(mut self, name: &str) -> Self {
self.name = Some(name.to_string());
self
}
#[must_use]
pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
self.guards.add(guard);
self
}
pub fn build<Sf, St, In, F>(self, service: F) -> impl WebServiceFactory<St, In>
where
St: AppState,
In: 'static,
F: IntoServiceFactory<Sf, St, WebRequest<In>>,
Sf: ServiceFactory<St, WebRequest<In>, Res = WebResponse> + 'static,
Sf::Error: WebResponseError<St, St::Error>,
Sf::InitError: IntoFailure,
{
WebServiceImpl {
srv: service
.into_factory()
.map_err(WebError::from_err)
.map_init_err(IntoFailure::fail),
rdef: self.rdef,
name: self.name,
guards: self.guards,
ph: PhantomData,
}
}
}
struct WebServiceImpl<Sf, In> {
srv: Sf,
rdef: Vec<String>,
name: Option<String>,
guards: AllGuard,
ph: PhantomData<In>,
}
impl<Sf, In, St> WebServiceFactory<St, In> for WebServiceImpl<Sf, In>
where
St: AppState,
In: 'static,
Sf: ServiceFactory<
St,
WebRequest<In>,
Res = WebResponse,
Error = WebError<St, St::Error>,
InitError = Failure,
> + 'static,
{
fn register(mut self, config: &mut WebServiceConfig<St, In>) {
let guards = if self.guards.0.is_empty() {
None
} else {
Some(std::mem::take(&mut self.guards.0))
};
let mut rdef = if config.is_root() || !self.rdef.is_empty() {
ResourceDef::new(insert_slash(self.rdef))
} else {
ResourceDef::new(self.rdef)
};
if let Some(ref name) = self.name {
rdef.name_mut().clone_from(name);
}
config.register_service(rdef, guards, None, self.srv);
}
}
#[allow(unused_parens)]
impl<T, St, In> WebServiceFactory<St, In> for Vec<T>
where
T: WebServiceFactory<St, In> + 'static,
St: AppState,
{
fn register(mut self, config: &mut WebServiceConfig<St, In>) {
for service in self.drain(..) {
service.register(config);
}
}
}
macro_rules! tuple_web_service(
{$(#[$meta:meta])* $(($n:tt, $T:ident)),+} => {
$(#[$meta])*
impl<St: AppState, In, $($T: WebServiceFactory<St, In> + 'static),+> WebServiceFactory<St, In> for ($($T,)+) {
fn register(self, config: &mut WebServiceConfig<St, In>) {
$(
self.$n.register(config);
)+
}
}
}
);
impl<St, In, T, const N: usize> WebServiceFactory<St, In> for [T; N]
where
St: AppState,
T: WebServiceFactory<St, In> + 'static,
{
fn register(self, config: &mut WebServiceConfig<St, In>) {
for t in self {
t.register(config);
}
}
}
#[allow(non_snake_case, clippy::wildcard_imports)]
#[rustfmt::skip]
mod m {
use super::*;
use variadics_please::all_tuples_enumerated;
all_tuples_enumerated!(#[doc(fake_variadic)] tuple_web_service, 1, 24, T);
}
#[cfg(test)]
mod tests {
use std::convert::Infallible;
use super::*;
use crate::http::{Method, StatusCode};
use crate::web::test::{TestRequest, init_service};
use crate::web::{self, App, DefaultError, HttpResponse, guard};
#[crate::rt_test]
async fn test_service() {
let srv = init_service(App::new().service(web::service("/test").name("test").build(
async move |req: WebRequest<()>| {
Ok::<_, Infallible>(req.into_response(HttpResponse::Ok().build()))
},
)))
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let srv = init_service(
App::new().service(web::service("/test").guard(guard::Get()).build(
async move |req: WebRequest<()>| {
Ok::<_, DefaultError>(req.into_response(HttpResponse::Ok().build()))
},
)),
)
.await;
let req = TestRequest::with_uri("/test")
.method(Method::PUT)
.to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
}
#[crate::rt_test]
async fn test_multi() {
let srv = init_service(App::new().service([
web::resource("/test1").to(async || HttpResponse::Ok()),
web::resource("/test2").to(async || HttpResponse::Ok()),
]))
.await;
let req = TestRequest::with_uri("/test1").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test2").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let srv = init_service(App::new().service((
web::resource("/test1").to(async || HttpResponse::Ok()),
web::resource("/test2").to(async || HttpResponse::Ok()),
)))
.await;
let req = TestRequest::with_uri("/test1").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test2").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let srv = init_service(App::new().service(vec![
web::resource("/test1").to(async || HttpResponse::Ok()),
web::resource("/test2").to(async || HttpResponse::Ok()),
]))
.await;
let req = TestRequest::with_uri("/test1").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test2").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn test_fmt_debug() {
let req = TestRequest::get()
.uri("/index.html?test=1")
.header("x-test", "111")
.to_srv_request();
let s = format!("{req:?}");
assert!(s.contains("WebRequest"));
assert!(s.contains("test=1"));
assert!(s.contains("x-test"));
let res = HttpResponse::Ok().header("x-test", "111").build();
let res = TestRequest::post()
.uri("/index.html?test=1")
.to_srv_response(res);
let s = format!("{res:?}");
assert!(s.contains("WebResponse"));
assert!(s.contains("x-test"));
}
}