paperclip-ng 0.1.3

Experimental OpenAPI V3.0.3 Code Generator
Documentation
use actix_web::http::StatusCode;
use actix_web::{web::ServiceConfig, FromRequest, HttpResponse, ResponseError};
use serde::Serialize;
use std::{
    fmt::{self, Debug, Display, Formatter},
    ops,
};

{{#apiInfo}}
{{#apis}}
{{#operations}}
{{#operation}}
{{#-last}}
pub use crate::apis::{{{classFilename}}}::actix::server::{{{classname}}};
{{/-last}}
{{/operation}}
{{/operations}}
{{/apis}}
{{/apiInfo}}

/// Rest Error wrapper with a status code and a JSON error
/// Note: Only a single error type for each handler is supported at the moment
pub struct RestError<T: Debug + Serialize> {
    status_code: StatusCode,
    error_response: T,
}

impl<T: Debug + Serialize> RestError<T> {
    pub fn new(status_code: StatusCode, error_response: T) -> Self {
        Self {
            status_code,
            error_response
        }
    }
}

impl<T: Debug + Serialize> Debug for RestError<T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.debug_struct("RestError")
            .field("status_code", &self.status_code)
            .field("error_response", &self.error_response)
            .finish()
    }
}

impl<T: Debug + Serialize> Display for RestError<T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{self:?}")
    }
}

impl<T: Debug + Serialize> ResponseError for RestError<T> {
    fn status_code(&self) -> StatusCode {
        self.status_code
    }

    fn error_response(&self) -> HttpResponse {
        HttpResponse::build(self.status_code).json(&self.error_response)
    }
}

/// 204 Response with no content
#[derive(Default)]
pub(crate) struct NoContent;

impl From<actix_web::web::Json<()>> for NoContent {
    fn from(_: actix_web::web::Json<()>) -> Self {
        NoContent {}
    }
}
impl From<()> for NoContent {
    fn from(_: ()) -> Self {
        NoContent {}
    }
}
impl actix_web::Responder for NoContent {
    {{^actixWeb4Beta}}type Body = actix_web::body::BoxBody;{{/actixWeb4Beta}}

    fn respond_to(self, _: &actix_web::HttpRequest) -> actix_web::HttpResponse {
        actix_web::HttpResponse::NoContent().finish()
    }
}

/// Wrapper type used as tag to easily distinguish the 3 different parameter types:
/// 1. Path 2. Query 3. Body
/// Example usage:
/// fn delete_resource(Path((p1, p2)): Path<(String, u64)>) { ... }
pub struct Path<T>(pub T);

impl<T> Path<T> {
    /// Deconstruct to an inner value
    pub fn into_inner(self) -> T {
        self.0
    }
}

impl<T> AsRef<T> for Path<T> {
    fn as_ref(&self) -> &T {
        &self.0
    }
}

impl<T> ops::Deref for Path<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}

impl<T> ops::DerefMut for Path<T> {
    fn deref_mut(&mut self) -> &mut T {
        &mut self.0
    }
}

/// Wrapper type used as tag to easily distinguish the 3 different parameter types:
/// 1. Path 2. Query 3. Body
/// Example usage:
/// fn delete_resource(Path((p1, p2)): Path<(String, u64)>) { ... }
pub struct Query<T>(pub T);

impl<T> Query<T> {
    /// Deconstruct to an inner value
    pub fn into_inner(self) -> T {
        self.0
    }
}

impl<T> AsRef<T> for Query<T> {
    fn as_ref(&self) -> &T {
        &self.0
    }
}

impl<T> ops::Deref for Query<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}

impl<T> ops::DerefMut for Query<T> {
    fn deref_mut(&mut self) -> &mut T {
        &mut self.0
    }
}

/// Wrapper type used as tag to easily distinguish the 3 different parameter types:
/// 1. Path 2. Query 3. Body
/// Example usage:
/// fn delete_resource(Path((p1, p2)): Path<(String, u64)>) { ... }
pub struct Body<T>(pub T);

impl<T> Body<T> {
    /// Deconstruct to an inner value
    pub fn into_inner(self) -> T {
        self.0
    }
}

impl<T> AsRef<T> for Body<T> {
    fn as_ref(&self) -> &T {
        &self.0
    }
}

impl<T> ops::Deref for Body<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}

impl<T> ops::DerefMut for Body<T> {
    fn deref_mut(&mut self) -> &mut T {
        &mut self.0
    }
}

/// Configure all actix server handlers
pub fn configure<T: {{#apiInfo}}{{#apis}}{{#operations}}{{^-last}}{{{classname}}} + {{/-last}}{{#-last}}{{{classname}}} + 'static{{#hasAuthMethods}}, A: FromRequest + 'static{{/hasAuthMethods}}{{/-last}}{{/operations}}{{/apis}}{{/apiInfo}}>(cfg: &mut ServiceConfig) {
{{#apiInfo}}
{{#apis}}
{{#operations}}
{{#operation}}
{{#-last}}
    crate::apis::{{{classFilename}}}::actix::server::handlers::configure::<T{{#hasAuthMethods}}, A{{/hasAuthMethods}}>(cfg);
{{/-last}}
{{/operation}}
{{/operations}}
{{/apis}}
{{/apiInfo}}
}

/// Used with Query to deserialize into Vec<I>.
#[allow(dead_code)]
pub(crate) fn deserialize_stringified_list<'de, D, I>(
    deserializer: D,
) -> std::result::Result<Vec<I>, D::Error>
where
    D: serde::de::Deserializer<'de>,
    I: serde::de::DeserializeOwned,
{
    struct StringVecVisitor<I>(std::marker::PhantomData<I>);

    impl<'de, I> serde::de::Visitor<'de> for StringVecVisitor<I>
    where
        I: serde::de::DeserializeOwned,
    {
        type Value = Vec<I>;

        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
            formatter.write_str("a string containing a list")
        }

        fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
        where
            E: serde::de::Error,
        {
            let mut list = Vec::new();
            if !v.is_empty() {
                for item in v.split(',') {
                    let item = I::deserialize(serde::de::IntoDeserializer::into_deserializer(item))?;
                    list.push(item);
                }
            }
            Ok(list)
        }
    }

    deserializer.deserialize_any(StringVecVisitor(std::marker::PhantomData::<I>))
}

/// Used with Query to deserialize into Option<Vec<I>>.
#[allow(dead_code)]
pub(crate) fn deserialize_option_stringified_list<'de, D, I>(
    deserializer: D,
) -> std::result::Result<Option<Vec<I>>, D::Error>
where
    D: serde::de::Deserializer<'de>,
    I: serde::de::DeserializeOwned,
{
    let list = deserialize_stringified_list(deserializer)?;
    match list.is_empty() {
        true => Ok(None),
        false => Ok(Some(list)),
    }
}