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

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

#![allow(unused_extern_crates)]
extern crate tokio_core;
extern crate native_tls;
extern crate hyper_tls;
extern crate openssl;
extern crate mime;
extern crate chrono;
extern crate url;
{{#usesUrlEncodedForm}}
extern crate serde_urlencoded;
{{/usesUrlEncodedForm}}
{{#apiUsesMultipart}}
extern crate multipart;
{{/apiUsesMultipart}}
{{#apiUsesUuid}}
extern crate uuid;
{{/apiUsesUuid}}

use hyper;
use hyper::header::{Headers, ContentType};
use hyper::Uri;
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 self::tokio_core::reactor::Handle;
use std::borrow::Cow;
use std::io::{Read, Error, ErrorKind};
use std::error;
use std::fmt;
use std::path::Path;
use std::sync::Arc;
use std::str;
use std::str::FromStr;
use std::string::ToString;
use swagger::headers::SafeHeaders;

{{#apiUsesMultipart}}
use hyper::mime::Mime; 
use std::io::Cursor; 
use client::multipart::client::lazy::Multipart; 
{{/apiUsesMultipart}}
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::{ApiError, XSpanId, XSpanIdString, Has, AuthData};

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

define_encode_set! {
    /// This encode set is used for object IDs
    ///
    /// Aside from the special characters defined in the `PATH_SEGMENT_ENCODE_SET`,
    /// the vertical bar (|) is encoded.
    pub ID_ENCODE_SET = [PATH_SEGMENT_ENCODE_SET] | {'|'}
}

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

    let scheme = uri.scheme().ok_or(ClientInitError::InvalidScheme)?;

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

    let host = uri.host().ok_or_else(|| ClientInitError::MissingHost)?;
    let port = uri.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.
pub struct Client where
  F: Future + 'static {
    client_service: Arc, Response=hyper::Response, Error=hyper::Error, Future=F>>>,
    base_path: String,
}

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

impl Clone for Client where
   F: Future  + 'static {
    fn clone(&self) -> Self {
        Client {
            client_service: self.client_service.clone(),
            base_path: self.base_path.clone()
        }
    }
}

impl Client {

    /// Create an HTTP client.
    ///
    /// # Arguments
    /// * `handle` - tokio reactor handle to use for execution
    /// * `base_path` - base path of the client API, i.e. "www.my-api-implementation.com"
    pub fn try_new_http(handle: Handle, base_path: &str) -> Result, ClientInitError> {
        let http_connector = swagger::http_connector();
        Self::try_new_with_connector::(
            handle,
            base_path,
            Some("http"),
            http_connector,
        )
    }

    /// Create a client with a TLS connection to the server.
    ///
    /// # Arguments
    /// * `handle` - tokio reactor handle to use for execution
    /// * `base_path` - base path of the client API, i.e. "www.my-api-implementation.com"
    /// * `ca_certificate` - Path to CA certificate used to authenticate the server
    pub fn try_new_https(
        handle: Handle,
        base_path: &str,
        ca_certificate: CA,
    ) -> Result, ClientInitError>
    where
        CA: AsRef,
    {
        let https_connector = swagger::https_connector(ca_certificate);
        Self::try_new_with_connector::>(
            handle,
            base_path,
            Some("https"),
            https_connector,
        )
    }

    /// Create a client with a mutually authenticated TLS connection to the server.
    ///
    /// # Arguments
    /// * `handle` - tokio reactor handle to use for execution
    /// * `base_path` - base path of the client API, i.e. "www.my-api-implementation.com"
    /// * `ca_certificate` - Path to CA certificate used to authenticate the server
    /// * `client_key` - Path to the client private key
    /// * `client_certificate` - Path to the client's public certificate associated with the private key
    pub fn try_new_https_mutual(
        handle: Handle,
        base_path: &str,
        ca_certificate: CA,
        client_key: K,
        client_certificate: C,
    ) -> Result, ClientInitError>
    where
        CA: AsRef,
        K: AsRef,
        C: AsRef,
    {
        let https_connector =
            swagger::https_mutual_connector(ca_certificate, client_key, client_certificate);
        Self::try_new_with_connector::>(
            handle,
            base_path,
            Some("https"),
            https_connector,
        )
    }

    /// Create a client with a custom implementation of hyper::client::Connect.
    ///
    /// Intended for use with custom implementations of connect for e.g. protocol logging
    /// or similar functionality which requires wrapping the transport layer. When wrapping a TCP connection,
    /// this function should be used in conjunction with
    /// `swagger::{http_connector, https_connector, https_mutual_connector}`.
    ///
    /// For ordinary tcp connections, prefer the use of `try_new_http`, `try_new_https`
    /// and `try_new_https_mutual`, to avoid introducing a dependency on the underlying transport layer.
    ///
    /// # Arguments
    ///
    /// * `handle` - tokio reactor handle to use for execution
    /// * `base_path` - base path of the client API, i.e. "www.my-api-implementation.com"
    /// * `protocol` - Which protocol to use when constructing the request url, e.g. `Some("http")`
    /// * `connector_fn` - Function which returns an implementation of `hyper::client::Connect`
    pub fn try_new_with_connector(
        handle: Handle,
        base_path: &str,
        protocol: Option<&'static str>,
        connector_fn: Box C + Send + Sync>,
    ) -> Result, ClientInitError>
    where
        C: hyper::client::Connect + hyper::client::Service,
    {
        let connector = connector_fn(&handle);
        let client_service = Box::new(hyper::Client::configure().connector(connector).build(
            &handle,
        ));

        Ok(Client {
            client_service: Arc::new(client_service),
            base_path: into_base_path(base_path, protocol)?,
        })
    }

    /// 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.
    #[deprecated(note="Use try_new_with_client_service instead")]
    pub fn try_new_with_hyper_client(
        hyper_client: Arc, Response=hyper::Response, Error=hyper::Error, Future=hyper::client::FutureResponse>>>,
        handle: Handle,
        base_path: &str
    ) -> Result, ClientInitError>
    {
        Ok(Client {
            client_service: hyper_client,
            base_path: into_base_path(base_path, None)?,
        })
    }
}

impl Client where
    F: Future  + 'static
{
    /// Constructor for creating a `Client` by passing in a pre-made `hyper` client Service.
    ///
    /// This allows adding custom wrappers around the underlying transport, for example for logging.
    pub fn try_new_with_client_service(client_service: Arc, Response=hyper::Response, Error=hyper::Error, Future=F>>>,
                                       handle: Handle,
                                       base_path: &str)
                                    -> Result, ClientInitError>
    {
        Ok(Client {
            client_service: client_service,
            base_path: into_base_path(base_path, None)?,
        })
    }
}

impl Api for Client where
    F: Future  + 'static,
    C: Has {{#hasAuthMethods}}+ Has>{{/hasAuthMethods}}{
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
    fn {{#vendorExtensions}}{{{operation_id}}}{{/vendorExtensions}}(&self{{#allParams}}, param_{{{paramName}}}: {{^required}}Option<{{/required}}{{#isListContainer}}&{{/isListContainer}}{{{dataType}}}{{^required}}>{{/required}}{{/allParams}}, context: &C) -> Box> {
        let mut uri = format!(
            "{}{{{basePathWithoutHost}}}{{{vendorExtensions.x-path-format-string}}}",
            self.base_path{{#pathParams}}, {{{paramName}}}=utf8_percent_encode(¶m_{{{paramName}}}.to_string(), ID_ENCODE_SET){{/pathParams}}
        );

        let mut query_string = self::url::form_urlencoded::Serializer::new("".to_owned());
{{#queryParams}}{{#required}}        query_string.append_pair("{{{baseName}}}", ¶m_{{{paramName}}}{{#isListContainer}}.join(","){{/isListContainer}}{{^isListContainer}}.to_string(){{/isListContainer}});{{/required}}
{{^required}}        if let Some({{{paramName}}}) = param_{{{paramName}}} {
            query_string.append_pair("{{{baseName}}}", &{{{paramName}}}{{#isListContainer}}.join(","){{/isListContainer}}{{^isListContainer}}.to_string(){{/isListContainer}});
        }{{/required}}{{/queryParams}}
{{#authMethods}}{{#isApiKey}}{{#isKeyInQuery}}        if let Some(auth_data) = (context as &dyn Has>).get().as_ref() {
            if let AuthData::ApiKey(ref api_key) = *auth_data {
                query_string.append_pair("{{keyParamName}}", api_key);
            }
        }{{/isKeyInQuery}}{{/isApiKey}}{{/authMethods}}
        let query_string_str = query_string.finish();
        if !query_string_str.is_empty() {
            uri += "?";
            uri += &query_string_str;
        }

        let uri = match Uri::from_str(&uri) {
            Ok(uri) => uri,
            Err(err) => return Box::new(futures::done(Err(ApiError(format!("Unable to build URI: {}", err))))),
        };

        let mut request = hyper::Request::new(hyper::Method::{{#vendorExtensions}}{{{HttpMethod}}}{{/vendorExtensions}}, uri);

{{#vendorExtensions}}
  {{#consumesMultipart}}
        let mut multipart = Multipart::new(); 

    {{#vendorExtensions}}
      {{#formParams}}
        {{#-first}}
        // For each parameter, encode as appropriate and add to the multipart body as a stream.
        {{/-first}}
        {{^isByteArray}}
          {{#jsonSchema}}

        let {{{paramName}}}_str = match serde_json::to_string(¶m_{{{paramName}}}) {
            Ok(str) => str,
            Err(e) => return Box::new(futures::done(Err(ApiError(format!("Unable to parse {{{paramName}}} to string: {}", e))))),
        };

        let {{{paramName}}}_vec = {{{paramName}}}_str.as_bytes().to_vec();

        let {{{paramName}}}_mime = mime::Mime::from_str("application/json").expect("impossible to fail to parse");

        let {{{paramName}}}_cursor = Cursor::new({{{paramName}}}_vec);

        multipart.add_stream("{{{paramName}}}",  {{{paramName}}}_cursor,  None as Option<&str>, Some({{{paramName}}}_mime));  

          {{/jsonSchema}}
        {{/isByteArray}}
        {{#isByteArray}}

        let {{{paramName}}}_vec = param_{{{paramName}}}.to_vec();

        let {{{paramName}}}_mime = match mime::Mime::from_str("application/octet-stream") {
            Ok(mime) => mime,
            Err(err) => return Box::new(futures::done(Err(ApiError(format!("Unable to get mime type: {:?}", err))))),
        };

        let {{{paramName}}}_cursor = Cursor::new({{{paramName}}}_vec);

        let filename = None as Option<&str> ;
        multipart.add_stream("{{{paramName}}}",  {{{paramName}}}_cursor,  filename, Some({{{paramName}}}_mime));  

        {{/isByteArray}}
        {{#-last}}
        {{/-last}}
      {{/formParams}}
    {{/vendorExtensions}}

        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();
        fields.to_body().read_to_string(&mut body_string).unwrap();
        let boundary = fields.boundary();

        let multipart_header = match Mime::from_str(&format!("multipart/form-data;boundary={}", boundary)) {
            Ok(multipart_header) => multipart_header,
            Err(err) => return Box::new(futures::done(Err(ApiError(format!("Unable to build multipart header: {:?}", err))))),
        };

        request.set_body(body_string.into_bytes());
        request.headers_mut().set(ContentType(multipart_header));

  {{/consumesMultipart}}
{{/vendorExtensions}}
{{#vendorExtensions}}
  {{^consumesMultipart}}
    {{#vendorExtensions}}
      {{#formParams}}
        {{#-first}}
        let params = &[
        {{/-first}}
            ("{{{baseName}}}", {{#vendorExtensions}}{{#required}}Some({{#isString}}param_{{{paramName}}}{{/isString}}{{^isString}}format!("{:?}", param_{{{paramName}}}){{/isString}}){{/required}}{{^required}}{{#isString}}param_{{{paramName}}}{{/isString}}{{^isString}}param_{{{paramName}}}.map(|param| format!("{:?}", param)){{/isString}}{{/required}}),{{/vendorExtensions}}
        {{#-last}}
        ];
        let body = serde_urlencoded::to_string(params).expect("impossible to fail to serialize");

        request.headers_mut().set(ContentType(mimetypes::requests::{{#vendorExtensions}}{{{uppercase_operation_id}}}{{/vendorExtensions}}.clone()));
        request.set_body(body.into_bytes());
        {{/-last}}
      {{/formParams}}
    {{/vendorExtensions}}
    {{#bodyParam}}
      {{#-first}}
        // Body parameter
      {{/-first}}
        {{#vendorExtensions}}
        {{#consumesPlainText}}
        {{#isByteArray}}
        let body = param_{{{paramName}}}.0;
        {{/isByteArray}}
        {{^isByteArray}}
        let body = param_{{{paramName}}};
        {{/isByteArray}}
        {{/consumesPlainText}}
        {{#required}}
        {{#consumesXml}}
        let body = param_{{{paramName}}}.to_xml();
        {{/consumesXml}}
        {{#consumesJson}}
        let body = serde_json::to_string(¶m_{{{paramName}}}).expect("impossible to fail to serialize");
        {{/consumesJson}}
        {{/required}}
        {{^required}}
        let body = param_{{{paramName}}}.map(|ref body| {
            {{#consumesXml}}
            body.to_xml()
            {{/consumesXml}}
            {{#consumesJson}}
            serde_json::to_string(body).expect("impossible to fail to serialize")
            {{/consumesJson}}
        });
        {{/required}}
        {{/vendorExtensions}}
        {{^required}}

        if let Some(body) = body {
{{/required}}
        request.set_body(body);
{{^required}}
        }
{{/required}}

        request.headers_mut().set(ContentType(mimetypes::requests::{{#vendorExtensions}}{{{uppercase_operation_id}}}{{/vendorExtensions}}.clone()));
    {{/bodyParam}}
  {{/consumesMultipart}}
{{/vendorExtensions}}

        request.headers_mut().set(XSpanId((context as &dyn Has).get().0.clone()));
{{#vendorExtensions.x-has-header-auth-methods}}

        (context as &dyn Has>).get().as_ref().map(|auth_data| {
            // Currently only authentication with Basic, API Key, and Bearer are supported
            match auth_data {
            {{#authMethods}}
                {{#isApiKey}}
                    {{#isKeyInHeader}}
                &AuthData::ApiKey(ref api_key) => {
                    header! { ({{#vendorExtensions}}{{x-apiKeyName}}{{/vendorExtensions}}, "{{keyParamName}}") => [String] }
                    request.headers_mut().set(
                        {{#vendorExtensions}}{{x-apiKeyName}}{{/vendorExtensions}}(api_key.to_string())
                    )
                },
                    {{/isKeyInHeader}}
                {{/isApiKey}}
                {{#isBasicBasic}}
                &AuthData::Basic(ref basic_header) => {
                    request.headers_mut().set(hyper::header::Authorization(
                        basic_header.clone(),
                    ))
                },
                {{/isBasicBasic}}
                {{#isBasicBearer}}
                &AuthData::Bearer(ref bearer_header) => {
                    request.headers_mut().set(hyper::header::Authorization(
                        bearer_header.clone(),
                    ))
                },
                {{/isBasicBearer}}
                {{#isOAuth}}
                {{^isBasicBearer}}
                &AuthData::Bearer(ref bearer_header) => {
                    request.headers_mut().set(hyper::header::Authorization(
                        bearer_header.clone(),
                    ))
                },
                {{/isBasicBearer}}
                {{/isOAuth}}
            {{/authMethods}}
                _ => {}
            }
        });
{{/vendorExtensions.x-has-header-auth-methods}}
{{#headerParams}}
{{#-first}}

        // Header parameters
{{/-first}}{{^isMapContainer}}        header! { (Request{{vendorExtensions.x-type-name}}, "{{{baseName}}}") => {{#isListContainer}}({{{baseType}}})*{{/isListContainer}}{{^isListContainer}}[{{{dataType}}}]{{/isListContainer}} }
{{#required}}        request.headers_mut().set(Request{{vendorExtensions.x-type-name}}(param_{{{paramName}}}{{#isListContainer}}.clone(){{/isListContainer}}));
{{/required}}{{^required}}        param_{{{paramName}}}.map(|header| request.headers_mut().set(Request{{vendorExtensions.x-type-name}}(header{{#isListContainer}}.clone(){{/isListContainer}})));
{{/required}}{{/isMapContainer}}{{#isMapContainer}}                let param_{{{paramName}}}: Option<{{{dataType}}}> = None;
{{/isMapContainer}}
{{/headerParams}}
        Box::new(self.client_service.call(request)
                             .map_err(|e| ApiError(format!("No response received: {}", e)))
                             .and_then(|mut response| {
            match response.status().as_u16() {
{{#responses}}
                {{{code}}} => {
{{#headers}}                    header! { (Response{{{nameInCamelCase}}}, "{{{baseName}}}") => [{{{datatype}}}] }
                    let response_{{{name}}} = match response.headers().safe_get::() {
                        Some(response_{{{name}}}) => response_{{{name}}}.0.clone(),
                        None => return Box::new(future::err(ApiError(String::from("Required response header {{{baseName}}} for response {{{code}}} was not found.")))) as Box>,
                    };
{{/headers}}
                    let body = response.body();
                    Box::new(
{{#dataType}}
                        body
                        .concat2()
                        .map_err(|e| ApiError(format!("Failed to read response: {}", e)))
                        .and_then(|body|
{{#vendorExtensions}}
  {{#producesBytes}}
                            Ok(swagger::ByteArray(body.to_vec()))
  {{/producesBytes}}
  {{^producesBytes}}
                            str::from_utf8(&body)
                            .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))
                            .and_then(|body|
    {{#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
                                serde_xml_rs::from_str::<{{{dataType}}}>(body)
                                .map_err(|e| ApiError(format!("Response body did not match the schema: {}", e)))
    {{/producesXml}}
    {{#producesJson}}
                                serde_json::from_str::<{{{dataType}}}>(body)
                                .map_err(|e| e.into())
    {{/producesJson}}
    {{#producesPlainText}}
                                Ok(body.to_string())
    {{/producesPlainText}}
                            )
  {{/producesBytes}}
{{/vendorExtensions}}
                        )
                        .map(move |body| {
                            {{{operationId}}}Response::{{#vendorExtensions}}{{x-responseId}}{{/vendorExtensions}}{{^headers}}(body){{/headers}}{{#headers}}{{#-first}}{ body: body, {{/-first}}{{{name}}}: response_{{{name}}}{{^-last}}, {{/-last}}{{#-last}} }{{/-last}}{{/headers}}
                        })
{{/dataType}}{{^dataType}}
                        future::ok(
                            {{{operationId}}}Response::{{#vendorExtensions}}{{x-responseId}}{{/vendorExtensions}}
{{#headers}}
  {{#-first}}
                            {
  {{/-first}}
                              {{{name}}}: response_{{{name}}},
  {{#-last}}
                            }
  {{/-last}}
{{/headers}}
                        )
{{/dataType}}
                    ) as Box>
                },
{{/responses}}
                code => {
                    let headers = response.headers().clone();
                    Box::new(response.body()
                            .take(100)
                            .concat2()
                            .then(move |body|
                                future::err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}",
                                    code,
                                    headers,
                                    match body {
                                        Ok(ref body) => match str::from_utf8(body) {
                                            Ok(body) => Cow::from(body),
                                            Err(e) => Cow::from(format!("", e)),
                                        },
                                        Err(e) => Cow::from(format!("", e)),
                                    })))
                            )
                    ) as Box>
                }
            }
        }))

    }
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
}

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

impl From for ClientInitError {
    fn from(err: hyper::error::UriError) -> ClientInitError {
        ClientInitError::InvalidUri(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 &dyn 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