diff options
Diffstat (limited to 'tools/unitctl/unit-openapi/openapi-templates/request.rs')
-rw-r--r-- | tools/unitctl/unit-openapi/openapi-templates/request.rs | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/tools/unitctl/unit-openapi/openapi-templates/request.rs b/tools/unitctl/unit-openapi/openapi-templates/request.rs new file mode 100644 index 00000000..9cf480cc --- /dev/null +++ b/tools/unitctl/unit-openapi/openapi-templates/request.rs @@ -0,0 +1,248 @@ +use std::collections::HashMap; +use std::pin::Pin; + +use base64::{alphabet, Engine}; +use base64::engine::general_purpose::NO_PAD; +use base64::engine::GeneralPurpose; + +use futures; +use futures::Future; +use futures::future::*; +use hyper; +use hyper::header::{AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE, HeaderValue, USER_AGENT}; +use serde; +use serde_json; + +use super::{configuration, Error}; + +const MIME_ENCODER: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, NO_PAD); + +pub(crate) struct ApiKey { + pub in_header: bool, + pub in_query: bool, + pub param_name: String, +} + +impl ApiKey { + fn key(&self, prefix: &Option<String>, key: &str) -> String { + match prefix { + None => key.to_owned(), + Some(ref prefix) => format!("{} {}", prefix, key), + } + } +} + +#[allow(dead_code)] +pub(crate) enum Auth { + None, + ApiKey(ApiKey), + Basic, + Oauth, +} + +/// If the authorization type is unspecified then it will be automatically detected based +/// on the configuration. This functionality is useful when the OpenAPI definition does not +/// include an authorization scheme. +pub(crate) struct Request { + auth: Option<Auth>, + method: hyper::Method, + path: String, + query_params: HashMap<String, String>, + no_return_type: bool, + path_params: HashMap<String, String>, + form_params: HashMap<String, String>, + header_params: HashMap<String, String>, + // TODO: multiple body params are possible technically, but not supported here. + serialized_body: Option<String>, +} + +#[allow(dead_code)] +impl Request { + pub fn new(method: hyper::Method, path: String) -> Self { + Request { + auth: None, + method, + path, + query_params: HashMap::new(), + path_params: HashMap::new(), + form_params: HashMap::new(), + header_params: HashMap::new(), + serialized_body: None, + no_return_type: false, + } + } + + pub fn with_body_param<T: serde::Serialize>(mut self, param: T) -> Self { + self.serialized_body = Some(serde_json::to_string(¶m).unwrap()); + self + } + + pub fn with_header_param(mut self, basename: String, param: String) -> Self { + self.header_params.insert(basename, param); + self + } + + #[allow(unused)] + pub fn with_query_param(mut self, basename: String, param: String) -> Self { + self.query_params.insert(basename, param); + self + } + + #[allow(unused)] + pub fn with_path_param(mut self, basename: String, param: String) -> Self { + self.path_params.insert(basename, param); + self + } + + #[allow(unused)] + pub fn with_form_param(mut self, basename: String, param: String) -> Self { + self.form_params.insert(basename, param); + self + } + + pub fn returns_nothing(mut self) -> Self { + self.no_return_type = true; + self + } + + pub fn with_auth(mut self, auth: Auth) -> Self { + self.auth = Some(auth); + self + } + + pub fn execute<'a, C, U>( + self, + conf: &configuration::Configuration<C>, + ) -> Pin<Box<dyn Future<Output=Result<U, Error>> + 'a>> + where + C: hyper::client::connect::Connect + Clone + std::marker::Send + Sync, + U: Sized + std::marker::Send + 'a, + for<'de> U: serde::Deserialize<'de>, + { + let mut query_string = ::url::form_urlencoded::Serializer::new("".to_owned()); + + let mut path = self.path; + for (k, v) in self.path_params { + // replace {id} with the value of the id path param + path = path.replace(&format!("{{{}}}", k), &v); + } + + for (key, val) in self.query_params { + query_string.append_pair(&key, &val); + } + + let mut uri_str = format!("{}{}", conf.base_path, path); + + let query_string_str = query_string.finish(); + if query_string_str != "" { + uri_str += "?"; + uri_str += &query_string_str; + } + let uri: hyper::Uri = match uri_str.parse() { + Err(e) => return Box::pin(futures::future::err(Error::UriError(e))), + Ok(u) => u, + }; + + let mut req_builder = hyper::Request::builder() + .uri(uri) + .method(self.method); + + // Detect the authorization type if it hasn't been set. + let auth = self.auth.unwrap_or_else(|| + if conf.api_key.is_some() { + panic!("Cannot automatically set the API key from the configuration, it must be specified in the OpenAPI definition") + } else if conf.oauth_access_token.is_some() { + Auth::Oauth + } else if conf.basic_auth.is_some() { + Auth::Basic + } else { + Auth::None + } + ); + match auth { + Auth::ApiKey(apikey) => { + if let Some(ref key) = conf.api_key { + let val = apikey.key(&key.prefix, &key.key); + if apikey.in_query { + query_string.append_pair(&apikey.param_name, &val); + } + if apikey.in_header { + req_builder = req_builder.header(&apikey.param_name, val); + } + } + } + Auth::Basic => { + if let Some(ref auth_conf) = conf.basic_auth { + let mut text = auth_conf.0.clone(); + text.push(':'); + if let Some(ref pass) = auth_conf.1 { + text.push_str(&pass[..]); + } + let encoded = MIME_ENCODER.encode(&text); + req_builder = req_builder.header(AUTHORIZATION, encoded); + } + } + Auth::Oauth => { + if let Some(ref token) = conf.oauth_access_token { + let text = "Bearer ".to_owned() + token; + req_builder = req_builder.header(AUTHORIZATION, text); + } + } + Auth::None => {} + } + + if let Some(ref user_agent) = conf.user_agent { + req_builder = req_builder.header(USER_AGENT, match HeaderValue::from_str(user_agent) { + Ok(header_value) => header_value, + Err(e) => return Box::pin(futures::future::err(super::Error::Header(e))) + }); + } + + for (k, v) in self.header_params { + req_builder = req_builder.header(&k, v); + } + + let req_headers = req_builder.headers_mut().unwrap(); + let request_result = if self.form_params.len() > 0 { + req_headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/x-www-form-urlencoded")); + let mut enc = ::url::form_urlencoded::Serializer::new("".to_owned()); + for (k, v) in self.form_params { + enc.append_pair(&k, &v); + } + req_builder.body(hyper::Body::from(enc.finish())) + } else if let Some(body) = self.serialized_body { + req_headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); + req_headers.insert(CONTENT_LENGTH, body.len().into()); + req_builder.body(hyper::Body::from(body)) + } else { + req_builder.body(hyper::Body::default()) + }; + let request = match request_result { + Ok(request) => request, + Err(e) => return Box::pin(futures::future::err(Error::from(e))) + }; + + let no_return_type = self.no_return_type; + Box::pin(conf.client + .request(request) + .map_err(|e| Error::from(e)) + .and_then(move |response| { + let status = response.status(); + if !status.is_success() { + futures::future::err::<U, Error>(Error::from((status, response.into_body()))).boxed() + } else if no_return_type { + // This is a hack; if there's no_ret_type, U is (), but serde_json gives an + // error when deserializing "" into (), so deserialize 'null' into it + // instead. + // An alternate option would be to require U: Default, and then return + // U::default() here instead since () implements that, but then we'd + // need to impl default for all models. + futures::future::ok::<U, Error>(serde_json::from_str("null").expect("serde null value")).boxed() + } else { + hyper::body::to_bytes(response.into_body()) + .map(|bytes| serde_json::from_slice(&bytes.unwrap())) + .map_err(|e| Error::from(e)).boxed() + } + })) + } +} |