Minimize the number of items on the rustdoc landing page.

Restructure the modules and imports to increase the signal to noise
ration on the cargo doc landing page. This includes exposing some
modules as public so that they can contain things that need to be public
but that users will rarely need to interact with. Most items from
types.rs were moved into an error.rs module that is now exposed
publicly.
This commit is contained in:
Glenn Griffin
2019-11-13 13:23:37 -08:00
parent 3aadc6b0ef
commit 0fe66619dd
9 changed files with 264 additions and 401 deletions

View File

@@ -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<C> AuthenticatorBuilder<C, InstalledFlow> {
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),

View File

@@ -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

View File

@@ -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::<JsonErrorOr<JsonData>>(&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::<JsonErrorOr<_>>(&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.

174
src/error.rs Normal file
View File

@@ -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<String>,
pub error_uri: Option<String>,
}
/// A helper type to deserialize either a JsonError or another piece of data.
#[derive(Deserialize, Debug)]
#[serde(untagged)]
pub(crate) enum JsonErrorOr<T> {
Err(JsonError),
Data(T),
}
impl<T> JsonErrorOr<T> {
pub(crate) fn into_result(self) -> Result<T, JsonError> {
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<Utc>),
/// 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<String>),
/// 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<dyn Error + Send + Sync>),
}
impl From<hyper::Error> for RequestError {
fn from(error: hyper::Error) -> RequestError {
RequestError::ClientError(error)
}
}
impl From<JsonError> 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<serde_json::Error> for RequestError {
fn from(value: serde_json::Error) -> RequestError {
RequestError::JSONError(value)
}
}
impl From<RefreshError> 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<String>),
}
impl From<hyper::Error> for RefreshError {
fn from(value: hyper::Error) -> Self {
RefreshError::ConnectionError(value)
}
}
impl From<JsonError> for RefreshError {
fn from(value: JsonError) -> Self {
RefreshError::ServerError(value.error, value.error_description)
}
}
impl From<serde_json::Error> for RefreshError {
fn from(_value: serde_json::Error) -> Self {
RefreshError::ServerError(
"failed to deserialize json token from refresh response".to_owned(),
None,
)
}
}

View File

@@ -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<i64>,
}
match serde_json::from_slice::<JsonErrorOr<JSONTokenResponse>>(&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::<JsonErrorOr<_>>(&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<dyn Error + Send + Sync>);
return Err("no redirect_uri!".into());
}
let mut rduri = rduri.unwrap();
rduri.push_str(&format!("?code={}", self.0));

View File

@@ -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};

View File

@@ -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<C>,
client_secret: &ApplicationSecret,
refresh_token: &str,
) -> Result<RefreshResult, RequestError> {
) -> Result<Token, RefreshError> {
// 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::<JsonErrorOr<JsonToken>>(&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::<JsonErrorOr<_>>(&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)),

View File

@@ -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<i64>,
}
match serde_json::from_slice::<JsonErrorOr<TokenResponse>>(&body)? {
JsonErrorOr::Err(err) => Err(err.into()),
JsonErrorOr::Data(TokenResponse {
match serde_json::from_slice::<JsonErrorOr<_>>(&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
))),

View File

@@ -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<String>,
pub error_uri: Option<String>,
}
/// A helper type to deserialize either a JsonError or another piece of data.
#[derive(Deserialize, Debug)]
#[serde(untagged)]
pub enum JsonErrorOr<T> {
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<String>),
/// 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<Utc>),
/// 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<String>),
/// 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<dyn Error + Send + Sync>),
}
impl From<hyper::Error> for RequestError {
fn from(error: hyper::Error) -> RequestError {
RequestError::ClientError(error)
}
}
impl From<JsonError> 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<serde_json::Error> 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<S: AsRef<str>>(error: S, desc: Option<S>) -> 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<String> 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<str> 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<TokenType, ()> {
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<hyper::header::HeaderValue> 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<Scheme, &'static str> {
let parts: Vec<&str> = s.split(' ').collect();
if parts.len() != 2 {
return Err("Expected two parts: <token_type> <token>");
}
match <TokenType as FromStr>::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());
}
}