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

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

There is a newer version: 3.0.0-rc1
Show newest version
#![allow(unused_extern_crates)]
extern crate hyper_openssl;
extern crate chrono;
extern crate url;
{{#apiHasFile}}extern crate multipart;{{/apiHasFile}}

{{#apiHasFile}}use multipart::client::lazy::Multipart;{{/apiHasFile}}
use hyper;
use hyper::client::IntoUrl;
use hyper::mime;
use hyper::header::{Headers, ContentType};
use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value};
use hyper::Url;
use self::hyper_openssl::openssl;
use self::url::percent_encoding::{utf8_percent_encode, PATH_SEGMENT_ENCODE_SET, QUERY_ENCODE_SET};
use futures;
use futures::{Future, Stream};
use futures::{future, stream};
use std::borrow::Cow;
use std::io::{Read, Error};
use std::error;
use std::fmt;
use std::path::Path;
use std::sync::Arc;
use std::str;

use mimetypes;

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

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

use swagger::{Context, ApiError, XSpanId};

use {Api{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}},
     {{operationId}}Response{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
     };
use models;

/// Convert input into a base path, e.g. "http://example:123". Also checks the scheme as it goes.
fn into_base_path(input: T, correct_scheme: Option<&'static str>) -> Result {
    // First convert to Url, since a base path is a subset of Url.
    let url = input.into_url()?;

    let scheme = url.scheme();

    // Check the scheme if necessary
    if let Some(correct_scheme) = correct_scheme {
        if scheme != correct_scheme {
            return Err(ClientInitError::InvalidScheme);
        }
    }

    let host = url.host().ok_or_else(|| ClientInitError::MissingHost)?;
    let port = url.port().map(|x| format!(":{}", x)).unwrap_or_default();
    Ok(format!("{}://{}{}", scheme, host, port))
}

/// A client that implements the API by making HTTP calls out to a server.
#[derive(Clone)]
pub struct Client {
    base_path: String,
    hyper_client: Arc hyper::client::Client + Sync + Send>,
}

impl fmt::Debug for Client {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Client {{ base_path: {} }}", self.base_path)
    }
}

impl Client {
    pub fn try_new_http(base_path: T) -> Result
        where T: IntoUrl
    {
        Ok(Client {
            base_path: into_base_path(base_path, Some("http"))?,
            hyper_client: Arc::new(hyper::client::Client::new),
        })
    }

    pub fn try_new_https(base_path: T,
                                ca_certificate: CA)
                            -> Result
        where T: IntoUrl,
              CA: AsRef
    {
        let ca_certificate = ca_certificate.as_ref().to_owned();

        let https_hyper_client = move || {
            // SSL implementation
            let mut ssl = openssl::ssl::SslConnectorBuilder::new(openssl::ssl::SslMethod::tls()).unwrap();

            // Server authentication
            ssl.set_ca_file(ca_certificate.clone()).unwrap();

            let ssl = hyper_openssl::OpensslClient::from(ssl.build());
            let connector = hyper::net::HttpsConnector::new(ssl);
            hyper::client::Client::with_connector(connector)
        };

        Ok(Client {
                base_path: into_base_path(base_path, Some("https"))?,
                hyper_client: Arc::new(https_hyper_client),
            })
    }

    pub fn try_new_https_mutual(base_path: T,
                                             ca_certificate: CA,
                                             client_key: K,
                                             client_certificate: C)
                                             -> Result
        where T: IntoUrl,
              CA: AsRef,
              K: AsRef,
              C: AsRef
    {
        let ca_certificate = ca_certificate.as_ref().to_owned();
        let client_key = client_key.as_ref().to_owned();
        let client_certificate = client_certificate.as_ref().to_owned();

        let https_mutual_hyper_client = move || {
            // SSL implementation
            let mut ssl = openssl::ssl::SslConnectorBuilder::new(openssl::ssl::SslMethod::tls()).unwrap();

            // Server authentication
            ssl.set_ca_file(ca_certificate.clone()).unwrap();

            // Client authentication
            ssl.set_private_key_file(client_key.clone(), openssl::x509::X509_FILETYPE_PEM).unwrap();
            ssl.set_certificate_chain_file(client_certificate.clone()).unwrap();
            ssl.check_private_key().unwrap();

            let ssl = hyper_openssl::OpensslClient::from(ssl.build());
            let connector = hyper::net::HttpsConnector::new(ssl);
            hyper::client::Client::with_connector(connector)
        };

        Ok(Client {
                base_path: into_base_path(base_path, Some("https"))?,
                hyper_client: Arc::new(https_mutual_hyper_client)
            })
    }

    /// Constructor for creating a `Client` by passing in a pre-made `hyper` client.
    ///
    /// One should avoid relying on this function if possible, since it adds a dependency on the underlying transport
    /// implementation, which it would be better to abstract away. Therefore, using this function may lead to a loss of
    /// code generality, which may make it harder to move the application to a serverless environment, for example.
    ///
    /// The reason for this function's existence is to support legacy test code, which did mocking at the hyper layer.
    /// This is not a recommended way to write new tests. If other reasons are found for using this function, they
    /// should be mentioned here.
    pub fn try_new_with_hyper_client(base_path: T,
                                    hyper_client: Arc hyper::client::Client + Sync + Send>)
                                    -> Result
        where T: IntoUrl
    {
        Ok(Client {
            base_path: into_base_path(base_path, None)?,
            hyper_client: hyper_client
        })
    }
}

impl Api for Client {
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
    fn {{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}(&self{{#allParams}}, param_{{paramName}}: {{^required}}{{#isFile}}Box{{#isFile}}, Error=Error> + Send>{{/isFile}}{{/required}}{{/allParams}}, context: &Context) -> Box + Send> {
{{#queryParams}}{{#-first}}
        // Query parameters
{{/-first}}{{#required}}        let query_{{paramName}} = format!("{{baseName}}={{=<% %>=}}{<% paramName %>}<%={{ }}=%>&", {{paramName}}=param_{{paramName}}{{#isListContainer}}.join(","){{/isListContainer}}{{^isListContainer}}.to_string(){{/isListContainer}});
{{/required}}{{^required}}        let query_{{paramName}} = param_{{paramName}}.map_or_else(String::new, |query| format!("{{baseName}}={{=<% %>=}}{<% paramName %>}<%={{ }}=%>&", {{paramName}}=query{{#isListContainer}}.join(","){{/isListContainer}}{{^isListContainer}}.to_string(){{/isListContainer}}));
{{/required}}{{/queryParams}}

        let url = format!(
            "{}{{basePathWithoutHost}}{{path}}{{#queryParams}}{{#-first}}?{{/-first}}{{=<% %>=}}{<% paramName %>}<%={{ }}=%>{{/queryParams}}",
            self.base_path{{#pathParams}}, {{baseName}}=utf8_percent_encode(¶m_{{paramName}}.to_string(), PATH_SEGMENT_ENCODE_SET){{/pathParams}}{{#queryParams}},
            {{paramName}}=utf8_percent_encode(&query_{{paramName}}, QUERY_ENCODE_SET){{/queryParams}}
        );

{{#vendorExtensions}}{{#hasFile}}        // Form data body
        let mut multipart = Multipart::new();{{/hasFile}}{{/vendorExtensions}}{{#formParams}}{{#isFile}}

{{^required}}        if let Ok(Some(param_{{paramName}})) = param_{{paramName}}.wait() { {{/required}}
{{^required}}    {{/required}}        match convert_stream_to_string(param_{{paramName}}) {
{{^required}}    {{/required}}            Ok(param_{{paramName}}) => {
                    // Add file to multipart form.
                    multipart.add_text("{{paramName}}", param_{{paramName}});
                },
{{^required}}    {{/required}}            Err(err) => return Box::new(futures::done(Err(err))),
{{^required}}    {{/required}}        }
        {{^required}}}{{/required}}{{/isFile}}{{/formParams}}{{#vendorExtensions}}{{#hasFile}}

        let mut fields = match multipart.prepare() {
            Ok(fields) => fields,
            Err(err) => return Box::new(futures::done(Err(ApiError(format!("Unable to build request: {}", err))))),
        };

        let mut body_string = String::new();
        let body = fields.to_body().read_to_string(&mut body_string);
        let boundary = fields.boundary();
        let multipart_header = Mime(TopLevel::Multipart, SubLevel::FormData, vec![(Attr::Boundary, Value::Ext(boundary.to_string()))]);{{/hasFile}}{{/vendorExtensions}}{{#bodyParam}}{{#-first}}
        // Body parameter
{{/-first}}{{#vendorExtensions}}{{#required}}{{#consumesPlainText}}        let body = param_{{paramName}};{{/consumesPlainText}}{{#consumesXml}}
{{^has_namespace}}        let body = serde_xml_rs::to_string(¶m_{{paramName}}).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 = serde_xml_rs::to_string_with_namespaces(¶m_{{paramName}}, namespaces).expect("impossible to fail to serialize");{{/has_namespace}}{{/consumesXml}}{{#consumesJson}}
        let body = serde_json::to_string(¶m_{{paramName}}).expect("impossible to fail to serialize");{{/consumesJson}}
{{/required}}{{^required}}{{#consumesPlainText}}        let body = param_{{paramName}};
{{/consumesPlainText}}{{^consumesPlainText}}        let body = param_{{paramName}}.map(|ref body| {
{{#consumesXml}}
{{^has_namespace}}        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());
            serde_xml_rs::to_string_with_namespaces(body, namespaces).expect("impossible to fail to serialize"){{/has_namespace}}{{/consumesXml}}{{#consumesJson}}
            serde_json::to_string(body).expect("impossible to fail to serialize"){{/consumesJson}}
        });{{/consumesPlainText}}{{/required}}{{/vendorExtensions}}{{/bodyParam}}
        let hyper_client = (self.hyper_client)();
        let request = hyper_client.request(hyper::method::Method::{{#vendorExtensions}}{{HttpMethod}}{{/vendorExtensions}}, &url);
        let mut custom_headers = hyper::header::Headers::new();

{{#bodyParam}}{{#required}}        let request = request.body(&body);
{{/required}}{{^required}}        let request = match body {
            Some(ref body) => request.body(body),
            None => request,
        };
{{/required}}

        custom_headers.set(ContentType(mimetypes::requests::{{#vendorExtensions}}{{uppercase_operation_id}}{{/vendorExtensions}}.clone()));
{{/bodyParam}}
        context.x_span_id.as_ref().map(|header| custom_headers.set(XSpanId(header.clone())));
{{#headerParams}}{{#-first}}
        // Header parameters
{{/-first}}{{^isMapContainer}}        header! { (Request{{vendorExtensions.typeName}}, "{{baseName}}") => {{#isListContainer}}({{{baseType}}})*{{/isListContainer}}{{^isListContainer}}[{{{dataType}}}]{{/isListContainer}} }
{{#required}}        custom_headers.set(Request{{vendorExtensions.typeName}}(param_{{paramName}}{{#isListContainer}}.clone(){{/isListContainer}}));
{{/required}}{{^required}}        param_{{paramName}}.map(|header| custom_headers.set(Request{{vendorExtensions.typeName}}(header{{#isListContainer}}.clone(){{/isListContainer}})));
{{/required}}{{/isMapContainer}}{{#isMapContainer}}                let param_{{paramName}}: Option<{{{dataType}}}> = None;
{{/isMapContainer}}{{/headerParams}}

        let request = request.headers(custom_headers);{{#vendorExtensions}}{{#hasFile}}
        let request = request.header(ContentType(multipart_header))
                    .body(&body_string);
{{/hasFile}}{{/vendorExtensions}}

        // Helper function to provide a code block to use `?` in (to be replaced by the `catch` block when it exists).
        fn parse_response(mut response: hyper::client::response::Response) -> Result<{{operationId}}Response, ApiError> {
            match response.status.to_u16() {
{{#responses}}
                {{code}} => {
{{#dataType}}{{^isFile}}                    let mut buf = String::new();
                    response.read_to_string(&mut buf).map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;{{#vendorExtensions}}{{#producesXml}}
                    // ToDo: this will move to swagger-rs and become a standard From conversion trait
                    // once https://github.com/RReverser/serde-xml-rs/pull/45 is accepted upstream
                    let body = serde_xml_rs::from_str::<{{{dataType}}}>(&buf)
                        .map_err(|e| ApiError(format!("Response body did not match the schema: {}", e)))?;{{/producesXml}}{{#producesPlainText}}
                    let body = buf;{{/producesPlainText}}{{#producesJson}}
                    let body = serde_json::from_str::<{{{dataType}}}>(&buf)?;{{/producesJson}}{{/vendorExtensions}}{{/isFile}}{{#isFile}}
                    let mut buf = Vec::new();
                    response.read_to_end(&mut buf).map_err(|e| ApiError(format!("Received error reading response: {}", e)))?;
                    let body = Box::new(stream::once(Ok(buf)));{{/isFile}}{{/dataType}}
{{#headers}}                    header! { (Response{{nameInCamelCase}}, "{{baseName}}") => [{{{datatype}}}] }
                    let response_{{name}} = response.headers.get::().ok_or_else(|| "Required response header {{baseName}} for response {{code}} was not found.")?;
{{/headers}}

{{#dataType}}                    Ok({{operationId}}Response::{{#vendorExtensions}}{{x-responseId}}{{/vendorExtensions}}{{^headers}}(body){{/headers}}{{#headers}}{{#-first}}{ body: body, {{/-first}}{{name}}: response_{{name}}.0.clone(){{^-last}}, {{/-last}}{{#-last}} }{{/-last}}{{/headers}})
{{/dataType}}{{^dataType}}                    Ok({{operationId}}Response::{{#vendorExtensions}}{{x-responseId}}{{/vendorExtensions}}{{#headers}}{{#-first}}{ {{/-first}}{{^-first}}, {{/-first}}{{name}}: response_{{name}}.0.clone(){{#-last}} }{{/-last}}{{/headers}})
{{/dataType}}
                },
{{/responses}}
                code => {
                    let mut buf = [0; 100];
                    let debug_body = match response.read(&mut buf) {
                        Ok(len) => match str::from_utf8(&buf[..len]) {
                            Ok(body) => Cow::from(body),
                            Err(_) => Cow::from(format!("", &buf[..len].to_vec())),
                        },
                        Err(e) => Cow::from(format!("", e)),
                    };
                    Err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}",
                                         code,
                                         response.headers,
                                         debug_body)))
                }
            }
        }{{#vendorExtensions}}{{#hasFile}}

        // Helper function to convert a Stream into a String. The String can then be used to build the HTTP body.
        fn convert_stream_to_string(stream: Box, Error=Error> + Send>) -> Result {

            stream.fold(Vec::new(), |mut body, chunk| {
                body.extend(chunk.iter());
                future::ok::,Error>(body)
            }).wait()
              .map_err(|e| ApiError(format!("Unable to fold stream: {}", e)))
              .and_then(|body| String::from_utf8(body)
                .map_err(|e| ApiError(format!("Failed to convert utf8 stream to String: {}", e))))
        }{{/hasFile}}{{/vendorExtensions}}

        let result = request.send().map_err(|e| ApiError(format!("No response received: {}", e))).and_then(parse_response);
        Box::new(futures::done(result))
    }
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
}

#[derive(Debug)]
pub enum ClientInitError {
    InvalidScheme,
    InvalidUrl(hyper::error::ParseError),
    MissingHost,
    SslError(openssl::error::ErrorStack)
}

impl From for ClientInitError {
    fn from(err: hyper::error::ParseError) -> ClientInitError {
        ClientInitError::InvalidUrl(err)
    }
}

impl From for ClientInitError {
    fn from(err: openssl::error::ErrorStack) -> ClientInitError {
        ClientInitError::SslError(err)
    }
}

impl fmt::Display for ClientInitError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        (self as &fmt::Debug).fmt(f)
    }
}

impl error::Error for ClientInitError {
    fn description(&self) -> &str {
        "Failed to produce a hyper client."
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy