#![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; {{#apiHasFile}}extern crate multipart;{{/apiHasFile}} {{#usesUrlEncodedForm}}extern crate serde_urlencoded;{{/usesUrlEncodedForm}} {{#apiUsesUuid}}use uuid;{{/apiUsesUuid}} {{#apiHasFile}}use self::multipart::client::lazy::Multipart;{{/apiHasFile}} 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 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: &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. #[derive(Clone)] pub struct Client { hyper_client: Arc Box, Response=hyper::Response, Error=hyper::Error, Future=hyper::client::FutureResponse>> + Sync + Send>, handle: Arc, base_path: String, } impl fmt::Debug for Client { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Client {{ base_path: {} }}", self.base_path) } } 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 { 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 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 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 where C: hyper::client::Connect + hyper::client::Service, { let hyper_client = { move |handle: &Handle| -> Box< hyper::client::Service< Request = hyper::Request, Response = hyper::Response, Error = hyper::Error, Future = hyper::client::FutureResponse, >, > { let connector = connector_fn(handle); Box::new(hyper::Client::configure().connector(connector).build( handle, )) } }; Ok(Client { hyper_client: Arc::new(hyper_client), handle: Arc::new(handle), 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. pub fn try_new_with_hyper_client(hyper_client: Arc Box, Response=hyper::Response, Error=hyper::Error, Future=hyper::client::FutureResponse>> + Sync + Send>, handle: Handle, base_path: &str) -> Result { Ok(Client { hyper_client: hyper_client, handle: Arc::new(handle), base_path: into_base_path(base_path, None)?, }) } } 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> { {{#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 uri = 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}} ); 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}}{{#hasFile}} // Form data body let mut multipart = Multipart::new(); // 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.concat2() .wait() .map_err(|e| ApiError(format!("Unable to collect stream: {}", e))) .and_then(|body| String::from_utf8(body) .map_err(|e| ApiError(format!("Failed to convert utf8 stream to String: {}", e)))) }{{/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 = match mime::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))))), };{{/hasFile}}{{^hasFile}}{{#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}}{{/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}} {{#bodyParam}}{{^required}}if let Some(body) = body { {{/required}} request.set_body(body.into_bytes()); {{^required}} }{{/required}} request.headers_mut().set(ContentType(mimetypes::requests::{{#vendorExtensions}}{{uppercase_operation_id}}{{/vendorExtensions}}.clone())); {{/bodyParam}} context.x_span_id.as_ref().map(|header| request.headers_mut().set(XSpanId(header.clone()))); {{#authMethods}}{{#isBasic}} context.auth_data.as_ref().map(|auth_data| { if let &swagger::AuthData::Basic(ref basic_header) = auth_data { request.headers_mut().set(hyper::header::Authorization( basic_header.clone(), )) } });{{/isBasic}}{{/authMethods}}{{#headerParams}}{{#-first}} // Header parameters {{/-first}}{{^isMapContainer}} header! { (Request{{vendorExtensions.typeName}}, "{{baseName}}") => {{#isListContainer}}({{{baseType}}})*{{/isListContainer}}{{^isListContainer}}[{{{dataType}}}]{{/isListContainer}} } {{#required}} request.headers_mut().set(Request{{vendorExtensions.typeName}}(param_{{paramName}}{{#isListContainer}}.clone(){{/isListContainer}})); {{/required}}{{^required}} param_{{paramName}}.map(|header| request.headers_mut().set(Request{{vendorExtensions.typeName}}(header{{#isListContainer}}.clone(){{/isListContainer}}))); {{/required}}{{/isMapContainer}}{{#isMapContainer}} let param_{{paramName}}: Option<{{{dataType}}}> = None; {{/isMapContainer}}{{/headerParams}} {{#vendorExtensions}}{{#hasFile}} request.headers_mut().set(ContentType(multipart_header)); request.set_body(body_string.into_bytes()); {{/hasFile}}{{/vendorExtensions}} let hyper_client = (self.hyper_client)(&*self.handle); Box::new(hyper_client.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().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}} {{^isFile}}let body = response.body();{{/isFile}}{{#isFile}}let body = Box::new(response.body() .map(|chunk| chunk.to_vec()) .map_err(|_| Error::new(ErrorKind::Other, "Received error reading response.") ));{{/isFile}} Box::new( {{#dataType}}{{^isFile}} body .concat2() .map_err(|e| ApiError(format!("Failed to read response: {}", e))) .and_then(|body| str::from_utf8(&body) .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e))) .and_then(|body| {{#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 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}}{{/vendorExtensions}} )) .map(move |body| {{/isFile}}{{#isFile}} future::ok( {{/isFile}} {{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}}{{^-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 &fmt::Debug).fmt(f) } } impl error::Error for ClientInitError { fn description(&self) -> &str { "Failed to produce a hyper client." } }