actix_swagger/
lib.rs

1#![deny(warnings)]
2
3mod error;
4
5pub use error::Error;
6
7use actix_http::Method;
8use actix_web::{
9    cookie::Cookie,
10    dev::{AppService, HttpServiceFactory},
11    http::header::{self, HeaderName, HeaderValue, TryIntoHeaderValue},
12    FromRequest, HttpRequest, HttpResponse, Responder, Route, Scope,
13};
14use serde::Serialize;
15use std::collections::HashMap;
16
17use actix_http::body::BoxBody;
18use actix_web::dev::Handler;
19pub use actix_web::http::StatusCode;
20use std::future::Future;
21
22/// Set content-type supported by actix-swagger
23#[derive(Debug)]
24pub enum ContentType {
25    Json,
26    FormData,
27    // TextPlain,
28}
29
30impl ToString for ContentType {
31    fn to_string(&self) -> String {
32        match self {
33            ContentType::Json => "application/json".to_string(),
34            // ContentType::TextPlain => "text/plain".to_string(),
35            ContentType::FormData => "application/x-www-form-urlencoded".to_string(),
36        }
37    }
38}
39
40/// Strict answer to complain with generated code by cargo-swagg
41pub struct Answer<'a, T> {
42    response: T,
43    status_code: Option<StatusCode>,
44    cookies: Vec<Cookie<'a>>,
45    headers: HashMap<String, HeaderValue>,
46    content_type: Option<ContentType>,
47}
48
49impl<'a, T: Serialize> Answer<'a, T> {
50    pub fn new(response: T) -> Answer<'a, T> {
51        Answer {
52            response,
53            status_code: None,
54            cookies: vec![],
55            headers: HashMap::new(),
56            content_type: None,
57        }
58    }
59
60    /// Set header to answer
61    pub fn header<V>(mut self, key: String, value: V) -> Self
62    where
63        V: TryIntoHeaderValue,
64    {
65        if let Ok(value) = value.try_into_value() {
66            self.headers.insert(key, value);
67        }
68
69        self
70    }
71
72    /// Add cookie to answer
73    pub fn cookie(mut self, cookie: Cookie<'a>) -> Self {
74        self.cookies.push(cookie);
75
76        self
77    }
78
79    /// Set status code
80    pub fn status(mut self, status: StatusCode) -> Self {
81        self.status_code = Some(status);
82
83        self
84    }
85
86    /// Set content-type
87    /// Content-Type changes serializer for answer
88    pub fn content_type(mut self, content_type: Option<ContentType>) -> Self {
89        self.content_type = content_type;
90
91        self
92    }
93
94    /// Serialize answer
95    pub fn to_string(&self) -> Result<String, Error> {
96        match self.content_type {
97            Some(ContentType::Json) => Ok(serde_json::to_string(&self.response)?),
98            Some(ContentType::FormData) => Ok(serde_urlencoded::to_string(&self.response)?),
99            // Some(ContentType::TextPlain) => Ok(serde_plain::to_string(&self.response)?),
100            _ => Ok("".to_owned()),
101        }
102    }
103}
104
105impl<'a, T: Serialize> Responder for Answer<'a, T> {
106    type Body = BoxBody;
107
108    fn respond_to(self, _: &HttpRequest) -> HttpResponse {
109        let body = match self.to_string() {
110            Ok(body) => body,
111            Err(e) => return HttpResponse::from_error(e),
112        };
113
114        let mut response = &mut HttpResponse::build(self.status_code.unwrap_or(StatusCode::OK));
115
116        if let Some(content_type) = self.content_type {
117            response = response.append_header((header::CONTENT_TYPE, content_type.to_string()));
118        }
119
120        for (name, value) in self.headers {
121            if let Ok(header_name) = name.parse::<HeaderName>() {
122                response = response.append_header((header_name, value))
123            }
124        }
125
126        for cookie in self.cookies {
127            response = response.cookie(cookie);
128        }
129
130        response.body(body)
131    }
132}
133
134// https://actix.rs/docs/errors/
135
136/// Handler scope and routes
137pub struct Api {
138    root: Scope,
139    resources: HashMap<String, Route>,
140}
141
142impl Default for Api {
143    fn default() -> Self {
144        Self::new()
145    }
146}
147
148impl Api {
149    pub fn new() -> Self {
150        Api {
151            root: Scope::new(""),
152            resources: HashMap::new(),
153        }
154    }
155
156    /// Attach route to path
157    pub fn bind<T, F, R>(mut self, path: &str, method: Method, handler: F) -> Self
158    where
159        T: FromRequest + 'static,
160        R: Future + 'static,
161        R::Output: Responder + 'static,
162        F: Handler<T, Future = R>,
163        F::Output: Responder + 'static,
164    {
165        take_mut::take(
166            self.resources
167                .entry(path.to_owned())
168                .or_insert_with(Route::new),
169            |route| route.method(method).to(handler),
170        );
171
172        self
173    }
174}
175
176impl HttpServiceFactory for Api {
177    fn register(mut self, config: &mut AppService) {
178        let keys: Vec<String> = self.resources.keys().cloned().collect();
179
180        for key in keys.iter() {
181            if let Some(resource) = self.resources.remove(key) {
182                self.root = self.root.route(key, resource);
183            }
184        }
185
186        self.root.register(config);
187    }
188}