All Downloads are FREE. Search and download functionalities are using the official Maven repository.

rust-server.server.mustache Maven / Gradle / Ivy

There is a newer version: 3.0.0-rc1
Show newest version
#![allow(unused_extern_crates)]
extern crate serde_ignored;
extern crate iron;
extern crate router;
extern crate bodyparser;
extern crate urlencoded;
extern crate uuid;
extern crate chrono;
{{#apiHasFile}}extern crate multipart;{{/apiHasFile}}

use futures::Future;
use futures::future;
use futures::{stream, Stream};
use hyper;
use hyper::header::{Headers, ContentType};
use self::iron::prelude::*;
use self::iron::{status, modifiers, BeforeMiddleware};
use self::iron::url::percent_encoding::percent_decode;
use self::router::Router;
use self::urlencoded::UrlEncodedQuery;
use mimetypes;
{{#apiHasFile}}use multipart::server::{Multipart, SaveResult};{{/apiHasFile}}

use serde_json;
{{#usesXml}}use serde_xml_rs;{{/usesXml}}

#[allow(unused_imports)]
use std::collections::{HashMap, BTreeMap};
#[allow(unused_imports)]
use swagger;
use std::io::Error;

#[allow(unused_imports)]
use std::collections::BTreeSet;

pub use swagger::auth::Authorization;
use swagger::auth::{AuthData, Scopes};
use swagger::{ApiError, Context, XSpanId};

use {Api{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}},
     {{operationId}}Response{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
     };
#[allow(unused_imports)]
use models;

header! { (Warning, "Warning") => [String] }

/// Create a new router for `Api`
pub fn router(api: T) -> Router where T: Api + Send + Sync + Clone + 'static {
    let mut router = Router::new();
    add_routes(&mut router, api);
    router
}

/// Add routes for `Api` to a provided router.
///
/// Note that these routes are added straight onto the router. This means that if the router
/// already has a route for an endpoint which clashes with those provided by this API, then the
/// old route will be lost.
///
/// It is generally a bad idea to add routes in this way to an existing router, which may have
/// routes on it for other APIs. Distinct APIs should be behind distinct paths to encourage
/// separation of interfaces, which this function does not enforce. APIs should not overlap.
///
/// Alternative approaches include:
///
/// - generate an `iron::middleware::Handler` (usually a `router::Router` or
///   `iron::middleware::chain`) for each interface, and add those handlers inside an existing
///   router, mounted at different paths - so the interfaces are separated by path
/// - use a different instance of `iron::Iron` for each interface - so the interfaces are
///   separated by the address/port they listen on
///
/// This function exists to allow legacy code, which doesn't separate its APIs properly, to make
/// use of this crate.
#[deprecated(note="APIs should not overlap - only for use in legacy code.")]
pub fn route(router: &mut Router, api: T) where T: Api + Send + Sync + Clone + 'static {
    add_routes(router, api)
}

/// Add routes for `Api` to a provided router
fn add_routes(router: &mut Router, api: T) where T: Api + Send + Sync + Clone + 'static {
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
    let api_clone = api.clone();
    router.{{#vendorExtensions}}{{httpmethod}}{{/vendorExtensions}}(
        "{{#vendorExtensions}}{{basePathWithoutHost}}{{path}}{{/vendorExtensions}}",
        move |req: &mut Request| {
            let mut context = Context::default();

            // Helper function to provide a code block to use `?` in (to be replaced by the `catch` block when it exists).
            fn handle_request(req: &mut Request, api: &T, context: &mut Context) -> Result where T: Api {

                context.x_span_id = Some(req.headers.get::().map(XSpanId::to_string).unwrap_or_else(|| self::uuid::Uuid::new_v4().to_string()));
                context.auth_data = req.extensions.remove::();
                context.authorization = req.extensions.remove::();

                {{#hasAuthMethods}}
                let authorization = context.authorization.as_ref().ok_or_else(|| {
                    Response::with((
                        status::Forbidden,
                        "Unauthenticated".to_string()
                    ))
                })?;

                {{#authMethods}}
                {{#isOAuth}}
                // Authorization
                if let Scopes::Some(ref scopes) = authorization.scopes {
                    let required_scopes: BTreeSet = vec![
                        {{#scopes}}
                        "{{scope}}".to_string(), // {{description}}
                        {{/scopes}}
                    ].into_iter().collect();

                    if !required_scopes.is_subset(scopes) {
                        let missing_scopes = required_scopes.difference(scopes);
                        return Err(Response::with((
                            status::Forbidden,
                            missing_scopes.fold(
                                "Insufficient authorization, missing scopes".to_string(),
                                |s, scope| format!("{} {}", s, scope)
                            )
                        )));
                    }
                }
                {{/isOAuth}}
                {{/authMethods}}
                {{/hasAuthMethods}}

{{#pathParams}}{{#-first}}
                // Path parameters
{{/-first}}                let param_{{paramName}} = {
                    let param = req.extensions.get::().ok_or_else(|| Response::with((status::InternalServerError, "An internal error occurred".to_string())))?
                        .find("{{baseName}}").ok_or_else(|| Response::with((status::BadRequest, "Missing path parameter {{baseName}}".to_string())))?;
                    percent_decode(param.as_bytes()).decode_utf8()
                        .map_err(|_| Response::with((status::BadRequest, format!("Couldn't percent-decode path parameter as UTF-8: {}", param))))?
                        .parse().map_err(|e| Response::with((status::BadRequest, format!("Couldn't parse path parameter {{baseName}}: {}", e))))?
                };
{{/pathParams}}
{{#headerParams}}{{#-first}}
                // Header parameters
{{/-first}}                header! { (Request{{vendorExtensions.typeName}}, "{{baseName}}") => {{#isListContainer}}({{{baseType}}})*{{/isListContainer}}{{^isListContainer}}[{{{dataType}}}]{{/isListContainer}} }
{{#required}}                let param_{{paramName}} = req.headers.get::().ok_or_else(|| Response::with((status::BadRequest, "Missing or invalid required header {{baseName}}".to_string())))?.0.clone();
{{/required}}{{^required}}                let param_{{paramName}} = req.headers.get::().map(|header| header.0.clone());
{{/required}}{{/headerParams}}
{{#queryParams}}{{#-first}}
                // Query parameters (note that non-required or collection query parameters will ignore garbage values, rather than causing a 400 response)
                let query_params = req.get::().unwrap_or_default();
{{/-first}}                let param_{{paramName}} = query_params.get("{{baseName}}")
{{#required}}                    .ok_or_else(|| Response::with((status::BadRequest, "Missing required query parameter {{baseName}}".to_string())))?
{{#isListContainer}}                    .iter().flat_map(|x| x.parse::<{{{baseType}}}>()).collect::>();
{{/isListContainer}}{{^isListContainer}}                    .first().ok_or_else(|| Response::with((status::BadRequest, "Required query parameter {{baseName}} was empty".to_string())))?
                    .parse::<{{{dataType}}}>().map_err(|e| Response::with((status::BadRequest, format!("Couldn't parse query parameter {{baseName}} - doesn't match schema: {}", e))))?;
{{/isListContainer}}{{/required}}{{^required}}{{#isListContainer}}                    .map(|list| list.iter().flat_map(|x| x.parse::<{{{baseType}}}>()).collect::>());
{{/isListContainer}}{{^isListContainer}}                    .and_then(|list| list.first()).and_then(|x| x.parse::<{{{dataType}}}>().ok());
{{/isListContainer}}{{/required}}{{/queryParams}}
{{#bodyParams}}{{#-first}}                // Body parameters (note that non-required body parameters will ignore garbage
                // values, rather than causing a 400 response). Produce warning header and logs for
                // any unused fields.
{{/-first}}{{#required}}
                let param_{{paramName}} = req.get::().map_err(|e| Response::with((status::BadRequest, format!("Couldn't parse body parameter {{baseName}} - not valid UTF-8: {}", e))))?;
{{/required}}{{^required}}
                let param_{{paramName}} = req.get::().unwrap_or(None);
{{/required}}{{#vendorExtensions}}{{^consumesPlainText}}
                let mut unused_elements = Vec::new();

                let param_{{paramName}} = if let Some(param_{{paramName}}_raw) = param_{{paramName}} { {{#consumesXml}}
                    let deserializer = &mut serde_xml_rs::de::Deserializer::new_from_reader(param_{{paramName}}_raw.as_bytes());{{/consumesXml}}{{#consumesJson}}
                    let deserializer = &mut serde_json::Deserializer::from_str(¶m_{{paramName}}_raw);{{/consumesJson}}

                    let param_{{paramName}}: Option<{{{dataType}}}> = serde_ignored::deserialize(deserializer, |path| {
                            warn!("Ignoring unknown field in body: {}", path);
                            unused_elements.push(path.to_string());
                        }){{#required}}.map_err(|e| Response::with((status::BadRequest, format!("Couldn't parse body parameter {{baseName}} - doesn't match schema: {}", e))))?{{/required}}{{^required}}.unwrap_or(None){{/required}};

                    param_{{paramName}}
                } else {
                    None
                };{{/consumesPlainText}}{{/vendorExtensions}}{{#required}}
                let param_{{paramName}} = param_{{paramName}}.ok_or_else(|| Response::with((status::BadRequest, "Missing required body parameter {{baseName}}".to_string())))?{{/required}};

{{/bodyParams}}
{{#formParams}}
    {{#-first}}
                // Form parameters
{{/-first}}{{/formParams}}{{#vendorExtensions}}{{#hasFile}}
                // Expecting a multipart form, extract and parse it now.
                let mut entries = match Multipart::from_request(req) {
                    Ok(mut multipart) => {

                        match multipart.save().temp() {
                            SaveResult::Full(entries) => {
                                Ok(entries)
                            },
                            _ => {
                                Err(Response::with((status::InternalServerError, format!("Unable to process all message parts"))))
                            },
                        }
                    },
                    Err(e) => {
                        // Unable to parse as multipart
                        Err(Response::with((status::BadRequest, format!("Couldn't parse body as multipart"))))
                    }
                }?;

{{/hasFile}}{{/vendorExtensions}}{{#allParams}}{{#isFormParam}}{{#isFile}}

                let param_{{paramName}} = entries.fields.remove("{{paramName}}");

                let param_{{paramName}} = match param_{{paramName}} {
                    Some(body) => {
                        Ok({let bytes = body.as_bytes();
                           {{^required}}Some({{/required}}
                                Box::new(stream::once(Ok(bytes.to_vec()))) as Box, Error=Error> + Send>
                            {{^required}}){{/required}}}
                        )
                    }
                    None => {Err(Response::with((status::BadRequest, format!("Body part not found!"))))}
                }?;
{{/isFile}}
                let param_{{paramName}} = {{#isFile}}{{^required}}Box::new(future::ok({{/required}}param_{{paramName}}{{^required}})){{/required}};{{/isFile}}{{^isFile}}{{^isContainer}}{{#vendorExtensions}}{{{example}}};{{/vendorExtensions}}{{/isContainer}}{{#isListContainer}}{{#required}}Vec::new();{{/required}}{{^required}}None;{{/required}}{{/isListContainer}}{{#isMapContainer}}None;{{/isMapContainer}}{{/isFile}}
    {{/isFormParam}}
{{/allParams}}

                match api.{{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}({{#allParams}}param_{{paramName}}{{#isListContainer}}.as_ref(){{/isListContainer}}, {{/allParams}}context).wait() {
                    Ok(rsp) => match rsp {
{{#responses}}
                        {{operationId}}Response::{{#vendorExtensions}}{{x-responseId}}{{/vendorExtensions}}{{#dataType}}{{^headers}}(body){{/headers}}{{#headers}}{{#-first}}{ body{{/-first}}{{/headers}}{{/dataType}}{{#headers}}{{#-first}}{{^dataType}}{ {{/dataType}}{{#dataType}}, {{/dataType}}{{/-first}}{{^-first}}, {{/-first}}{{name}}{{#-last}} }{{/-last}}{{/headers}} => {
{{^isFile}}
{{#dataType}}{{#vendorExtensions}}{{#producesPlainText}}                            let body_string = body;
{{/producesPlainText}}{{#producesXml}}
{{^has_namespace}}                            let body_string = serde_xml_rs::to_string(&body).expect("impossible to fail to serialize");{{/has_namespace}}{{#has_namespace}}
                            let mut namespaces = BTreeMap::new();
                            // An empty string is used to indicate a global namespace in xmltree.
                            namespaces.insert("".to_string(), models::namespaces::{{uppercase_data_type}}.clone());
                            let body_string = serde_xml_rs::to_string_with_namespaces(&body, namespaces).expect("impossible to fail to serialize");{{/has_namespace}}{{/producesXml}}{{#producesJson}}
                            let body_string = serde_json::to_string(&body).expect("impossible to fail to serialize");{{/producesJson}}{{/vendorExtensions}}{{/dataType}}

                            let mut response = Response::with((status::Status::from_u16({{code}}){{#dataType}}, body_string{{/dataType}}));{{/isFile}}{{#isFile}}                            body.fold(Vec::new(), |mut body, chunk| {
                                body.extend(chunk.iter());
                                future::ok::, Error>(body)
                            })

                            // Block whilst waiting for the stream to complete
                            .wait()

                            // It's possible that errors were received in the stream, if this is the case then we can't return a success response to the client and instead must return an internal error.
                            .map_err(|_| Response::with((status::InternalServerError, "An internal error occurred".to_string())))

                            // Assuming no errors then create an Iron response.
                            .map(|rsp| {
                                let mut response = Response::new();
                                response.status = Some(status::Status::from_u16({{code}}));
                                response.body = Some(Box::new(rsp));
{{/isFile}}
{{#headers}}{{#isFile}}    {{/isFile}}                            header! { (Response{{nameInCamelCase}}, "{{baseName}}") => [{{{datatype}}}] }
{{#isFile}}    {{/isFile}}                            response.headers.set(Response{{nameInCamelCase}}({{name}}));
{{/headers}}
    {{#produces}}{{#-first}}
{{#dataType}}{{#isFile}}    {{/isFile}}                            response.headers.set(ContentType(mimetypes::responses::{{#vendorExtensions}}{{uppercase_operation_id}}_{{x-uppercaseResponseId}}{{/vendorExtensions}}.clone()));{{/dataType}}
{{/-first}}{{/produces}}
{{#isFile}}    {{/isFile}}                            context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
{{#bodyParams}}{{#vendorExtensions}}{{^consumesPlainText}}                            if !unused_elements.is_empty() {
                                response.headers.set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
                            }{{/consumesPlainText}}{{/vendorExtensions}}{{/bodyParams}}
                            {{^isFile}}Ok(response){{/isFile}}{{#isFile}}    response
                            }){{/isFile}}
                        },
{{/responses}}
                    },
                    Err(_) => {
                        // Application code returned an error. This should not happen, as the implementation should
                        // return a valid response.
                        Err(Response::with((status::InternalServerError, "An internal error occurred".to_string())))
                    }
                }
            }

            handle_request(req, &api_clone, &mut context).or_else(|mut response| {
                context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
                Ok(response)
            })
        },
        "{{operationId}}");
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
}

/// Middleware to extract authentication data from request
pub struct ExtractAuthData;

impl BeforeMiddleware for ExtractAuthData {
    fn before(&self, req: &mut Request) -> IronResult<()> {
        {{#authMethods}}
        {{#isBasic}}
        {
            use hyper::header::{Authorization, Basic, Bearer};
            use std::ops::Deref;
            if let Some(basic) = req.headers.get::>() {
                req.extensions.insert::(AuthData::Basic(basic.deref().clone()));
                return Ok(());
            }
        }
        {{/isBasic}}
        {{#isOAuth}}
        {
            use hyper::header::{Authorization, Basic, Bearer};
            use std::ops::Deref;
            if let Some(bearer) = req.headers.get::>() {
                req.extensions.insert::(AuthData::Bearer(bearer.deref().clone()));
                return Ok(());
            }
        }
        {{/isOAuth}}
        {{#isApiKey}}
        {{#isKeyInHeader}}
        {
            header! { (ApiKey{{-index}}, "{{keyParamName}}") => [String] }
            if let Some(header) = req.headers.get::() {
                req.extensions.insert::(AuthData::ApiKey(header.0.clone()));
                return Ok(());
            }
        }
        {{/isKeyInHeader}}
        {{#isKeyInQuery}}
        {
            let header = match req.get_ref::() {
                Ok(query) => query.get("{{keyParamName}}").map(|v| v[0].clone()),
                _ => None
            };
            if let Some(key) = header {
                req.extensions.insert::(AuthData::ApiKey(key));
                return Ok(());
            }
        }
        {{/isKeyInQuery}}
        {{/isApiKey}}
        {{/authMethods}}

        Ok(())
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy