feat(doit): json decode and delegation

Now json errors are handled and delegated with the option to retry,
and all other values are just decoded according to plan.

For now, I brutally unwrap json values assuming this will work, because
it really should work.
This commit is contained in:
Sebastian Thiel
2015-03-16 17:03:01 +01:00
parent 2c79f6e3cf
commit eef1471357
5 changed files with 1189 additions and 730 deletions

View File

@@ -42,7 +42,7 @@ impl<T: Seek + Read> ReadSeek for T {}
/// A utility type which can decode a server response that indicates error
#[derive(RustcDecodable)]
struct JsonServerError {
pub struct JsonServerError {
error: String,
error_description: Option<String>
}
@@ -68,6 +68,22 @@ pub trait Delegate {
fn api_key(&mut self) -> Option<String> {
None
}
/// Called whenever the Authenticator didn't yield a token. The delegate
/// may attempt to provide one, or just take is a general information about the
/// pending impending failure
fn token(&mut self) -> Option<oauth2::Token> {
None
}
/// Called whenever the http request returns with a non-success status code.
/// This can involve authentication issues, or anything else that very much
/// depends on the used API method.
/// The delegate should check the status, header and decoded json error to decide
/// whether to retry or not. In the latter case, the underlying call will fail.
fn http_failure(&mut self, _: &hyper::client::Response, JsonServerError) -> oauth2::Retry {
oauth2::Retry::Abort
}
}
#[derive(Default)]
@@ -91,6 +107,9 @@ pub enum Result<T = ()> {
/// An additional, free form field clashed with one of the built-in optional ones
FieldClash(&'static str),
/// Indicates an HTTP repsonse with a non-success status code
Failure(hyper::client::Response),
/// It worked !
Success(T),
}

File diff suppressed because it is too large Load Diff

View File

@@ -42,6 +42,7 @@ use std::borrow::BorrowMut;
use std::default::Default;
use std::collections::BTreeMap;
use std::marker::PhantomData;
use rustc_serialize::json;
use std::io;
use std::fs;
use std::old_io::timer::sleep;

View File

@@ -334,11 +334,11 @@ match result {
where = ''
qualifier = 'pub '
add_args = ''
rtype = 'cmn::Result<()>'
rtype = 'cmn::Result<hyper::client::Response>'
response_schema = method_response(c, m)
if response_schema:
rtype = 'cmn::Result<%s>' % (response_schema.id)
rtype = 'cmn::Result<(hyper::client::Response, %s)>' % (response_schema.id)
if media_params:
stripped = lambda s: s.strip().strip(',')
@@ -365,26 +365,20 @@ match result {
client = '(*self.hub.client.borrow_mut()).borrow_mut()'
if supports_scopes(auth):
all_scopes = auth.oauth2.scopes.keys()
all_scopes = sorted(auth.oauth2.scopes.keys())
default_scope = all_scopes[0]
assert 'readonly' not in default_scope
if m.httpMethod in ('HEAD', 'GET', 'OPTIONS', 'TRACE'):
for scope in all_scopes:
if 'readonly' in scope:
default_scope = scope
break
# end for each scope
# end try to find read-only default scope
# end handle default scope
%>
/// Perform the operation you have build so far.
${action_fn} {
use hyper::method::Method;
use hyper::header::UserAgent;
% if request_value or response_schema:
use hyper::header::ContentType;
use mime::{Mime, TopLevel, SubLevel};
use rustc_serialize::json;
% endif
use std::io::Read;
let mut params: Vec<(&str, String)> = Vec::with_capacity(${len(params)} + ${paddfields}.len());
% for p in field_params:
<%
@@ -463,19 +457,22 @@ else {
loop {
% if supports_scopes(auth):
let token = ${auth_call}.token(self.${api.properties.scopes}.keys());
let mut token = ${auth_call}.token(self.${api.properties.scopes}.keys());
if token.is_none() && ${delegate}.is_some() {
token = ${delegate_call}.token();
}
if token.is_none() {
return cmn::Result::MissingToken
}
let auth_header = hyper::header::Authorization(token.unwrap().access_token);
% endif
match ${client}.request(Method::Extension("${m.httpMethod}".to_string()), url.as_slice())
.header(UserAgent("google-api-rust-client/${cargo.build_version}".to_string()))
match ${client}.request(hyper::method::Method::Extension("${m.httpMethod}".to_string()), url.as_slice())
.header(hyper::header::UserAgent("google-api-rust-client/${cargo.build_version}".to_string()))
% if supports_scopes(auth):
.header(auth_header)
% endif
% if request_value:
.header(ContentType(Mime(TopLevel::Application, SubLevel::Json, Default::default())))
.header(hyper::header::ContentType(mime::Mime(mime::TopLevel::Application, mime::SubLevel::Json, Default::default())))
.body(json::encode(&self.${property(REQUEST_VALUE_PROPERTY_NAME)}).unwrap().as_slice())
% endif
.send() {
@@ -493,19 +490,29 @@ else {
}
}
Ok(mut res) => {
break;
if !res.status.is_success() {
if ${delegate}.is_some() {
let mut json_err = String::new();
res.read_to_string(&mut json_err).ok();
let error_info: cmn::JsonServerError = json::decode(&json_err).unwrap();
if let oauth2::Retry::After(d) = ${delegate_call}.http_failure(&res, error_info) {
sleep(d);
continue;
}
}
return cmn::Result::Failure(res)
}
% if response_schema:
let mut json_response = String::new();
res.read_to_string(&mut json_response).ok();
let result_value = (res, json::decode(&json_response).unwrap());
% else:
let result_value = res;
% endif
return cmn::Result::Success(result_value)
}
}
}
% if response_schema:
let response: ${response_schema.id} = Default::default();
% else:
let response = ();
% endif
cmn::Result::Success(response)
}
% for p in media_params:

View File

@@ -40,7 +40,7 @@ impl<T: Seek + Read> ReadSeek for T {}
/// A utility type which can decode a server response that indicates error
#[derive(RustcDecodable)]
struct JsonServerError {
pub struct JsonServerError {
error: String,
error_description: Option<String>
}
@@ -66,6 +66,22 @@ pub trait Delegate {
fn api_key(&mut self) -> Option<String> {
None
}
/// Called whenever the Authenticator didn't yield a token. The delegate
/// may attempt to provide one, or just take is a general information about the
/// pending impending failure
fn token(&mut self) -> Option<oauth2::Token> {
None
}
/// Called whenever the http request returns with a non-success status code.
/// This can involve authentication issues, or anything else that very much
/// depends on the used API method.
/// The delegate should check the status, header and decoded json error to decide
/// whether to retry or not. In the latter case, the underlying call will fail.
fn http_failure(&mut self, _: &hyper::client::Response, JsonServerError) -> oauth2::Retry {
oauth2::Retry::Abort
}
}
#[derive(Default)]
@@ -89,6 +105,9 @@ pub enum Result<T = ()> {
/// An additional, free form field clashed with one of the built-in optional ones
FieldClash(&'static str),
/// Indicates an HTTP repsonse with a non-success status code
Failure(hyper::client::Response),
/// It worked !
Success(T),
}