From b8a66bc666354b262675e2ab4a25beb11c78386a Mon Sep 17 00:00:00 2001 From: Daniel Rodgers-Pryor Date: Mon, 1 Feb 2021 23:29:25 +1100 Subject: [PATCH] Update hyper, rustls and yup-oauth2 I've switched to a new M1 laptop which couldn't build the old dependencies because the old ring library version doesn't compile on mac arm (but new version do). There's no way to update ring in isolation, so I've got to update the whole tree at once. --- Cargo.toml | 11 +- src/mako/Cargo.toml.mako | 15 +- src/mako/api/api.rs.mako | 4 +- src/mako/api/lib/mbuild.mako | 162 ++++++------ src/mako/cli/lib/engine.mako | 2 +- src/mako/lib/util.py | 13 +- src/mako/rustfmt.toml.mako | 2 + src/rust/api/client.rs | 255 +++++++++---------- src/rust/cli/client.rs | 467 +++++++++++++++-------------------- src/rust/lib.rs | 66 +++-- 10 files changed, 477 insertions(+), 520 deletions(-) create mode 100644 src/mako/rustfmt.toml.mako diff --git a/Cargo.toml b/Cargo.toml index 87bcb205a7..c6e2d6f854 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ description = "A library to facilitate interacting with your youtube account" repository = "https://github.com/Byron/google-apis-rs" license = "MIT/Apache-2.0" keywords = ["youtube", "google", "protocol", "not-for-use"] +edition = "2018" [lib] # The common client code, used by all generated implementations @@ -17,14 +18,14 @@ path = "src/rust/lib.rs" [dependencies] clap = "2" -hyper = "0.10" +hyper = "0.14" mime = "0.2" rustc-serialize = "*" -yup-oauth2 = "^ 1.0" +yup-oauth2 = "^ 5.0" serde = "1" serde_json = "1" serde_derive = "1" strsim = "*" - -[dev-dependencies] -yup-hyper-mock = "^2" +tokio = "^ 1.0" +hyper-rustls = "^0.22" +itertools = "^ 0.10" \ No newline at end of file diff --git a/src/mako/Cargo.toml.mako b/src/mako/Cargo.toml.mako index 9dffaa8d31..d37998eaad 100644 --- a/src/mako/Cargo.toml.mako +++ b/src/mako/Cargo.toml.mako @@ -17,6 +17,7 @@ documentation = "${util.doc_base_url()}" license = "${copyright.license_abbrev}" keywords = ["${name[:20]}", ${", ".join(estr(cargo.keywords))}] autobins = false +edition = "2018" % if cargo.get('is_executable', False): [[bin]] @@ -24,25 +25,21 @@ name = "${util.program_name()}" path = "src/main.rs" % endif -[dev-dependencies] -hyper-rustls = "^0.6" - [dependencies] -hyper = "^ 0.10" +hyper = "^ 0.14" +hyper-rustls = "^0.22" ## Must match the one hyper uses, otherwise there are duplicate similarly named `Mime` structs mime = "^ 0.2.0" serde = "^ 1.0" serde_json = "^ 1.0" serde_derive = "^ 1.0" -yup-oauth2 = { version = "^ 1.0", default-features = false } +yup-oauth2 = "^ 5.0" +tokio = "^ 1.0" +itertools = "^ 0.10" % for dep in cargo.get('dependencies', list()): ${dep} % endfor -[features] -default = ["openssl"] -openssl = ["yup-oauth2/default"] -rustls = ["yup-oauth2/no-openssl"] <% api_name = util.library_name() crate_name_we_depend_on = None diff --git a/src/mako/api/api.rs.mako b/src/mako/api/api.rs.mako index 59ab1a9680..d735276985 100644 --- a/src/mako/api/api.rs.mako +++ b/src/mako/api/api.rs.mako @@ -50,7 +50,7 @@ ${lib.hub_usage_example(c)}\ pub struct ${hub_type}${ht_params} { client: RefCell, - auth: RefCell, + auth: RefCell>>, _user_agent: String, _base_url: String, _root_url: String, @@ -61,7 +61,7 @@ impl<'a, ${', '.join(HUB_TYPE_PARAMETERS)}> client::Hub for ${hub_type}${ht_para impl<'a, ${', '.join(HUB_TYPE_PARAMETERS)}> ${hub_type}${ht_params} where ${', '.join(hub_type_bounds())} { - pub fn new(client: C, authenticator: A) -> ${hub_type}${ht_params} { + pub fn new(client: C, authenticator: oauth2::authenticator::Authenticator>) -> ${hub_type}${ht_params} { ${hub_type} { client: RefCell::new(client), auth: RefCell::new(authenticator), diff --git a/src/mako/api/lib/mbuild.mako b/src/mako/api/lib/mbuild.mako index 618c671d92..e267f570ea 100644 --- a/src/mako/api/lib/mbuild.mako +++ b/src/mako/api/lib/mbuild.mako @@ -412,7 +412,7 @@ match result { where = '' qualifier = 'pub ' add_args = '' - rtype = 'client::Result' + rtype = 'client::Result>' response_schema = method_response(c, m) supports_download = m.get('supportsMediaDownload', False); @@ -420,7 +420,7 @@ match result { if response_schema: if not supports_download: reserved_params = ['alt'] - rtype = 'client::Result<(hyper::client::Response, %s)>' % (response_schema.id) + rtype = 'client::Result<(hyper::Response, %s)>' % (response_schema.id) mtype_param = 'RS' @@ -440,9 +440,9 @@ match result { # end handle media params if doit_without_upload: - action_fn = qualifier + 'fn ' + "doit_without_upload" + type_params + '(mut self)' + ' -> ' + rtype + where + action_fn = qualifier + 'async fn ' + "doit_without_upload" + type_params + '(mut self)' + ' -> ' + rtype + where else: - action_fn = qualifier + 'fn ' + api.terms.action + type_params + ('(mut self%s)' % add_args) + ' -> ' + rtype + where + action_fn = qualifier + 'async fn ' + api.terms.action + type_params + ('(mut self%s)' % add_args) + ' -> ' + rtype + where field_params = [p for p in params if p.get('is_query_param', True)] @@ -504,7 +504,7 @@ match result { use url::percent_encoding::{percent_encode, DEFAULT_ENCODE_SET}; % endif use std::io::{Read, Seek}; - use hyper::header::{ContentType, ContentLength, Authorization, Bearer, UserAgent, Location}; + use hyper::header::{CONTENT_TYPE, CONTENT_LENGTH, AUTHORIZATION, USER_AGENT, LOCATION}; use client::ToParts; let mut dd = client::DefaultDelegate; let mut dlg: &mut dyn client::Delegate = match ${delegate} { @@ -678,10 +678,10 @@ else { } % endif - let url = hyper::Url::parse_with_params(&url, params).unwrap(); + let url = url::Url::parse_with_params(&url, params).unwrap(); % if request_value: - let mut json_mime_type = mime::Mime(mime::TopLevel::Application, mime::SubLevel::Json, Default::default()); + let mut json_mime_type: mime::Mime = "application/json".parse().unwrap(); let mut request_value_reader = { let mut value = json::value::to_value(&self.${property(REQUEST_VALUE_PROPERTY_NAME)}).expect("serde to work"); @@ -702,10 +702,11 @@ else { loop { % if default_scope: - let token = match ${auth_call}.token(self.${api.properties.scopes}.keys()) { - Ok(token) => token, + let authenticator = ${auth_call}; + let token = match authenticator.token(&self.${api.properties.scopes}.keys().collect::>()[..]).await { + Ok(token) => token.clone(), Err(err) => { - match dlg.token(&*err) { + match dlg.token(&err) { Some(token) => token, None => { ${delegate_finish}(false); @@ -714,7 +715,6 @@ else { } } }; - let auth_header = Authorization(Bearer { token: token.access_token }); % endif % if request_value: request_value_reader.seek(io::SeekFrom::Start(0)).unwrap(); @@ -724,12 +724,12 @@ else { if should_ask_dlg_for_url && (upload_url = dlg.upload_url()) == () && upload_url.is_some() { should_ask_dlg_for_url = false; upload_url_from_server = false; - let url = upload_url.as_ref().and_then(|s| Some(hyper::Url::parse(s).unwrap())).unwrap(); - hyper::client::Response::new(url, Box::new(client::DummyNetworkStream)).and_then(|mut res| { - res.status = hyper::status::StatusCode::Ok; - res.headers.set(Location(upload_url.as_ref().unwrap().clone())); - Ok(res) - }) + let url = upload_url.as_ref().and_then(|s| Some(url::Url::parse(s).unwrap())).unwrap(); + Ok(hyper::Response::builder() + .status(hyper::StatusCode::OK) + .header("Localtion", upload_url.as_ref().unwrap().clone()) + .body(hyper::body::Body::empty()) + .unwrap()) } else { % endif <%block filter="indent_by(resumable_media_param and 4 or 0)">\ @@ -742,48 +742,59 @@ else { mp_reader.add_part(&mut request_value_reader, request_size, json_mime_type.clone()) .add_part(&mut reader, size, reader_mime_type.clone()); let mime_type = mp_reader.mime_type(); - (&mut mp_reader as &mut dyn io::Read, ContentType(mime_type)) + (&mut mp_reader as &mut dyn io::Read, (CONTENT_TYPE, format!("{}", mime_type))) }, - _ => (&mut request_value_reader as &mut dyn io::Read, ContentType(json_mime_type.clone())), + _ => (&mut request_value_reader as &mut dyn io::Read, (CONTENT_TYPE, format!("{}", json_mime_type))), }; % endif let mut client = &mut *self.hub.client.borrow_mut(); - let mut req = client.borrow_mut().request(${method_name_to_variant(m.httpMethod)}, url.clone()) - .header(UserAgent(self.hub._user_agent.clone()))\ - % if default_scope: - - .header(auth_header.clone())\ - % endif - % if request_value: - % if not simple_media_param: - - .header(ContentType(json_mime_type.clone())) - .header(ContentLength(request_size as u64)) - .body(&mut request_value_reader)\ - % else: - - .header(content_type) - .body(&mut body_reader)\ - % endif ## not simple_media_param - % endif + dlg.pre_request(); + let mut req_builder = hyper::Request::builder().method(${method_name_to_variant(m.httpMethod)}).uri(url.clone().into_string()) + .header(USER_AGENT, self.hub._user_agent.clone())\ + % if default_scope: + .header(AUTHORIZATION, format!("Bearer {}", token.as_str()))\ + % endif ; - % if simple_media_param and not request_value: - if protocol == "${simple_media_param.protocol}" { - ${READER_SEEK | indent_all_but_first_by(4)} - req = req.header(ContentType(reader_mime_type.clone())) - .header(ContentLength(size)) - .body(&mut reader); - } - % endif ## media upload handling + % if resumable_media_param: upload_url_from_server = true; if protocol == "${resumable_media_param.protocol}" { - req = req.header(client::XUploadContentType(reader_mime_type.clone())); + req_builder = req_builder.header("X-Upload-Content-Type", format!("{}", reader_mime_type)); } % endif - dlg.pre_request(); - req.send() + % if request_value: + % if not simple_media_param: + let request = req_builder + .header(CONTENT_TYPE, format!("{}", json_mime_type)) + .header(CONTENT_LENGTH, request_size as u64) + .body(hyper::body::Body::from(request_value_reader.get_ref().clone()))\ + % else: + let mut body_reader_bytes = vec![]; + body_reader.read_to_end(&mut body_reader_bytes).unwrap(); + let request = req_builder + .header(content_type.0, content_type.1) + .body(hyper::body::Body::from(body_reader_bytes))\ + % endif ## not simple_media_param + % else: + % if simple_media_param: + let request = if protocol == "${simple_media_param.protocol}" { + ${READER_SEEK | indent_all_but_first_by(4)} + req_builder.header(CONTENT_TYPE, reader_mime_type.clone()) + .header(CONTENT_LENGTH, size) + .body(&mut reader); + } else { + req_builder.body(hyper::body::Body::empty()) + }\ + % else: + let request = req_builder + .body(hyper::body::Body::empty())\ + % endif + % endif +; + + client.borrow_mut().request(request.unwrap()).await + \ % if resumable_media_param: } @@ -792,7 +803,7 @@ else { match req_result { Err(err) => { - if let oauth2::Retry::After(d) = dlg.http_error(&err) { + if let client::Retry::After(d) = dlg.http_error(&err) { sleep(d); continue; } @@ -800,24 +811,33 @@ else { return Err(client::Error::HttpError(err)) } Ok(mut res) => { - if !res.status.is_success() { - let mut json_err = String::new(); - res.read_to_string(&mut json_err).unwrap(); + let (res_parts, res_body) = res.into_parts(); + let res_body_string: String = String::from_utf8( + hyper::body::to_bytes(res_body) + .await + .unwrap() + .into_iter() + .collect(), + ) + .unwrap(); + let reconstructed_result = + hyper::Response::from_parts(res_parts, res_body_string.clone().into()); - let json_server_error = json::from_str::(&json_err).ok(); - let server_error = json::from_str::(&json_err) - .or_else(|_| json::from_str::(&json_err).map(|r| r.error)) + if !reconstructed_result.status().is_success() { + let json_server_error = json::from_str::(&res_body_string).ok(); + let server_error = json::from_str::(&res_body_string) + .or_else(|_| json::from_str::(&res_body_string).map(|r| r.error)) .ok(); - if let oauth2::Retry::After(d) = dlg.http_failure(&res, + if let client::Retry::After(d) = dlg.http_failure(&reconstructed_result, json_server_error, server_error) { sleep(d); continue; } ${delegate_finish}(false); - return match json::from_str::(&json_err){ - Err(_) => Err(client::Error::Failure(res)), + return match json::from_str::(&res_body_string){ + Err(_) => Err(client::Error::Failure(reconstructed_result)), Ok(serr) => Err(client::Error::BadRequest(serr)) } } @@ -826,7 +846,7 @@ else { ${READER_SEEK | indent_all_but_first_by(6)} let mut client = &mut *self.hub.client.borrow_mut(); let upload_result = { - let url_str = &res.headers.get::().expect("Location header is part of protocol").0; + let url_str = &reconstructed_result.headers().get("Location").expect("LOCATION header is part of protocol").to_str().unwrap(); if upload_url_from_server { dlg.store_upload_url(Some(url_str)); } @@ -837,12 +857,12 @@ else { start_at: if upload_url_from_server { Some(0) } else { None }, auth: &mut *self.hub.auth.borrow_mut(), user_agent: &self.hub._user_agent, - auth_header: auth_header.clone(), + auth_header: format!("Bearer {}", token.as_str()), url: url_str, reader: &mut reader, media_type: reader_mime_type.clone(), content_length: size - }.upload() + }.upload().await }; match upload_result { None => { @@ -858,7 +878,7 @@ else { ## decoded next Some(Ok(upload_result)) => { res = upload_result; - if !res.status.is_success() { + if !res.status().is_success() { ## delegate was called in upload() already - don't tell him again dlg.store_upload_url(None); ${delegate_finish}(false); @@ -875,22 +895,20 @@ else { if enable_resource_parsing \ % endif { - let mut json_response = String::new(); - res.read_to_string(&mut json_response).unwrap(); - match json::from_str(&json_response) { - Ok(decoded) => (res, decoded), + match json::from_str(&res_body_string) { + Ok(decoded) => (reconstructed_result, decoded), Err(err) => { - dlg.response_json_decode_error(&json_response, &err); - return Err(client::Error::JsonDecodeError(json_response, err)); + dlg.response_json_decode_error(&res_body_string, &err); + return Err(client::Error::JsonDecodeError(res_body_string, err)); } } }\ % if supports_download: - else { (res, Default::default()) }\ + else { (reconstructed_result, Default::default()) }\ % endif ; % else: - let result_value = res; + let result_value = reconstructed_result; % endif ${delegate_finish}(true); @@ -906,9 +924,9 @@ if enable_resource_parsing \ % for item_name, item in p.info.items(): /// * *${split_camelcase_s(item_name)}*: ${isinstance(item, (list, tuple)) and put_and(enclose_in("'", item)) or str(item)} % endfor - pub fn ${upload_action_fn(api.terms.upload_action, p.type.suffix)}<${mtype_param}>(self, ${p.type.arg_name}: ${mtype_param}, mime_type: mime::Mime) -> ${rtype} + pub async fn ${upload_action_fn(api.terms.upload_action, p.type.suffix)}<${mtype_param}>(self, ${p.type.arg_name}: ${mtype_param}, mime_type: mime::Mime) -> ${rtype} where ${mtype_param}: client::ReadSeek { - self.${api.terms.action}(${p.type.arg_name}, mime_type, "${p.protocol}") + self.${api.terms.action}(${p.type.arg_name}, mime_type, "${p.protocol}").await } % endfor diff --git a/src/mako/cli/lib/engine.mako b/src/mako/cli/lib/engine.mako index 99b99a56b7..3f742b294a 100644 --- a/src/mako/cli/lib/engine.mako +++ b/src/mako/cli/lib/engine.mako @@ -49,7 +49,7 @@ enum DoitError { struct Engine<'n> { opt: ArgMatches<'n>, - hub: ${hub_type_name}>, + hub: ${hub_type_name}, hyper::body::Body>, Authenticator, hyper::body::Body>>>, gp: ${"Vec<&'static str>"}, gpm: Vec<(&'static str, &'static str)>, } diff --git a/src/mako/lib/util.py b/src/mako/lib/util.py index d125ecbdfa..083365570e 100644 --- a/src/mako/lib/util.py +++ b/src/mako/lib/util.py @@ -110,7 +110,7 @@ data_unit_multipliers = { '%': 1, } -HUB_TYPE_PARAMETERS = ('C', 'A') +HUB_TYPE_PARAMETERS = ('C',) def items(p): if isinstance(p, dict): @@ -899,8 +899,7 @@ def hub_type_params_s(): # return a list of where statements to server as bounds for the hub. def hub_type_bounds(): - return ['C: BorrowMut', - 'A: oauth2::GetToken'] + return ['C: BorrowMut, hyper::body::Body>>'] # Returns True if this API has particular authentication scopes to choose from def supports_scopes(auth): @@ -1025,11 +1024,11 @@ def scope_url_to_variant(name, url, fully_qualified=True): return fqvn(dot_sep_to_canonical_type_name(repl(base))) def method_name_to_variant(name): - fmt = 'hyper::method::Method::Extension("%s")' + name = name.upper() + fmt = 'hyper::Method.from_str("%s")' if name in HTTP_METHODS: - name = name.capitalize() - fmt = 'hyper::method::Method::%s' - return fmt % name.capitalize() + fmt = 'hyper::Method::%s' + return fmt % name # given a rust type-name (no optional, as from to_rust_type), you will get a suitable random default value # as string suitable to be passed as reference (or copy, where applicable) diff --git a/src/mako/rustfmt.toml.mako b/src/mako/rustfmt.toml.mako new file mode 100644 index 0000000000..f95f984ad9 --- /dev/null +++ b/src/mako/rustfmt.toml.mako @@ -0,0 +1,2 @@ +# Required for async keyword to be recognised +edition = "2018" diff --git a/src/rust/api/client.rs b/src/rust/api/client.rs index defa1cd2ab..dbf821a6b1 100644 --- a/src/rust/api/client.rs +++ b/src/rust/api/client.rs @@ -6,18 +6,27 @@ use std::str::FromStr; use std::thread::sleep; use std::time::Duration; +use itertools::Itertools; + use hyper; -use hyper::header::{ - Authorization, Bearer, ContentLength, ContentType, Header, HeaderFormat, Headers, UserAgent, -}; -use hyper::http::h1::LINE_ENDING; -use hyper::method::Method; -use hyper::status::StatusCode; +use hyper::header::{HeaderMap, AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE, USER_AGENT}; +use hyper::Method; +use hyper::StatusCode; + use mime::{Attr, Mime, SubLevel, TopLevel, Value}; -use oauth2::{self, Retry, TokenType}; +use oauth2; use serde_json as json; +const LINE_ENDING: &'static str = "\r\n"; + +pub enum Retry { + /// Signal you don't want to retry + Abort, + /// Signals you want to retry after the given duration + After(Duration), +} + /// Identifies the Hub. There is only one per library, this trait is supposed /// to make intended use more explicit. /// The hub allows to access all resource methods more easily. @@ -91,39 +100,6 @@ pub struct ServerMessage { pub location: Option, } -#[derive(Copy, Clone)] -pub struct DummyNetworkStream; - -impl Read for DummyNetworkStream { - fn read(&mut self, _: &mut [u8]) -> io::Result { - Ok(0) - } -} - -impl Write for DummyNetworkStream { - fn write(&mut self, _: &[u8]) -> io::Result { - Ok(0) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl hyper::net::NetworkStream for DummyNetworkStream { - fn peer_addr(&mut self) -> io::Result { - Ok("127.0.0.1:1337".parse().unwrap()) - } - - fn set_read_timeout(&self, _dur: Option) -> io::Result<()> { - Ok(()) - } - - fn set_write_timeout(&self, _dur: Option) -> io::Result<()> { - Ok(()) - } -} - /// A trait specifying functionality to help controlling any request performed by the API. /// The trait has a conservative default implementation. /// @@ -136,7 +112,7 @@ pub trait Delegate { /// The matching `finished()` call will always be made, no matter whether or not the API /// request was successful. That way, the delegate may easily maintain a clean state /// between various API calls. - fn begin(&mut self, MethodInfo) {} + fn begin(&mut self, _info: MethodInfo) {} /// Called whenever there is an [HttpError](hyper::Error), usually if there are network problems. /// @@ -144,7 +120,7 @@ pub trait Delegate { /// [exponential backoff algorithm](http://en.wikipedia.org/wiki/Exponential_backoff). /// /// Return retry information. - fn http_error(&mut self, &hyper::Error) -> Retry { + fn http_error(&mut self, _err: &hyper::Error) -> Retry { Retry::Abort } @@ -160,7 +136,7 @@ pub trait Delegate { /// impending failure. /// The given Error provides information about why the token couldn't be acquired in the /// first place - fn token(&mut self, err: &dyn error::Error) -> Option { + fn token(&mut self, err: &oauth2::Error) -> Option { let _ = err; None } @@ -213,8 +189,8 @@ pub trait Delegate { /// [exponential backoff algorithm](http://en.wikipedia.org/wiki/Exponential_backoff). fn http_failure( &mut self, - _: &hyper::client::Response, - Option, + _: &hyper::Response, + _err: Option, _: Option, ) -> Retry { Retry::Abort @@ -279,7 +255,7 @@ pub enum Error { MissingAPIKey, /// We required a Token, but didn't get one from the Authenticator - MissingToken(Box), + MissingToken(oauth2::Error), /// The delgate instructed to cancel the operation Cancelled, @@ -292,7 +268,7 @@ pub enum Error { JsonDecodeError(String, json::Error), /// Indicates an HTTP repsonse with a non-success status code - Failure(hyper::client::Response), + Failure(hyper::Response), } impl Display for Error { @@ -376,7 +352,7 @@ const BOUNDARY: &'static str = "MDuXWGyeE33QFXGchb2VFWc4Z7945d"; /// to google APIs, and might not be a fully-featured implementation. #[derive(Default)] pub struct MultiPartReader<'a> { - raw_parts: Vec<(Headers, &'a mut dyn Read)>, + raw_parts: Vec<(HeaderMap, &'a mut dyn Read)>, current_part: Option<(Cursor>, &'a mut dyn Read)>, last_part_boundary: Option>>, } @@ -404,9 +380,12 @@ impl<'a> MultiPartReader<'a> { size: u64, mime_type: Mime, ) -> &mut MultiPartReader<'a> { - let mut headers = Headers::new(); - headers.set(ContentType(mime_type)); - headers.set(ContentLength(size)); + let mut headers = HeaderMap::new(); + headers.insert( + CONTENT_TYPE, + hyper::header::HeaderValue::from_str(&format!("{}", mime_type)).unwrap(), + ); + headers.insert(CONTENT_LENGTH, size.into()); self.raw_parts.push((headers, reader)); self } @@ -463,7 +442,14 @@ impl<'a> Read for MultiPartReader<'a> { (write!( &mut c, "{}--{}{}{}{}", - LINE_ENDING, BOUNDARY, LINE_ENDING, headers, LINE_ENDING + LINE_ENDING, + BOUNDARY, + LINE_ENDING, + headers + .iter() + .map(|(k, v)| format!("{}: {}", k, v.to_str().unwrap())) + .join(LINE_ENDING), + LINE_ENDING )) .unwrap(); c.seek(SeekFrom::Start(0)).unwrap(); @@ -530,19 +516,6 @@ impl ::std::ops::DerefMut for XUploadContentType { &mut self.0 } } -impl Header for XUploadContentType { - fn header_name() -> &'static str { - "X-Upload-Content-Type" - } - fn parse_header(raw: &[Vec]) -> hyper::error::Result { - hyper::header::parsing::from_one_raw_str(raw).map(XUploadContentType) - } -} -impl HeaderFormat for XUploadContentType { - fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result { - Display::fmt(&**self, f) - } -} impl Display for XUploadContentType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&**self, f) @@ -591,98 +564,89 @@ pub struct ContentRange { pub total_length: u64, } -impl Header for ContentRange { - fn header_name() -> &'static str { - "Content-Range" - } - - /// We are not parsable, as parsing is done by the `Range` header - fn parse_header(_: &[Vec]) -> hyper::error::Result { - Err(hyper::error::Error::Method) - } -} - -impl HeaderFormat for ContentRange { - fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.write_str("bytes ")?; - match self.range { - Some(ref c) => c.fmt(fmt)?, - None => fmt.write_str("*")?, - } - (write!(fmt, "/{}", self.total_length)).ok(); - Ok(()) +impl ContentRange { + pub fn header_value(&self) -> String { + format!( + "bytes {}/{}", + match self.range { + Some(ref c) => format!("{}", c), + None => "*".to_string(), + }, + self.total_length + ) } } #[derive(Clone, PartialEq, Debug)] pub struct RangeResponseHeader(pub Chunk); -impl Header for RangeResponseHeader { - fn header_name() -> &'static str { - "Range" - } - - fn parse_header(raw: &[Vec]) -> hyper::error::Result { +impl RangeResponseHeader { + fn from_bytes(raw: &[u8]) -> Self { if raw.len() > 0 { - let v = &raw[0]; - if let Ok(s) = std::str::from_utf8(v) { + if let Ok(s) = std::str::from_utf8(raw) { const PREFIX: &'static str = "bytes "; if s.starts_with(PREFIX) { if let Ok(c) = ::from_str(&s[PREFIX.len()..]) { - return Ok(RangeResponseHeader(c)); + return RangeResponseHeader(c); } } } } - Err(hyper::error::Error::Method) - } -} -impl HeaderFormat for RangeResponseHeader { - /// No implmentation necessary, we just need to parse - fn fmt_header(&self, _: &mut fmt::Formatter) -> fmt::Result { - Err(fmt::Error) + panic!(format!("Unable to parse Range header {:?}", raw)) } } /// A utility type to perform a resumable upload from start to end. pub struct ResumableUploadHelper<'a, A: 'a> { - pub client: &'a mut hyper::client::Client, + pub client: &'a mut hyper::client::Client< + hyper_rustls::HttpsConnector, + hyper::body::Body, + >, pub delegate: &'a mut dyn Delegate, pub start_at: Option, pub auth: &'a mut A, pub user_agent: &'a str, - pub auth_header: Authorization, + pub auth_header: String, pub url: &'a str, pub reader: &'a mut dyn ReadSeek, pub media_type: Mime, pub content_length: u64, } -impl<'a, A> ResumableUploadHelper<'a, A> -where - A: oauth2::GetToken, -{ - fn query_transfer_status( +impl<'a, A> ResumableUploadHelper<'a, A> { + async fn query_transfer_status( &mut self, - ) -> std::result::Result> { + ) -> std::result::Result>> { loop { match self .client - .post(self.url) - .header(UserAgent(self.user_agent.to_string())) - .header(ContentRange { - range: None, - total_length: self.content_length, - }) - .header(self.auth_header.clone()) - .send() + .request( + hyper::Request::builder() + .method(hyper::Method::POST) + .uri(self.url) + .header(USER_AGENT, self.user_agent.to_string()) + .header( + "Content-Range", + ContentRange { + range: None, + total_length: self.content_length, + } + .header_value(), + ) + .header(AUTHORIZATION, self.auth_header.clone()) + .body(hyper::body::Body::empty()) + .unwrap(), + ) + .await { Ok(r) => { // 308 = resume-incomplete == PermanentRedirect - let headers = r.headers.clone(); - let h: &RangeResponseHeader = match headers.get() { - Some(hh) if r.status == StatusCode::PermanentRedirect => hh, + let headers = r.headers().clone(); + let h: RangeResponseHeader = match headers.get("Range") { + Some(hh) if r.status() == StatusCode::PERMANENT_REDIRECT => { + RangeResponseHeader::from_bytes(hh.as_bytes()) + } None | Some(_) => { if let Retry::After(d) = self.delegate.http_failure(&r, None, None) { sleep(d); @@ -707,10 +671,10 @@ where /// returns None if operation was cancelled by delegate, or the HttpResult. /// It can be that we return the result just because we didn't understand the status code - /// caller should check for status himself before assuming it's OK to use - pub fn upload(&mut self) -> Option> { + pub async fn upload(&mut self) -> Option>> { let mut start = match self.start_at { Some(s) => s, - None => match self.query_transfer_status() { + None => match self.query_transfer_status().await { Ok(s) => s, Err(result) => return Some(result), }, @@ -730,6 +694,8 @@ where }; let mut section_reader = self.reader.take(request_size); + let mut req_bytes = vec![]; + section_reader.read_to_end(&mut req_bytes).unwrap(); let range_header = ContentRange { range: Some(Chunk { first: start, @@ -743,30 +709,45 @@ where } let res = self .client - .post(self.url) - .header(range_header) - .header(ContentType(self.media_type.clone())) - .header(UserAgent(self.user_agent.to_string())) - .body(&mut section_reader) - .send(); + .request( + hyper::Request::builder() + .method(hyper::Method::POST) + .header("Content-Range", range_header.header_value()) + .header(CONTENT_TYPE, format!("{}", self.media_type)) + .header(USER_AGENT, self.user_agent.to_string()) + .body(hyper::body::Body::from(req_bytes)) + .unwrap(), + ) + .await; match res { - Ok(mut res) => { - if res.status == StatusCode::PermanentRedirect { + Ok(res) => { + if res.status() == StatusCode::PERMANENT_REDIRECT { continue; } - if !res.status.is_success() { - let mut json_err = String::new(); - res.read_to_string(&mut json_err).unwrap(); + + let (res_parts, res_body) = res.into_parts(); + let res_body_string: String = String::from_utf8( + hyper::body::to_bytes(res_body) + .await + .unwrap() + .into_iter() + .collect(), + ) + .unwrap(); + let reconstructed_result = + hyper::Response::from_parts(res_parts, res_body_string.clone().into()); + + if !reconstructed_result.status().is_success() { if let Retry::After(d) = self.delegate.http_failure( - &res, - json::from_str(&json_err).ok(), - json::from_str(&json_err).ok(), + &reconstructed_result, + json::from_str(&res_body_string).ok(), + json::from_str(&res_body_string).ok(), ) { sleep(d); continue; } } - return Some(Ok(res)); + return Some(Ok(reconstructed_result)); } Err(err) => { if let Retry::After(d) = self.delegate.http_error(&err) { diff --git a/src/rust/cli/client.rs b/src/rust/cli/client.rs index 0ed1896c0a..fe0bea3325 100644 --- a/src/rust/cli/client.rs +++ b/src/rust/cli/client.rs @@ -1,25 +1,24 @@ -use oauth2::{ApplicationSecret, ConsoleApplicationSecret, TokenStorage, Token}; +use clap::{App, SubCommand}; +use mime::Mime; +use oauth2::{ApplicationSecret, ConsoleApplicationSecret}; use serde_json as json; use serde_json::value::Value; -use mime::Mime; -use clap::{App, SubCommand}; use strsim; -use std::fs; use std::env; -use std::io; use std::error::Error as StdError; use std::fmt; +use std::fs; +use std::io; +use std::io::{stdout, Read, Write}; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::string::ToString; -use std::io::{Write, Read, stdout}; use std::default::Default; const FIELD_SEP: char = '.'; - pub enum ComplexType { Pod, Vec, @@ -81,12 +80,11 @@ pub fn remove_json_null_values(value: &mut Value) { } fn did_you_mean<'a>(v: &str, possible_values: &[&'a str]) -> Option<&'a str> { - let mut candidate: Option<(f64, &str)> = None; for pv in possible_values { let confidence = strsim::jaro_winkler(v, pv); - if confidence > 0.8 && - (candidate.is_none() || (candidate.as_ref().unwrap().0 < confidence)) { + if confidence > 0.8 && (candidate.is_none() || (candidate.as_ref().unwrap().0 < confidence)) + { candidate = Some((confidence, pv)); } } @@ -101,7 +99,7 @@ pub enum CallType { Standard, } -arg_enum!{ +arg_enum! { pub enum UploadProtocol { Simple, Resumable @@ -129,7 +127,7 @@ impl AsRef for CallType { #[derive(Clone, Default)] pub struct FieldCursor(Vec); -impl ToString for FieldCursor { +impl ToString for FieldCursor { fn to_string(&self) -> String { self.0.join(".") } @@ -206,7 +204,9 @@ impl FieldCursor { fields.truncate(0); } if char_count > 1 && num_conscutive_field_seps == 1 { - return Err(CLIError::Field(FieldError::TrailingFieldSep(value.to_string()))); + return Err(CLIError::Field(FieldError::TrailingFieldSep( + value.to_string(), + ))); } self.0 = fields; @@ -255,20 +255,20 @@ impl FieldCursor { } } - pub fn set_json_value(&self, - mut object: &mut Value, - value: &str, - type_info: JsonTypeInfo, - err: &mut InvalidOptionsError, - orig_cursor: &FieldCursor) { + pub fn set_json_value( + &self, + mut object: &mut Value, + value: &str, + type_info: JsonTypeInfo, + err: &mut InvalidOptionsError, + orig_cursor: &FieldCursor, + ) { assert!(self.0.len() > 0); for field in &self.0[..self.0.len() - 1] { let tmp = object; object = match *tmp { - Value::Object(ref mut mapping) => { - assure_entry(mapping, &field) - } + Value::Object(ref mut mapping) => assure_entry(mapping, &field), _ => panic!("We don't expect non-object Values here ..."), }; } @@ -276,58 +276,55 @@ impl FieldCursor { match *object { Value::Object(ref mut mapping) => { let field = &self.0[self.0.len() - 1]; - let to_jval = |value: &str, - jtype: JsonType, - err: &mut InvalidOptionsError| - -> Value { - match jtype { - JsonType::Boolean => - Value::Bool(arg_from_str(value, err, &field, "boolean")), - JsonType::Int => - Value::Number(json::Number::from_f64(arg_from_str(value, - err, - &field, - "int")) - .expect("valid f64")), - JsonType::Uint => - Value::Number(json::Number::from_f64(arg_from_str(value, - err, - &field, - "uint")) - .expect("valid f64")), - JsonType::Float => - Value::Number(json::Number::from_f64(arg_from_str(value, - err, - &field, - "float")) - .expect("valid f64")), - JsonType::String => Value::String(value.to_owned()), - } - }; + let to_jval = + |value: &str, jtype: JsonType, err: &mut InvalidOptionsError| -> Value { + match jtype { + JsonType::Boolean => { + Value::Bool(arg_from_str(value, err, &field, "boolean")) + } + JsonType::Int => Value::Number( + json::Number::from_f64(arg_from_str(value, err, &field, "int")) + .expect("valid f64"), + ), + JsonType::Uint => Value::Number( + json::Number::from_f64(arg_from_str(value, err, &field, "uint")) + .expect("valid f64"), + ), + JsonType::Float => Value::Number( + json::Number::from_f64(arg_from_str(value, err, &field, "float")) + .expect("valid f64"), + ), + JsonType::String => Value::String(value.to_owned()), + } + }; match type_info.ctype { ComplexType::Pod => { - if mapping.insert(field.to_owned(), to_jval(value, type_info.jtype, err)) - .is_some() { - err.issues.push(CLIError::Field(FieldError::Duplicate(orig_cursor.to_string()))); + if mapping + .insert(field.to_owned(), to_jval(value, type_info.jtype, err)) + .is_some() + { + err.issues.push(CLIError::Field(FieldError::Duplicate( + orig_cursor.to_string(), + ))); } } - ComplexType::Vec => { - match *assure_entry(mapping, field) { - Value::Array(ref mut values) => - values.push(to_jval(value, type_info.jtype, err)), - _ => unreachable!(), + ComplexType::Vec => match *assure_entry(mapping, field) { + Value::Array(ref mut values) => { + values.push(to_jval(value, type_info.jtype, err)) } - } + _ => unreachable!(), + }, ComplexType::Map => { let (key, value) = parse_kv_arg(value, err, true); let jval = to_jval(value.unwrap_or(""), type_info.jtype, err); match *assure_entry(mapping, &field) { - Value::Object(ref mut value_map) => { if value_map.insert(key.to_owned(), jval).is_some() { - err.issues.push(CLIError::Field(FieldError::Duplicate(orig_cursor.to_string()))); + err.issues.push(CLIError::Field(FieldError::Duplicate( + orig_cursor.to_string(), + ))); } } _ => unreachable!(), @@ -344,12 +341,14 @@ impl FieldCursor { } } -pub fn parse_kv_arg<'a>(kv: &'a str, - err: &mut InvalidOptionsError, - for_hashmap: bool) - -> (&'a str, Option<&'a str>) { +pub fn parse_kv_arg<'a>( + kv: &'a str, + err: &mut InvalidOptionsError, + for_hashmap: bool, +) -> (&'a str, Option<&'a str>) { let mut add_err = || { - err.issues.push(CLIError::InvalidKeyValueSyntax(kv.to_string(), for_hashmap)) + err.issues + .push(CLIError::InvalidKeyValueSyntax(kv.to_string(), for_hashmap)) }; match kv.find('=') { None => { @@ -367,14 +366,18 @@ pub fn parse_kv_arg<'a>(kv: &'a str, } } -pub fn calltype_from_str(name: &str, - valid_protocols: Vec, - err: &mut InvalidOptionsError) - -> CallType { +pub fn calltype_from_str( + name: &str, + valid_protocols: Vec, + err: &mut InvalidOptionsError, +) -> CallType { CallType::Upload(match UploadProtocol::from_str(name) { Ok(up) => up, Err(msg) => { - err.issues.push(CLIError::InvalidUploadProtocol(name.to_string(), valid_protocols)); + err.issues.push(CLIError::InvalidUploadProtocol( + name.to_string(), + valid_protocols, + )); UploadProtocol::Simple } }) @@ -384,7 +387,10 @@ pub fn input_file_from_opts(file_path: &str, err: &mut InvalidOptionsError) -> O match fs::File::open(file_path) { Ok(f) => Some(f), Err(io_err) => { - err.issues.push(CLIError::Input(InputError::Io((file_path.to_string(), io_err)))); + err.issues.push(CLIError::Input(InputError::Io(( + file_path.to_string(), + io_err, + )))); None } } @@ -394,7 +400,8 @@ pub fn input_mime_from_opts(mime: &str, err: &mut InvalidOptionsError) -> Option match mime.parse() { Ok(m) => Some(m), Err(_) => { - err.issues.push(CLIError::Input(InputError::Mime(mime.to_string()))); + err.issues + .push(CLIError::Input(InputError::Mime(mime.to_string()))); None } } @@ -404,120 +411,42 @@ pub fn writer_from_opts(arg: Option<&str>) -> Result, io::Error> let f = arg.unwrap_or("-"); match f { "-" => Ok(Box::new(stdout())), - _ => match fs::OpenOptions::new().create(true).truncate(true).write(true).open(f) { + _ => match fs::OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(f) + { Ok(f) => Ok(Box::new(f)), Err(io_err) => Err(io_err), }, } } - -pub fn arg_from_str<'a, T>(arg: &str, - err: &mut InvalidOptionsError, - arg_name: &'a str, - arg_type: &'a str) - -> T - where T: FromStr + Default, - ::Err: fmt::Display +pub fn arg_from_str<'a, T>( + arg: &str, + err: &mut InvalidOptionsError, + arg_name: &'a str, + arg_type: &'a str, +) -> T +where + T: FromStr + Default, + ::Err: fmt::Display, { match FromStr::from_str(arg) { Err(perr) => { - err.issues.push(CLIError::ParseError(arg_name.to_owned(), - arg_type.to_owned(), - arg.to_string(), - format!("{}", perr))); + err.issues.push(CLIError::ParseError( + arg_name.to_owned(), + arg_type.to_owned(), + arg.to_string(), + format!("{}", perr), + )); Default::default() } Ok(v) => v, } } -pub struct JsonTokenStorage { - pub program_name: &'static str, - pub db_dir: String, -} - -impl JsonTokenStorage { - fn path(&self, scope_hash: u64) -> PathBuf { - Path::new(&self.db_dir).join(&format!("{}-token-{}.json", self.program_name, scope_hash)) - } -} - - -#[derive(Debug)] -pub enum TokenStorageError { - Json(json::Error), - Io(io::Error), -} - -impl fmt::Display for TokenStorageError { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match *self { - TokenStorageError::Json(ref err) => writeln!(f, "Could not serialize secrets: {}", err), - TokenStorageError::Io(ref err) => writeln!(f, "Failed to write secret token: {}", err), - } - } -} - -impl StdError for TokenStorageError { - fn description(&self) -> &str { - "Failure when getting or setting the token storage" - } -} - - -impl TokenStorage for JsonTokenStorage { - type Error = TokenStorageError; - - // NOTE: logging might be interesting, currently we swallow all errors - fn set(&mut self, - scope_hash: u64, - _: &Vec<&str>, - token: Option) - -> Result<(), TokenStorageError> { - match token { - None => { - match fs::remove_file(self.path(scope_hash)) { - Err(err) => match err.kind() { - io::ErrorKind::NotFound => Ok(()), - _ => Err(TokenStorageError::Io(err)), - }, - Ok(_) => Ok(()), - } - } - Some(token) => { - match fs::OpenOptions::new().create(true).write(true).truncate(true).open(&self.path(scope_hash)) { - Ok(mut f) => { - match json::to_writer_pretty(&mut f, &token) { - Ok(_) => Ok(()), - Err(serde_err) => Err(TokenStorageError::Json(serde_err)), - } - } - Err(io_err) => Err(TokenStorageError::Io(io_err)), - } - } - } - } - - fn get(&self, scope_hash: u64, _: &Vec<&str>) -> Result, TokenStorageError> { - match fs::File::open(&self.path(scope_hash)) { - Ok(f) => { - match json::de::from_reader(f) { - Ok(token) => Ok(Some(token)), - Err(err) => Err(TokenStorageError::Json(err)), - } - } - Err(io_err) => { - match io_err.kind() { - io::ErrorKind::NotFound => Ok(None), - _ => Err(TokenStorageError::Io(io_err)), - } - } - } - } -} - - #[derive(Debug)] pub enum ApplicationSecretError { DecoderError((String, json::Error)), @@ -527,15 +456,16 @@ pub enum ApplicationSecretError { impl fmt::Display for ApplicationSecretError { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match *self { - ApplicationSecretError::DecoderError((ref path, ref err)) => - writeln!(f, - "Could not decode file at '{}' with error: {}.", - path, - err), - ApplicationSecretError::FormatError(ref path) => - writeln!(f, - "'installed' field is unset in secret file at '{}'.", - path), + ApplicationSecretError::DecoderError((ref path, ref err)) => writeln!( + f, + "Could not decode file at '{}' with error: {}.", + path, err + ), + ApplicationSecretError::FormatError(ref path) => writeln!( + f, + "'installed' field is unset in secret file at '{}'.", + path + ), } } } @@ -552,22 +482,23 @@ pub enum ConfigurationError { impl fmt::Display for ConfigurationError { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match *self { - ConfigurationError::DirectoryCreationFailed((ref dir, ref err)) => - writeln!(f, - "Directory '{}' could not be created with error: {}.", - dir, - err), + ConfigurationError::DirectoryCreationFailed((ref dir, ref err)) => writeln!( + f, + "Directory '{}' could not be created with error: {}.", + dir, err + ), ConfigurationError::DirectoryUnset => writeln!(f, "--config-dir was unset or empty."), - ConfigurationError::HomeExpansionFailed(ref dir) => - writeln!(f, - "Couldn't find HOME directory of current user, failed to expand '{}'.", - dir), + ConfigurationError::HomeExpansionFailed(ref dir) => writeln!( + f, + "Couldn't find HOME directory of current user, failed to expand '{}'.", + dir + ), ConfigurationError::Secret(ref err) => writeln!(f, "Secret -> {}", err), - ConfigurationError::Io((ref path, ref err)) => - writeln!(f, - "IO operation failed on path '{}' with error: {}.", - path, - err), + ConfigurationError::Io((ref path, ref err)) => writeln!( + f, + "IO operation failed on path '{}' with error: {}.", + path, err + ), } } } @@ -581,11 +512,11 @@ pub enum InputError { impl fmt::Display for InputError { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match *self { - InputError::Io((ref file_path, ref io_err)) => - writeln!(f, - "Failed to open '{}' for reading with error: {}.", - file_path, - io_err), + InputError::Io((ref file_path, ref io_err)) => writeln!( + f, + "Failed to open '{}' for reading with error: {}.", + file_path, io_err + ), InputError::Mime(ref mime) => writeln!(f, "'{}' is not a known mime-type.", mime), } } @@ -600,16 +531,17 @@ pub enum FieldError { Empty, } - impl fmt::Display for FieldError { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match *self { - FieldError::PopOnEmpty(ref field) => - writeln!(f, "'{}': Cannot move up on empty field cursor.", field), - FieldError::TrailingFieldSep(ref field) => - writeln!(f, - "'{}': Single field separator may not be last character.", - field), + FieldError::PopOnEmpty(ref field) => { + writeln!(f, "'{}': Cannot move up on empty field cursor.", field) + } + FieldError::TrailingFieldSep(ref field) => writeln!( + f, + "'{}': Single field separator may not be last character.", + field + ), FieldError::Unknown(ref field, ref suggestion, ref value) => { let suffix = match *suggestion { Some(ref s) => { @@ -623,14 +555,14 @@ impl fmt::Display for FieldError { }; writeln!(f, "Field '{}' does not exist.{}", field, suffix) } - FieldError::Duplicate(ref cursor) => - writeln!(f, "Value at '{}' was already set", cursor), + FieldError::Duplicate(ref cursor) => { + writeln!(f, "Value at '{}' was already set", cursor) + } FieldError::Empty => writeln!(f, "Field names must not be empty."), } } } - #[derive(Debug)] pub enum CLIError { Configuration(ConfigurationError), @@ -650,18 +582,17 @@ impl fmt::Display for CLIError { CLIError::Configuration(ref err) => write!(f, "Configuration -> {}", err), CLIError::Input(ref err) => write!(f, "Input -> {}", err), CLIError::Field(ref err) => write!(f, "Field -> {}", err), - CLIError::InvalidUploadProtocol(ref proto_name, ref valid_names) => - writeln!(f, - "'{}' is not a valid upload protocol. Choose from one of {}.", - proto_name, - valid_names.join(", ")), - CLIError::ParseError(ref arg_name, ref type_name, ref value, ref err_desc) => - writeln!(f, - "Failed to parse argument '{}' with value '{}' as {} with error: {}.", - arg_name, - value, - type_name, - err_desc), + CLIError::InvalidUploadProtocol(ref proto_name, ref valid_names) => writeln!( + f, + "'{}' is not a valid upload protocol. Choose from one of {}.", + proto_name, + valid_names.join(", ") + ), + CLIError::ParseError(ref arg_name, ref type_name, ref value, ref err_desc) => writeln!( + f, + "Failed to parse argument '{}' with value '{}' as {} with error: {}.", + arg_name, value, type_name, err_desc + ), CLIError::UnknownParameter(ref param_name, ref possible_values) => { let suffix = match did_you_mean(param_name, &possible_values) { Some(v) => format!(" Did you mean '{}' ?", v), @@ -670,21 +601,19 @@ impl fmt::Display for CLIError { write!(f, "Parameter '{}' is unknown.{}\n", param_name, suffix) } CLIError::InvalidKeyValueSyntax(ref kv, is_hashmap) => { - let hashmap_info = if is_hashmap { - "hashmap " - } else { - "" - }; - writeln!(f, - "'{}' does not match {}pattern =.", - kv, - hashmap_info) + let hashmap_info = if is_hashmap { "hashmap " } else { "" }; + writeln!( + f, + "'{}' does not match {}pattern =.", + kv, hashmap_info + ) } CLIError::MissingCommandError => writeln!(f, "Please specify the main sub-command."), - CLIError::MissingMethodError(ref cmd) => - writeln!(f, - "Please specify the method to call on the '{}' command.", - cmd), + CLIError::MissingMethodError(ref cmd) => writeln!( + f, + "Please specify the method to call on the '{}' command.", + cmd + ), } } } @@ -728,7 +657,11 @@ pub fn assure_config_dir_exists(dir: &str) -> Result { let expanded_config_dir = if trdir.as_bytes()[0] == b'~' { match env::var("HOME").ok().or(env::var("UserProfile").ok()) { - None => return Err(CLIError::Configuration(ConfigurationError::HomeExpansionFailed(trdir.to_string()))), + None => { + return Err(CLIError::Configuration( + ConfigurationError::HomeExpansionFailed(trdir.to_string()), + )) + } Some(mut user) => { user.push_str(&trdir[1..]); user @@ -741,21 +674,26 @@ pub fn assure_config_dir_exists(dir: &str) -> Result { if let Err(err) = fs::create_dir(&expanded_config_dir) { if err.kind() != io::ErrorKind::AlreadyExists { return Err(CLIError::Configuration( - ConfigurationError::DirectoryCreationFailed((expanded_config_dir, err)))); + ConfigurationError::DirectoryCreationFailed((expanded_config_dir, err)), + )); } } Ok(expanded_config_dir) } -pub fn application_secret_from_directory(dir: &str, - secret_basename: &str, - json_console_secret: &str) - -> Result { +pub fn application_secret_from_directory( + dir: &str, + secret_basename: &str, + json_console_secret: &str, +) -> Result { let secret_path = Path::new(dir).join(secret_basename); let secret_str = || secret_path.as_path().to_str().unwrap().to_string(); let secret_io_error = |io_err: io::Error| { - Err(CLIError::Configuration(ConfigurationError::Io((secret_str(), io_err)))) + Err(CLIError::Configuration(ConfigurationError::Io(( + secret_str(), + io_err, + )))) }; for _ in 0..2 { @@ -765,18 +703,20 @@ pub fn application_secret_from_directory(dir: &str, // Write our built-in one - user may adjust the written file at will err = match fs::OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(&secret_path) { + .create(true) + .write(true) + .truncate(true) + .open(&secret_path) + { Err(cfe) => cfe, Ok(mut f) => { // Assure we convert 'ugly' json string into pretty one let console_secret: ConsoleApplicationSecret = json::from_str(json_console_secret).unwrap(); match json::to_writer_pretty(&mut f, &console_secret) { - Err(serde_err) => - panic!("Unexpected serde error: {:#?}", serde_err), + Err(serde_err) => { + panic!("Unexpected serde error: {:#?}", serde_err) + } Ok(_) => continue, } } @@ -785,24 +725,21 @@ pub fn application_secret_from_directory(dir: &str, } return secret_io_error(err); } - Ok(f) => { - match json::de::from_reader::<_, ConsoleApplicationSecret>(f) { - Err(json_err) => - return Err(CLIError::Configuration( - ConfigurationError::Secret( - ApplicationSecretError::DecoderError( - (secret_str(), json_err) - )))), - Ok(console_secret) => match console_secret.installed { - Some(secret) => return Ok(secret), - None => return Err( - CLIError::Configuration( - ConfigurationError::Secret( - ApplicationSecretError::FormatError(secret_str()) - ))), - }, + Ok(f) => match json::de::from_reader::<_, ConsoleApplicationSecret>(f) { + Err(json_err) => { + return Err(CLIError::Configuration(ConfigurationError::Secret( + ApplicationSecretError::DecoderError((secret_str(), json_err)), + ))) } - } + Ok(console_secret) => match console_secret.installed { + Some(secret) => return Ok(secret), + None => { + return Err(CLIError::Configuration(ConfigurationError::Secret( + ApplicationSecretError::FormatError(secret_str()), + ))) + } + }, + }, } } unreachable!(); diff --git a/src/rust/lib.rs b/src/rust/lib.rs index aca0df4985..c25ce8dc59 100644 --- a/src/rust/lib.rs +++ b/src/rust/lib.rs @@ -1,4 +1,10 @@ -#![allow(dead_code, deprecated, unused_features, unused_variables, unused_imports)] +#![allow( + dead_code, + deprecated, + unused_features, + unused_variables, + unused_imports +)] #[macro_use] extern crate clap; @@ -7,9 +13,9 @@ extern crate clap; extern crate hyper; extern crate mime; extern crate rustc_serialize; -extern crate yup_oauth2 as oauth2; extern crate serde; extern crate serde_json; +extern crate yup_oauth2 as oauth2; #[macro_use] extern crate serde_derive; extern crate strsim; @@ -22,17 +28,16 @@ mod cli; #[cfg(test)] mod test_api { extern crate yup_hyper_mock as hyper_mock; - use super::api::client::*; use self::hyper_mock::*; - use std::io::Read; - use std::default::Default; + use super::api::client::*; use hyper; + use std::default::Default; + use std::io::Read; use std::str::FromStr; use serde_json as json; - const EXPECTED: &'static str = -"\r\n--MDuXWGyeE33QFXGchb2VFWc4Z7945d\r\n\ + const EXPECTED: &'static str = "\r\n--MDuXWGyeE33QFXGchb2VFWc4Z7945d\r\n\ Content-Length: 50\r\n\ Content-Type: application/json\r\n\ \r\n\ @@ -44,7 +49,7 @@ Content-Type: application/plain\r\n\ bar\r\n\ --MDuXWGyeE33QFXGchb2VFWc4Z7945d--"; - const EXPECTED_LEN: usize= 223; + const EXPECTED_LEN: usize = 223; #[test] fn multi_part_reader() { @@ -53,15 +58,15 @@ bar\r\n\ let mut mpr: MultiPartReader = Default::default(); mpr.add_part(&mut r1, 50, "application/json".parse().unwrap()) - .add_part(&mut r2, 25, "application/plain".parse().unwrap()); + .add_part(&mut r2, 25, "application/plain".parse().unwrap()); let mut res = String::new(); let r = mpr.read_to_string(&mut res).unwrap(); assert_eq!(res.len(), r); // NOTE: This CAN fail, as the underlying header hashmap is not sorted - // As the test is just for dev, and doesn't run on travis, we are fine, - // for now. Possible solution would be to omit the size field (make it + // As the test is just for dev, and doesn't run on travis, we are fine, + // for now. Possible solution would be to omit the size field (make it // optional) assert_eq!(r, EXPECTED_LEN); // assert_eq!(res, EXPECTED); @@ -74,7 +79,7 @@ bar\r\n\ let mut mpr: MultiPartReader = Default::default(); mpr.add_part(&mut r1, 50, "application/json".parse().unwrap()) - .add_part(&mut r2, 25, "application/plain".parse().unwrap()); + .add_part(&mut r2, 25, "application/plain".parse().unwrap()); let buf = &mut [0u8]; let mut v = Vec::::new(); @@ -111,12 +116,11 @@ bar\r\n\ #[derive(Default, Serialize, Deserialize)] struct Bar { - #[serde(rename="snooSnoo")] - snoo_snoo: String + #[serde(rename = "snooSnoo")] + snoo_snoo: String, } json::to_string(&::default()).unwrap(); - let j = "{\"snooSnoo\":\"foo\"}"; let b: Bar = json::from_str(&j).unwrap(); assert_eq!(b.snoo_snoo, "foo"); @@ -133,10 +137,25 @@ bar\r\n\ #[test] fn content_range() { - for &(ref c, ref expected) in - &[(ContentRange {range: None, total_length: 50 }, "Content-Range: bytes */50\r\n"), - (ContentRange {range: Some(Chunk { first: 23, last: 40 }), total_length: 45}, - "Content-Range: bytes 23-40/45\r\n")] { + for &(ref c, ref expected) in &[ + ( + ContentRange { + range: None, + total_length: 50, + }, + "Content-Range: bytes */50\r\n", + ), + ( + ContentRange { + range: Some(Chunk { + first: 23, + last: 40, + }), + total_length: 45, + }, + "Content-Range: bytes 23-40/45\r\n", + ), + ] { let mut headers = hyper::header::Headers::new(); headers.set(c.clone()); assert_eq!(headers.to_string(), expected.to_string()); @@ -145,13 +164,16 @@ bar\r\n\ #[test] fn byte_range_from_str() { - assert_eq!(::from_str("2-42"), - Ok(Chunk { first: 2, last: 42 })) + assert_eq!( + ::from_str("2-42"), + Ok(Chunk { first: 2, last: 42 }) + ) } #[test] fn parse_range_response() { - let r: RangeResponseHeader = hyper::header::Header::parse_header(&[b"bytes 2-42".to_vec()]).unwrap(); + let r: RangeResponseHeader = + hyper::header::Header::parse_header(&[b"bytes 2-42".to_vec()]).unwrap(); assert_eq!(r.0.first, 2); assert_eq!(r.0.last, 42); }