#![allow(clippy::return_self_not_must_use)]
extern crate actix_service2 as actix_service;
extern crate actix_web4 as actix_web;
pub use actix_web::{
web::{
block, service, to, Bytes, BytesMut, Data, Form, FormConfig, Json, JsonConfig, Path,
PathConfig, Payload, PayloadConfig, Query, QueryConfig, ReqData,
},
HttpRequest, HttpResponse,
};
use crate::Mountable;
use actix_service::ServiceFactory;
use actix_web::{
body::MessageBody,
dev::{AppService, Handler, HttpServiceFactory, ServiceRequest, ServiceResponse, Transform},
guard::Guard,
http::Method,
Error, FromRequest, Responder,
};
use paperclip_core::v2::{
models::{
DefaultOperationRaw, DefaultPathItemRaw, DefaultSchemaRaw, HttpMethod, SecurityScheme,
},
schema::Apiv2Operation,
};
use std::{collections::BTreeMap, fmt::Debug, future::Future, mem};
const METHODS: &[Method] = &[
Method::GET,
Method::PUT,
Method::POST,
Method::DELETE,
Method::OPTIONS,
Method::HEAD,
Method::PATCH,
];
pub struct Resource<R = actix_web::Resource> {
path: String,
operations: BTreeMap<HttpMethod, DefaultOperationRaw>,
definitions: BTreeMap<String, DefaultSchemaRaw>,
security: BTreeMap<String, SecurityScheme>,
inner: R,
}
impl Resource {
pub fn new(path: &str) -> Resource {
Resource {
path: path.into(),
operations: BTreeMap::new(),
definitions: BTreeMap::new(),
security: BTreeMap::new(),
inner: actix_web::Resource::new(path),
}
}
}
impl<T, B> HttpServiceFactory for Resource<actix_web::Resource<T>>
where
T: ServiceFactory<
ServiceRequest,
Config = (),
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
> + 'static,
B: MessageBody + 'static,
{
fn register(self, config: &mut AppService) {
self.inner.register(config)
}
}
impl<T> Mountable for Resource<T> {
fn path(&self) -> &str {
&self.path
}
fn operations(&mut self) -> BTreeMap<HttpMethod, DefaultOperationRaw> {
mem::take(&mut self.operations)
}
fn definitions(&mut self) -> BTreeMap<String, DefaultSchemaRaw> {
mem::take(&mut self.definitions)
}
fn security_definitions(&mut self) -> BTreeMap<String, SecurityScheme> {
mem::take(&mut self.security)
}
}
impl<T> Resource<actix_web::Resource<T>>
where
T: ServiceFactory<ServiceRequest, Config = (), Error = Error, InitError = ()>,
{
pub fn name(mut self, name: &str) -> Self {
self.inner = self.inner.name(name);
self
}
pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
self.inner = self.inner.guard(guard);
self
}
pub fn route(mut self, route: Route) -> Self {
let w = RouteWrapper::from(&self.path, route);
self.operations.extend(w.operations);
self.definitions.extend(w.definitions);
SecurityScheme::append_map(w.security, &mut self.security);
self.inner = self.inner.route(w.inner);
self
}
pub fn app_data<U: 'static>(mut self, data: U) -> Self {
let w = self.inner.app_data(data);
self.inner = w;
self
}
pub fn to<F, Args>(mut self, handler: F) -> Self
where
F: Handler<Args>,
Args: FromRequest + 'static,
F::Output: Responder + 'static,
F::Future: Apiv2Operation,
{
self.update_from_handler::<F::Future>();
self.inner = self.inner.to(handler);
self
}
pub fn wrap<M, B>(
self,
mw: M,
) -> Resource<
actix_web::Resource<
impl ServiceFactory<
ServiceRequest,
Config = (),
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>,
>,
>
where
B: MessageBody,
M: Transform<
T::Service,
ServiceRequest,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
> + 'static,
{
Resource {
path: self.path,
operations: self.operations,
definitions: self.definitions,
security: self.security,
inner: self.inner.wrap(mw),
}
}
pub fn wrap_fn<F, R, B>(
self,
mw: F,
) -> Resource<
actix_web::Resource<
impl ServiceFactory<
ServiceRequest,
Config = (),
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>,
>,
>
where
B: MessageBody,
F: Fn(ServiceRequest, &T::Service) -> R + Clone + 'static,
R: Future<Output = Result<ServiceResponse<B>, Error>>,
{
Resource {
path: self.path,
operations: self.operations,
definitions: self.definitions,
security: self.security,
inner: self.inner.wrap_fn(mw),
}
}
pub fn default_service<F, U>(mut self, f: F) -> Self
where
F: actix_service::IntoServiceFactory<U, ServiceRequest>,
U: ServiceFactory<
ServiceRequest,
Config = (),
Response = ServiceResponse,
Error = Error,
InitError = (),
> + 'static,
U::InitError: Debug,
{
self.inner = self.inner.default_service(f);
self
}
fn update_from_handler<U>(&mut self)
where
U: Apiv2Operation,
{
let mut op = U::operation();
if U::is_visible() {
op.set_parameter_names_from_path_template(&self.path);
for method in METHODS {
self.operations.insert(method.into(), op.clone());
}
self.definitions.extend(U::definitions());
SecurityScheme::append_map(U::security_definitions(), &mut self.security);
}
}
}
pub fn resource(path: &str) -> Resource {
Resource::new(path)
}
pub struct Scope<S = actix_web::Scope> {
path: String,
path_map: BTreeMap<String, DefaultPathItemRaw>,
definitions: BTreeMap<String, DefaultSchemaRaw>,
security: BTreeMap<String, SecurityScheme>,
inner: Option<S>,
}
impl Scope {
pub fn new(path: &str) -> Self {
Scope {
path: path.into(),
path_map: BTreeMap::new(),
definitions: BTreeMap::new(),
security: BTreeMap::new(),
inner: Some(actix_web::Scope::new(path)),
}
}
}
impl<T, B> HttpServiceFactory for Scope<actix_web::Scope<T>>
where
T: ServiceFactory<
ServiceRequest,
Config = (),
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
> + 'static,
B: MessageBody + 'static,
{
fn register(self, config: &mut AppService) {
if let Some(s) = self.inner {
s.register(config);
}
}
}
impl<T> Scope<actix_web::Scope<T>>
where
T: ServiceFactory<ServiceRequest, Config = (), Error = Error, InitError = ()>,
{
pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
self.inner = self.inner.take().map(|s| s.guard(guard));
self
}
pub fn app_data<U: 'static>(mut self, data: U) -> Self {
self.inner = self.inner.take().map(|s| s.app_data(data));
self
}
pub fn configure<F>(mut self, f: F) -> Self
where
F: FnOnce(&mut ServiceConfig),
{
self.inner = self.inner.take().map(|s| {
s.configure(|c| {
let mut cfg = ServiceConfig::from(c);
f(&mut cfg);
self.update_from_mountable(&mut cfg);
})
});
self
}
pub fn service<F>(mut self, mut factory: F) -> Self
where
F: Mountable + HttpServiceFactory + 'static,
{
self.update_from_mountable(&mut factory);
self.inner = self.inner.take().map(|s| s.service(factory));
self
}
pub fn route(mut self, path: &str, route: Route) -> Self {
let mut w = RouteWrapper::from(path, route);
self.update_from_mountable(&mut w);
self.inner = self.inner.take().map(|s| s.route(path, w.inner));
self
}
pub fn default_service<F, U>(mut self, f: F) -> Self
where
F: actix_service::IntoServiceFactory<U, ServiceRequest>,
U: ServiceFactory<
ServiceRequest,
Config = (),
Response = ServiceResponse,
Error = Error,
InitError = (),
> + 'static,
U::InitError: Debug,
{
self.inner = self.inner.map(|s| s.default_service(f));
self
}
pub fn wrap<M, B>(
mut self,
mw: M,
) -> Scope<
actix_web::Scope<
impl ServiceFactory<
ServiceRequest,
Config = (),
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>,
>,
>
where
M: Transform<
T::Service,
ServiceRequest,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
> + 'static,
B: MessageBody,
{
Scope {
path: self.path,
path_map: self.path_map,
definitions: self.definitions,
security: self.security,
inner: self.inner.take().map(|s| s.wrap(mw)),
}
}
pub fn wrap_fn<F, R>(
mut self,
mw: F,
) -> Scope<
actix_web::Scope<
impl ServiceFactory<
ServiceRequest,
Config = (),
Response = ServiceResponse,
Error = Error,
InitError = (),
>,
>,
>
where
F: Fn(ServiceRequest, &T::Service) -> R + Clone + 'static,
R: Future<Output = Result<ServiceResponse, Error>>,
{
Scope {
path: self.path,
path_map: self.path_map,
definitions: self.definitions,
security: self.security,
inner: self.inner.take().map(|s| s.wrap_fn(mw)),
}
}
fn update_from_mountable<M>(&mut self, factory: &mut M)
where
M: Mountable,
{
self.definitions.extend(factory.definitions());
let mut path_map = BTreeMap::new();
factory.update_operations(&mut path_map);
for (path, mut map) in path_map {
let p = if !self.path.ends_with('/') && !path.starts_with('/') {
self.path.clone() + "/" + &path
} else {
self.path.clone() + &path
};
for op in map.methods.values_mut() {
op.set_parameter_names_from_path_template(&p);
}
if let Some(existing) = self.path_map.get_mut(&p) {
existing.methods.append(&mut map.methods);
existing.parameters.append(&mut map.parameters);
} else {
self.path_map.insert(p.clone(), map);
}
}
SecurityScheme::append_map(factory.security_definitions(), &mut self.security);
}
}
impl<T> Mountable for Scope<T> {
fn path(&self) -> &str {
unimplemented!("Scope has multiple paths. Use `update_operations` object instead.");
}
fn operations(&mut self) -> BTreeMap<HttpMethod, DefaultOperationRaw> {
unimplemented!("Scope has multiple operation maps. Use `update_operations` object instead.")
}
fn security_definitions(&mut self) -> BTreeMap<String, SecurityScheme> {
mem::take(&mut self.security)
}
fn definitions(&mut self) -> BTreeMap<String, DefaultSchemaRaw> {
mem::take(&mut self.definitions)
}
fn update_operations(&mut self, map: &mut BTreeMap<String, DefaultPathItemRaw>) {
for (path, item) in mem::take(&mut self.path_map) {
let op_map = map.entry(path).or_default();
op_map.methods.extend(item.methods);
}
}
}
pub fn scope(path: &str) -> Scope {
Scope::new(path)
}
pub struct Route {
method: Option<HttpMethod>,
operation: Option<DefaultOperationRaw>,
definitions: BTreeMap<String, DefaultSchemaRaw>,
security: BTreeMap<String, SecurityScheme>,
inner: actix_web::Route,
}
impl ServiceFactory<ServiceRequest> for Route {
type Config = ();
type Error = Error;
type InitError = ();
type Service = <actix_web::Route as ServiceFactory<ServiceRequest>>::Service;
type Future = <actix_web::Route as ServiceFactory<ServiceRequest>>::Future;
type Response =
<<actix_web::Route as ServiceFactory<ServiceRequest>>::Service as actix_service::Service<
ServiceRequest,
>>::Response;
#[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 {
method: None,
operation: None,
definitions: BTreeMap::new(),
security: BTreeMap::new(),
inner: actix_web::Route::new(),
}
}
pub fn method(mut self, method: Method) -> Self {
self.method = Some(HttpMethod::from(&method));
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: Apiv2Operation,
{
if F::Future::is_visible() {
self.operation = Some(F::Future::operation());
self.definitions = F::Future::definitions();
self.security = F::Future::security_definitions();
}
self.inner = self.inner.to(handler);
self
}
}
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(crate) struct RouteWrapper<S> {
path: S,
pub(crate) operations: BTreeMap<HttpMethod, DefaultOperationRaw>,
pub(crate) definitions: BTreeMap<String, DefaultSchemaRaw>,
pub(crate) security: BTreeMap<String, SecurityScheme>,
pub(crate) inner: actix_web::Route,
}
impl<S> RouteWrapper<S>
where
S: AsRef<str>,
{
pub(crate) fn from(path: S, route: Route) -> Self {
let mut operations = BTreeMap::new();
if let Some(mut op) = route.operation {
op.set_parameter_names_from_path_template(path.as_ref());
if let Some(meth) = route.method {
operations.insert(meth, op);
} else {
for method in METHODS {
operations.insert(method.into(), op.clone());
}
}
}
RouteWrapper {
path,
operations,
definitions: route.definitions,
security: route.security,
inner: route.inner,
}
}
}
impl<S> Mountable for RouteWrapper<S>
where
S: AsRef<str>,
{
fn path(&self) -> &str {
self.path.as_ref()
}
fn operations(&mut self) -> BTreeMap<HttpMethod, DefaultOperationRaw> {
mem::take(&mut self.operations)
}
fn security_definitions(&mut self) -> BTreeMap<String, SecurityScheme> {
mem::take(&mut self.security)
}
fn definitions(&mut self) -> BTreeMap<String, DefaultSchemaRaw> {
mem::take(&mut self.definitions)
}
}
pub struct ServiceConfig<'a> {
path_map: BTreeMap<String, DefaultPathItemRaw>,
definitions: BTreeMap<String, DefaultSchemaRaw>,
security: BTreeMap<String, SecurityScheme>,
inner: &'a mut actix_web::web::ServiceConfig,
}
impl<'a> From<&'a mut actix_web::web::ServiceConfig> for ServiceConfig<'a> {
fn from(cfg: &'a mut actix_web::web::ServiceConfig) -> Self {
ServiceConfig {
path_map: BTreeMap::new(),
definitions: BTreeMap::new(),
security: BTreeMap::new(),
inner: cfg,
}
}
}
impl<'a> Mountable for ServiceConfig<'a> {
fn path(&self) -> &str {
unimplemented!("ServiceConfig has multiple paths. Use `update_operations` object instead.");
}
fn operations(&mut self) -> BTreeMap<HttpMethod, DefaultOperationRaw> {
unimplemented!(
"ServiceConfig has multiple operation maps. Use `update_operations` object instead."
)
}
fn security_definitions(&mut self) -> BTreeMap<String, SecurityScheme> {
mem::take(&mut self.security)
}
fn definitions(&mut self) -> BTreeMap<String, DefaultSchemaRaw> {
mem::take(&mut self.definitions)
}
fn update_operations(&mut self, map: &mut BTreeMap<String, DefaultPathItemRaw>) {
for (path, item) in mem::take(&mut self.path_map) {
let op_map = map.entry(path).or_default();
op_map.methods.extend(item.methods);
}
}
}
impl<'a> ServiceConfig<'a> {
pub fn route(&mut self, path: &str, route: Route) -> &mut Self {
let mut w = RouteWrapper::from(path, route);
self.definitions.extend(w.definitions());
w.update_operations(&mut self.path_map);
SecurityScheme::append_map(w.security, &mut self.security);
self.inner.route(path, w.inner);
self
}
pub fn service<F>(&mut self, mut factory: F) -> &mut Self
where
F: Mountable + HttpServiceFactory + 'static,
{
self.definitions.extend(factory.definitions());
factory.update_operations(&mut self.path_map);
SecurityScheme::append_map(factory.security_definitions(), &mut self.security);
self.inner.service(factory);
self
}
pub fn external_resource<N, U>(&mut self, name: N, url: U) -> &mut Self
where
N: AsRef<str>,
U: AsRef<str>,
{
self.inner.external_resource(name, url);
self
}
pub fn app_data<U: 'static>(&mut self, data: U) -> &mut Self {
self.inner.app_data(data);
self
}
}