use crate::internal::actix::METHODS;
use crate::internal::actix::utils::OperationUpdater;
use actix_service::boxed::BoxService;
use actix_service::{ServiceFactory, Transform};
use actix_web::body::MessageBody;
use actix_web::dev::{ServiceRequest, ServiceResponse};
use actix_web::guard::Guard;
use actix_web::http::Method;
use actix_web::{Error, FromRequest, Handler, Responder};
use apistos_core::PathItemDefinition;
use apistos_models::components::Components;
use apistos_models::paths::{Operation, OperationType, PathItem};
use indexmap::IndexMap;
use log::warn;
pub fn method(method: Method) -> Route {
Route::new().method(method)
}
pub fn get() -> Route {
method(Method::GET)
}
pub fn put() -> Route {
method(Method::PUT)
}
pub fn post() -> Route {
method(Method::POST)
}
pub fn patch() -> Route {
method(Method::PATCH)
}
pub fn delete() -> Route {
method(Method::DELETE)
}
pub fn options() -> Route {
method(Method::OPTIONS)
}
pub fn head() -> Route {
method(Method::HEAD)
}
pub enum OperationTypeDoc {
OperationType(OperationType),
AllMethods,
Undocumented,
}
pub struct Route {
operation: Option<Operation>,
path_item_type: OperationTypeDoc,
components: Vec<Components>,
inner: actix_web::Route,
}
impl ServiceFactory<ServiceRequest> for Route {
type Response =
<<actix_web::Route as ServiceFactory<ServiceRequest>>::Service as actix_service::Service<ServiceRequest>>::Response;
type Error = Error;
type Config = ();
type Service = <actix_web::Route as ServiceFactory<ServiceRequest>>::Service;
type InitError = ();
type Future = <actix_web::Route as ServiceFactory<ServiceRequest>>::Future;
#[allow(clippy::unit_arg)]
fn new_service(&self, cfg: Self::Config) -> Self::Future {
self.inner.new_service(cfg)
}
}
impl Route {
#[allow(clippy::new_without_default)]
pub fn new() -> Route {
Route {
operation: None,
path_item_type: OperationTypeDoc::AllMethods,
components: Default::default(),
inner: actix_web::Route::new(),
}
}
#[doc(alias = "middleware")]
#[doc(alias = "use")] pub fn wrap<M, B>(self, mw: M) -> Self
where
M: Transform<
BoxService<ServiceRequest, ServiceResponse, Error>,
ServiceRequest,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
> + 'static,
B: MessageBody + 'static,
{
Route {
operation: self.operation,
path_item_type: self.path_item_type,
components: self.components,
inner: self.inner.wrap(mw),
}
}
pub fn method(mut self, method: Method) -> Self {
let path_item_type = match method.as_str() {
"PUT" => OperationTypeDoc::OperationType(OperationType::Put),
"POST" => OperationTypeDoc::OperationType(OperationType::Post),
"DELETE" => OperationTypeDoc::OperationType(OperationType::Delete),
"OPTIONS" => OperationTypeDoc::OperationType(OperationType::Options),
"HEAD" => OperationTypeDoc::OperationType(OperationType::Head),
"PATCH" => OperationTypeDoc::OperationType(OperationType::Patch),
"TRACE" => OperationTypeDoc::OperationType(OperationType::Trace),
"GET" => OperationTypeDoc::OperationType(OperationType::Get),
m => {
warn!("Unsupported method found: {m}, operation will not be documented");
OperationTypeDoc::Undocumented
}
};
self.path_item_type = path_item_type;
self.inner = self.inner.method(method);
self
}
pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
self.inner = self.inner.guard(guard);
self
}
pub fn to<F, Args>(mut self, handler: F) -> Self
where
F: Handler<Args>,
Args: FromRequest + 'static,
F::Output: Responder + 'static,
F::Future: PathItemDefinition,
{
if F::Future::is_visible() {
self.operation = Some(F::Future::operation());
self.components = F::Future::components();
}
self.inner = self.inner.to(handler);
self
}
}
pub(crate) struct PathDefinition {
pub(crate) path: String,
pub(crate) item: PathItem,
}
pub(crate) struct RouteWrapper {
pub(crate) def: PathDefinition,
pub(crate) component: Vec<Components>,
pub(crate) inner: actix_web::Route,
}
impl RouteWrapper {
pub(crate) fn new<S: Into<String>>(path: S, route: Route) -> Self {
let mut operations: IndexMap<OperationType, Operation> = Default::default();
let mut path_item = PathItem::default();
let path: String = path.into();
if let Some(mut operation) = route.operation {
operation.update_path_parameter_name_from_path(&path);
match route.path_item_type {
OperationTypeDoc::OperationType(path_item_type) => {
operations.insert(path_item_type, operation);
}
OperationTypeDoc::AllMethods => {
for path_item_type in METHODS {
operations.insert(*path_item_type, operation.clone());
}
}
OperationTypeDoc::Undocumented => {}
}
}
path_item.operations = operations;
Self {
def: PathDefinition { path, item: path_item },
component: route.components,
inner: route.inner,
}
}
}