diff --git a/src/authenticator.rs b/src/authenticator.rs index 1b5a2de..b1824e4 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -2,14 +2,14 @@ use crate::authenticator_delegate::{ AuthenticatorDelegate, DefaultAuthenticatorDelegate, FlowDelegate, }; use crate::device::DeviceFlow; +use crate::error::RequestError; use crate::installed::{InstalledFlow, InstalledFlowReturnMethod}; use crate::refresh::RefreshFlow; use crate::storage::{self, Storage}; -use crate::types::{ApplicationSecret, RefreshResult, RequestError, Token}; +use crate::types::{ApplicationSecret, Token}; use private::AuthFlow; use std::borrow::Cow; -use std::error::Error; use std::io; use std::path::PathBuf; use std::sync::Mutex; @@ -42,32 +42,23 @@ where .. }) => { // token is expired but has a refresh token. - let rr = RefreshFlow::refresh_token( + let token = match RefreshFlow::refresh_token( &self.hyper_client, &self.app_secret, &refresh_token, ) - .await?; - match rr { - RefreshResult::Error(ref e) => { - self.auth_delegate.token_refresh_failed( - e.description(), - Some("the request has likely timed out"), - ); - Err(RequestError::Refresh(rr)) + .await + { + Err(err) => { + self.auth_delegate.token_refresh_failed(&err); + return Err(err.into()); } - RefreshResult::RefreshError(ref s, ref ss) => { - self.auth_delegate.token_refresh_failed( - &format!("{}{}", s, ss.as_ref().map(|s| format!(" ({})", s)).unwrap_or_else(String::new)), - Some("the refresh token is likely invalid and your authorization has been revoked"), - ); - Err(RequestError::Refresh(rr)) - } - RefreshResult::Success(t) => { - self.storage.set(scope_key, scopes, Some(t.clone())).await; - Ok(t) - } - } + Ok(token) => token, + }; + self.storage + .set(scope_key, scopes, Some(token.clone())) + .await; + Ok(token) } None | Some(Token { @@ -248,8 +239,10 @@ impl AuthenticatorBuilder { mod private { use crate::device::DeviceFlow; + use crate::error::RequestError; use crate::installed::InstalledFlow; - use crate::types::{ApplicationSecret, RequestError, Token}; + use crate::types::{ApplicationSecret, Token}; + pub enum AuthFlow { DeviceFlow(DeviceFlow), InstalledFlow(InstalledFlow), diff --git a/src/authenticator_delegate.rs b/src/authenticator_delegate.rs index b8fe106..92f5329 100644 --- a/src/authenticator_delegate.rs +++ b/src/authenticator_delegate.rs @@ -4,7 +4,7 @@ use std::error::Error; use std::fmt; use std::pin::Pin; -use crate::types::{PollError, RequestError}; +use crate::error::{PollError, RefreshError, RequestError}; use chrono::{DateTime, Local, Utc}; use std::time::Duration; @@ -85,14 +85,7 @@ pub trait AuthenticatorDelegate: Send + Sync { /// Called if we could not acquire a refresh token for a reason possibly specified /// by the server. /// This call is made for the delegate's information only. - fn token_refresh_failed(&self, error: &str, error_description: Option<&str>) { - { - let _ = error; - } - { - let _ = error_description; - } - } + fn token_refresh_failed(&self, _: &RefreshError) {} } /// FlowDelegate methods are called when an OAuth flow needs to ask the application what to do in diff --git a/src/device.rs b/src/device.rs index 373c36a..e1bdff6 100644 --- a/src/device.rs +++ b/src/device.rs @@ -10,7 +10,8 @@ use serde_json as json; use url::form_urlencoded; use crate::authenticator_delegate::{DefaultFlowDelegate, FlowDelegate, PollInformation, Retry}; -use crate::types::{ApplicationSecret, JsonErrorOr, PollError, RequestError, Token}; +use crate::error::{JsonErrorOr, PollError, RequestError}; +use crate::types::{ApplicationSecret, Token}; pub const GOOGLE_DEVICE_CODE_URL: &str = "https://accounts.google.com/o/oauth2/device/code"; @@ -166,20 +167,15 @@ impl DeviceFlow { } let json_bytes = resp.into_body().try_concat().await?; - match json::from_slice::>(&json_bytes)? { - JsonErrorOr::Err(e) => Err(e.into()), - JsonErrorOr::Data(decoded) => { - let expires_in = decoded.expires_in.unwrap_or(60 * 60); - - let pi = PollInformation { - user_code: decoded.user_code, - verification_url: decoded.verification_uri, - expires_at: Utc::now() + chrono::Duration::seconds(expires_in), - interval: Duration::from_secs(i64::abs(decoded.interval) as u64), - }; - Ok((pi, decoded.device_code)) - } - } + let decoded: JsonData = json::from_slice::>(&json_bytes)?.into_result()?; + let expires_in = decoded.expires_in.unwrap_or(60 * 60); + let pi = PollInformation { + user_code: decoded.user_code, + verification_url: decoded.verification_uri, + expires_at: Utc::now() + chrono::Duration::seconds(expires_in), + interval: Duration::from_secs(i64::abs(decoded.interval) as u64), + }; + Ok((pi, decoded.device_code)) } /// If the first call is successful, this method may be called. diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..191007a --- /dev/null +++ b/src/error.rs @@ -0,0 +1,174 @@ +use std::error::Error; +use std::fmt; +use std::io; + +use chrono::{DateTime, Utc}; + +#[derive(Deserialize, Debug)] +pub(crate) struct JsonError { + pub error: String, + pub error_description: Option, + pub error_uri: Option, +} + +/// A helper type to deserialize either a JsonError or another piece of data. +#[derive(Deserialize, Debug)] +#[serde(untagged)] +pub(crate) enum JsonErrorOr { + Err(JsonError), + Data(T), +} + +impl JsonErrorOr { + pub(crate) fn into_result(self) -> Result { + match self { + JsonErrorOr::Err(err) => Result::Err(err), + JsonErrorOr::Data(value) => Result::Ok(value), + } + } +} + +/// Encapsulates all possible results of a `poll_token(...)` operation in the Device flow. +#[derive(Debug)] +pub enum PollError { + /// Connection failure - retry if you think it's worth it + HttpError(hyper::Error), + /// Indicates we are expired, including the expiration date + Expired(DateTime), + /// Indicates that the user declined access. String is server response + AccessDenied, + /// Indicates that too many attempts failed. + TimedOut, + /// Other type of error. + Other(String), +} + +/// Encapsulates all possible results of the `token(...)` operation +#[derive(Debug)] +pub enum RequestError { + /// Indicates connection failure + ClientError(hyper::Error), + /// The OAuth client was not found + InvalidClient, + /// Some requested scopes were invalid. String contains the scopes as part of + /// the server error message + InvalidScope(String), + /// A 'catch-all' variant containing the server error and description + /// First string is the error code, the second may be a more detailed description + NegativeServerResponse(String, Option), + /// A malformed server response. + BadServerResponse(String), + /// Error while decoding a JSON response. + JSONError(serde_json::Error), + /// Error within user input. + UserError(String), + /// A lower level IO error. + LowLevelError(io::Error), + /// A poll error occurred in the DeviceFlow. + Poll(PollError), + /// An error occurred while refreshing tokens. + Refresh(RefreshError), + /// Error in token cache layer + Cache(Box), +} + +impl From for RequestError { + fn from(error: hyper::Error) -> RequestError { + RequestError::ClientError(error) + } +} + +impl From for RequestError { + fn from(value: JsonError) -> RequestError { + match &*value.error { + "invalid_client" => RequestError::InvalidClient, + "invalid_scope" => RequestError::InvalidScope( + value + .error_description + .unwrap_or_else(|| "no description provided".to_string()), + ), + _ => RequestError::NegativeServerResponse(value.error, value.error_description), + } + } +} + +impl From for RequestError { + fn from(value: serde_json::Error) -> RequestError { + RequestError::JSONError(value) + } +} + +impl From for RequestError { + fn from(value: RefreshError) -> RequestError { + RequestError::Refresh(value) + } +} + +impl fmt::Display for RequestError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match *self { + RequestError::ClientError(ref err) => err.fmt(f), + RequestError::InvalidClient => "Invalid Client".fmt(f), + RequestError::InvalidScope(ref scope) => writeln!(f, "Invalid Scope: '{}'", scope), + RequestError::NegativeServerResponse(ref error, ref desc) => { + error.fmt(f)?; + if let Some(ref desc) = *desc { + write!(f, ": {}", desc)?; + } + "\n".fmt(f) + } + RequestError::BadServerResponse(ref s) => s.fmt(f), + RequestError::JSONError(ref e) => format!( + "JSON Error; this might be a bug with unexpected server responses! {}", + e + ) + .fmt(f), + RequestError::UserError(ref s) => s.fmt(f), + RequestError::LowLevelError(ref e) => e.fmt(f), + RequestError::Poll(ref pe) => pe.fmt(f), + RequestError::Refresh(ref rr) => format!("{:?}", rr).fmt(f), + RequestError::Cache(ref e) => e.fmt(f), + } + } +} + +impl Error for RequestError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match *self { + RequestError::ClientError(ref err) => Some(err), + RequestError::LowLevelError(ref err) => Some(err), + RequestError::JSONError(ref err) => Some(err), + _ => None, + } + } +} + +/// All possible outcomes of the refresh flow +#[derive(Debug)] +pub enum RefreshError { + /// Indicates connection failure + ConnectionError(hyper::Error), + /// The server did not answer with a new token, providing the server message + ServerError(String, Option), +} + +impl From for RefreshError { + fn from(value: hyper::Error) -> Self { + RefreshError::ConnectionError(value) + } +} + +impl From for RefreshError { + fn from(value: JsonError) -> Self { + RefreshError::ServerError(value.error, value.error_description) + } +} + +impl From for RefreshError { + fn from(_value: serde_json::Error) -> Self { + RefreshError::ServerError( + "failed to deserialize json token from refresh response".to_owned(), + None, + ) + } +} diff --git a/src/installed.rs b/src/installed.rs index 8e1bf3d..b70979f 100644 --- a/src/installed.rs +++ b/src/installed.rs @@ -17,7 +17,8 @@ use url::form_urlencoded; use url::percent_encoding::{percent_encode, QUERY_ENCODE_SET}; use crate::authenticator_delegate::{DefaultFlowDelegate, FlowDelegate}; -use crate::types::{ApplicationSecret, JsonErrorOr, RequestError, Token}; +use crate::error::{JsonErrorOr, RequestError}; +use crate::types::{ApplicationSecret, Token}; const OOB_REDIRECT_URI: &str = "urn:ietf:wg:oauth:2.0:oob"; @@ -208,25 +209,21 @@ impl InstalledFlow { expires_in: Option, } - match serde_json::from_slice::>(&body)? { - JsonErrorOr::Err(err) => Err(err.into()), - JsonErrorOr::Data(JSONTokenResponse { - access_token, - refresh_token, - token_type, - expires_in, - }) => { - let mut token = Token { - access_token, - refresh_token, - token_type, - expires_in, - expires_in_timestamp: None, - }; - token.set_expiry_absolute(); - Ok(token) - } - } + let JSONTokenResponse { + access_token, + refresh_token, + token_type, + expires_in, + } = serde_json::from_slice::>(&body)?.into_result()?; + let mut token = Token { + access_token, + refresh_token, + token_type, + expires_in, + expires_in_timestamp: None, + }; + token.set_expiry_absolute(); + Ok(token) } /// Sends the authorization code to the provider in order to obtain access and refresh tokens. @@ -406,7 +403,6 @@ mod tests { use super::*; use crate::authenticator_delegate::FlowDelegate; use crate::helper::*; - use crate::types::StringError; #[test] fn test_end2end() { @@ -442,8 +438,7 @@ mod tests { } } if rduri.is_none() { - return Err(Box::new(StringError::new("no redirect uri!", None)) - as Box); + return Err("no redirect_uri!".into()); } let mut rduri = rduri.unwrap(); rduri.push_str(&format!("?code={}", self.0)); diff --git a/src/lib.rs b/src/lib.rs index 152f5b4..1e63c14 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,28 +71,26 @@ #[macro_use] extern crate serde_derive; -mod authenticator; -mod authenticator_delegate; +pub mod authenticator; +pub mod authenticator_delegate; mod device; +pub mod error; mod helper; mod installed; mod refresh; -mod service_account; +pub mod service_account; mod storage; mod types; -pub use crate::authenticator::{ - Authenticator, AuthenticatorBuilder, DeviceFlowAuthenticator, InstalledFlowAuthenticator, -}; -pub use crate::authenticator_delegate::{ - AuthenticatorDelegate, DefaultAuthenticatorDelegate, DefaultFlowDelegate, FlowDelegate, - PollInformation, -}; -pub use crate::device::GOOGLE_DEVICE_CODE_URL; +#[doc(inline)] +pub use crate::authenticator::{DeviceFlowAuthenticator, InstalledFlowAuthenticator}; + pub use crate::helper::*; pub use crate::installed::InstalledFlowReturnMethod; -pub use crate::service_account::*; -pub use crate::types::{ - ApplicationSecret, ConsoleApplicationSecret, PollError, RefreshResult, RequestError, Scheme, - Token, TokenType, -}; + +#[doc(inline)] +pub use crate::service_account::{ServiceAccountAuthenticator, ServiceAccountKey}; + +#[doc(inline)] +pub use crate::error::RequestError; +pub use crate::types::{ApplicationSecret, ConsoleApplicationSecret, Token}; diff --git a/src/refresh.rs b/src/refresh.rs index 7b33269..f6826be 100644 --- a/src/refresh.rs +++ b/src/refresh.rs @@ -1,4 +1,5 @@ -use crate::types::{ApplicationSecret, JsonErrorOr, RefreshResult, RequestError}; +use crate::error::{JsonErrorOr, RefreshError}; +use crate::types::ApplicationSecret; use super::Token; use chrono::Utc; @@ -33,7 +34,7 @@ impl RefreshFlow { client: &hyper::Client, client_secret: &ApplicationSecret, refresh_token: &str, - ) -> Result { + ) -> Result { // TODO: Does this function ever return RequestError? Maybe have it just return RefreshResult. let req = form_urlencoded::Serializer::new(String::new()) .extend_pairs(&[ @@ -49,14 +50,8 @@ impl RefreshFlow { .body(hyper::Body::from(req)) .unwrap(); // TODO: error handling - let resp = match client.request(request).await { - Ok(resp) => resp, - Err(err) => return Ok(RefreshResult::Error(err)), - }; - let body = match resp.into_body().try_concat().await { - Ok(body) => body, - Err(err) => return Ok(RefreshResult::Error(err)), - }; + let resp = client.request(request).await?; + let body = resp.into_body().try_concat().await?; #[derive(Deserialize)] struct JsonToken { @@ -65,27 +60,18 @@ impl RefreshFlow { expires_in: i64, } - match serde_json::from_slice::>(&body) { - Err(_) => Ok(RefreshResult::RefreshError( - "failed to deserialized json token from refresh response".to_owned(), - None, - )), - Ok(JsonErrorOr::Err(json_err)) => Ok(RefreshResult::RefreshError( - json_err.error, - json_err.error_description, - )), - Ok(JsonErrorOr::Data(JsonToken { - access_token, - token_type, - expires_in, - })) => Ok(RefreshResult::Success(Token { - access_token, - token_type, - refresh_token: Some(refresh_token.to_string()), - expires_in: None, - expires_in_timestamp: Some(Utc::now().timestamp() + expires_in), - })), - } + let JsonToken { + access_token, + token_type, + expires_in, + } = serde_json::from_slice::>(&body)?.into_result()?; + Ok(Token { + access_token, + token_type, + refresh_token: Some(refresh_token.to_string()), + expires_in: None, + expires_in_timestamp: Some(Utc::now().timestamp() + expires_in), + }) } } @@ -128,16 +114,11 @@ mod tests { .with_body(r#"{"access_token": "new-access-token", "token_type": "Bearer", "expires_in": 1234567}"#) .create(); let fut = async { - let rr = RefreshFlow::refresh_token(&client, &app_secret, refresh_token) + let token = RefreshFlow::refresh_token(&client, &app_secret, refresh_token) .await .unwrap(); - match rr { - RefreshResult::Success(tok) => { - assert_eq!("new-access-token", tok.access_token); - assert_eq!("Bearer", tok.token_type); - } - _ => panic!(format!("unexpected RefreshResult {:?}", rr)), - } + assert_eq!("new-access-token", token.access_token); + assert_eq!("Bearer", token.token_type); Ok(()) as Result<(), ()> }; @@ -154,11 +135,9 @@ mod tests { .create(); let fut = async { - let rr = RefreshFlow::refresh_token(&client, &app_secret, refresh_token) - .await - .unwrap(); + let rr = RefreshFlow::refresh_token(&client, &app_secret, refresh_token).await; match rr { - RefreshResult::RefreshError(e, None) => { + Err(RefreshError::ServerError(e, None)) => { assert_eq!(e, "invalid_token"); } _ => panic!(format!("unexpected RefreshResult {:?}", rr)), diff --git a/src/service_account.rs b/src/service_account.rs index eb013e7..c2732a2 100644 --- a/src/service_account.rs +++ b/src/service_account.rs @@ -14,8 +14,9 @@ use std::sync::Mutex; use crate::authenticator::{DefaultHyperClient, HyperClientBuilder}; +use crate::error::{JsonErrorOr, RequestError}; use crate::storage::{self, Storage}; -use crate::types::{JsonErrorOr, RequestError, Token}; +use crate::types::Token; use futures::prelude::*; use hyper::header; @@ -291,14 +292,13 @@ where expires_in: Option, } - match serde_json::from_slice::>(&body)? { - JsonErrorOr::Err(err) => Err(err.into()), - JsonErrorOr::Data(TokenResponse { + match serde_json::from_slice::>(&body)?.into_result()? { + TokenResponse { access_token: Some(access_token), token_type: Some(token_type), expires_in: Some(expires_in), .. - }) => { + } => { let expires_ts = chrono::Utc::now().timestamp() + expires_in; Ok(Token { access_token, @@ -308,7 +308,7 @@ where expires_in_timestamp: Some(expires_ts), }) } - JsonErrorOr::Data(token) => Err(RequestError::BadServerResponse(format!( + token => Err(RequestError::BadServerResponse(format!( "Token response lacks fields: {:?}", token ))), diff --git a/src/types.rs b/src/types.rs index 796cb90..cb044a1 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,247 +1,4 @@ use chrono::{DateTime, TimeZone, Utc}; -use hyper; -use std::error::Error; -use std::fmt; -use std::io; -use std::str::FromStr; - -#[derive(Deserialize, Debug)] -pub struct JsonError { - pub error: String, - pub error_description: Option, - pub error_uri: Option, -} - -/// A helper type to deserialize either a JsonError or another piece of data. -#[derive(Deserialize, Debug)] -#[serde(untagged)] -pub enum JsonErrorOr { - Err(JsonError), - Data(T), -} - -/// All possible outcomes of the refresh flow -#[derive(Debug)] -pub enum RefreshResult { - /// Indicates connection failure - Error(hyper::Error), - /// The server did not answer with a new token, providing the server message - RefreshError(String, Option), - /// The refresh operation finished successfully, providing a new `Token` - Success(Token), -} - -/// Encapsulates all possible results of a `poll_token(...)` operation in the Device flow. -#[derive(Debug)] -pub enum PollError { - /// Connection failure - retry if you think it's worth it - HttpError(hyper::Error), - /// Indicates we are expired, including the expiration date - Expired(DateTime), - /// Indicates that the user declined access. String is server response - AccessDenied, - /// Indicates that too many attempts failed. - TimedOut, - /// Other type of error. - Other(String), -} - -/// Encapsulates all possible results of the `token(...)` operation -#[derive(Debug)] -pub enum RequestError { - /// Indicates connection failure - ClientError(hyper::Error), - /// The OAuth client was not found - InvalidClient, - /// Some requested scopes were invalid. String contains the scopes as part of - /// the server error message - InvalidScope(String), - /// A 'catch-all' variant containing the server error and description - /// First string is the error code, the second may be a more detailed description - NegativeServerResponse(String, Option), - /// A malformed server response. - BadServerResponse(String), - /// Error while decoding a JSON response. - JSONError(serde_json::Error), - /// Error within user input. - UserError(String), - /// A lower level IO error. - LowLevelError(io::Error), - /// A poll error occurred in the DeviceFlow. - Poll(PollError), - /// An error occurred while refreshing tokens. - Refresh(RefreshResult), - /// Error in token cache layer - Cache(Box), -} - -impl From for RequestError { - fn from(error: hyper::Error) -> RequestError { - RequestError::ClientError(error) - } -} - -impl From for RequestError { - fn from(value: JsonError) -> RequestError { - match &*value.error { - "invalid_client" => RequestError::InvalidClient, - "invalid_scope" => RequestError::InvalidScope( - value - .error_description - .unwrap_or_else(|| "no description provided".to_string()), - ), - _ => RequestError::NegativeServerResponse(value.error, value.error_description), - } - } -} - -impl From for RequestError { - fn from(value: serde_json::Error) -> RequestError { - RequestError::JSONError(value) - } -} - -impl fmt::Display for RequestError { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match *self { - RequestError::ClientError(ref err) => err.fmt(f), - RequestError::InvalidClient => "Invalid Client".fmt(f), - RequestError::InvalidScope(ref scope) => writeln!(f, "Invalid Scope: '{}'", scope), - RequestError::NegativeServerResponse(ref error, ref desc) => { - error.fmt(f)?; - if let Some(ref desc) = *desc { - write!(f, ": {}", desc)?; - } - "\n".fmt(f) - } - RequestError::BadServerResponse(ref s) => s.fmt(f), - RequestError::JSONError(ref e) => format!( - "JSON Error; this might be a bug with unexpected server responses! {}", - e - ) - .fmt(f), - RequestError::UserError(ref s) => s.fmt(f), - RequestError::LowLevelError(ref e) => e.fmt(f), - RequestError::Poll(ref pe) => pe.fmt(f), - RequestError::Refresh(ref rr) => format!("{:?}", rr).fmt(f), - RequestError::Cache(ref e) => e.fmt(f), - } - } -} - -impl Error for RequestError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match *self { - RequestError::ClientError(ref err) => Some(err), - RequestError::LowLevelError(ref err) => Some(err), - RequestError::JSONError(ref err) => Some(err), - _ => None, - } - } -} - -#[derive(Debug)] -pub struct StringError { - error: String, -} - -impl fmt::Display for StringError { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - self.description().fmt(f) - } -} - -impl StringError { - pub fn new>(error: S, desc: Option) -> StringError { - let mut error = error.as_ref().to_string(); - if let Some(d) = desc { - error.push_str(": "); - error.push_str(d.as_ref()); - } - - StringError { error } - } -} - -impl<'a> From<&'a dyn Error> for StringError { - fn from(err: &'a dyn Error) -> StringError { - StringError::new(err.description().to_string(), None) - } -} - -impl From for StringError { - fn from(value: String) -> StringError { - StringError::new(value, None) - } -} - -impl Error for StringError { - fn description(&self) -> &str { - &self.error - } -} - -/// Represents all implemented token types -#[derive(Clone, PartialEq, Debug)] -pub enum TokenType { - /// Means that whoever bears the access token will be granted access - Bearer, -} - -impl AsRef for TokenType { - fn as_ref(&self) -> &'static str { - match *self { - TokenType::Bearer => "Bearer", - } - } -} - -impl FromStr for TokenType { - type Err = (); - fn from_str(s: &str) -> Result { - match s { - "Bearer" => Ok(TokenType::Bearer), - _ => Err(()), - } - } -} - -/// A scheme for use in `hyper::header::Authorization` -#[derive(Clone, PartialEq, Debug)] -pub struct Scheme { - /// The type of our access token - pub token_type: TokenType, - /// The token returned by one of the Authorization Flows - pub access_token: String, -} - -impl std::convert::Into for Scheme { - fn into(self) -> hyper::header::HeaderValue { - hyper::header::HeaderValue::from_str(&format!( - "{} {}", - self.token_type.as_ref(), - self.access_token - )) - .expect("Invalid Scheme header value") - } -} - -impl FromStr for Scheme { - type Err = &'static str; - fn from_str(s: &str) -> Result { - let parts: Vec<&str> = s.split(' ').collect(); - if parts.len() != 2 { - return Err("Expected two parts: "); - } - match ::from_str(parts[0]) { - Ok(t) => Ok(Scheme { - token_type: t, - access_token: parts[1].to_string(), - }), - Err(_) => Err("Couldn't parse token type"), - } - } -} /// Represents a token as returned by OAuth2 servers. /// @@ -362,7 +119,6 @@ pub struct ConsoleApplicationSecret { #[cfg(test)] pub mod tests { use super::*; - use hyper; pub const SECRET: &'static str = "{\"installed\":{\"auth_uri\":\"https://accounts.google.com/o/oauth2/auth\",\ @@ -380,25 +136,4 @@ pub mod tests { Err(err) => panic!(err), } } - - #[test] - fn schema() { - let s = Scheme { - token_type: TokenType::Bearer, - access_token: "foo".to_string(), - }; - let mut headers = hyper::HeaderMap::new(); - headers.insert(hyper::header::AUTHORIZATION, s.into()); - assert_eq!( - format!("{:?}", headers), - "{\"authorization\": \"Bearer foo\"}".to_string() - ); - } - - #[test] - fn parse_schema() { - let auth = Scheme::from_str("Bearer foo").unwrap(); - assert_eq!(auth.token_type, TokenType::Bearer); - assert_eq!(auth.access_token, "foo".to_string()); - } }