Skip to content

Commit 47ddd90

Browse files
committed
Refactor quite a bit, add initial tests for logging in successfully
1 parent f2cc724 commit 47ddd90

File tree

6 files changed

+158
-31
lines changed

6 files changed

+158
-31
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,4 @@ nb-configuration.xml
4040
*.sublime-project
4141
*.sublime-workspace
4242
Cargo.lock
43+
.env

Diff for: Cargo.toml

+4-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ authors = ["Dabo Ross <[email protected]>"]
55

66
[dependencies]
77
hyper = "0.10"
8-
hyper-rustls = "0.3"
98
serde_derive = "0.9"
109
serde = "0.9"
1110
serde_json = "0.9"
11+
12+
[dev-dependencies]
13+
hyper-rustls = "0.3"
14+
dotenv = "*"

Diff for: README.md

+2
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ The API provided for screeps.com is unofficial, and not officially documented, b
88
Documentation for the API calls that rust-screeps-api makes can be found at https://github.com/screepers/python-screeps/blob/master/docs/Endpoints.md.
99

1010
When this library is relatively stable, I will post the rust docs online and provide some example usages. For now, the library does not do enough to warrant this.
11+
12+
To run tests, provide SCREEPS_API_USERNAME and SCREEPS_API_PASSWORD in .env or as environmental variables. These variables are only ever used when running tests, and are never included in the compiled library.

Diff for: src/error.rs

+70-12
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,114 @@
11
extern crate hyper;
22
extern crate serde_json;
33

4+
use std::error::Error as StdError;
45
use std::fmt;
56
use std::io;
6-
use std::error::Error as StdError;
7-
use self::Error::*;
7+
use self::ErrorType::*;
88

99
#[derive(Debug)]
10-
pub enum Error {
10+
pub enum ErrorType {
11+
Unauthorized,
1112
SerdeJson(serde_json::error::Error),
1213
Hyper(hyper::error::Error),
1314
Io(io::Error),
1415
StatusCode(hyper::status::StatusCode),
1516
Api(ApiError),
1617
}
1718

18-
pub type Result<T> = ::std::result::Result<T, Error>;
19+
#[derive(Debug)]
20+
pub struct Error {
21+
pub err: ErrorType,
22+
pub url: Option<hyper::Url>,
23+
}
24+
25+
impl Error {
26+
pub fn new<T: Into<Error>>(err: T, url: Option<hyper::Url>) -> Error {
27+
Error {
28+
err: err.into().err,
29+
url: url,
30+
}
31+
}
32+
}
1933

34+
pub type Result<T> = ::std::result::Result<T, Error>;
2035

2136
impl From<serde_json::error::Error> for Error {
22-
fn from(err: serde_json::error::Error) -> Error { Error::SerdeJson(err) }
37+
fn from(err: serde_json::error::Error) -> Error {
38+
Error {
39+
err: ErrorType::SerdeJson(err),
40+
url: None,
41+
}
42+
}
2343
}
2444

2545
impl From<hyper::error::Error> for Error {
26-
fn from(err: hyper::error::Error) -> Error { Error::Hyper(err) }
46+
fn from(err: hyper::error::Error) -> Error {
47+
Error {
48+
err: ErrorType::Hyper(err),
49+
url: None,
50+
}
51+
}
52+
}
53+
54+
impl From<hyper::error::ParseError> for Error {
55+
fn from(err: hyper::error::ParseError) -> Error {
56+
Error {
57+
err: ErrorType::Hyper(hyper::Error::Uri(err)),
58+
url: None,
59+
}
60+
}
2761
}
2862

2963
impl From<io::Error> for Error {
30-
fn from(err: io::Error) -> Error { Error::Io(err) }
64+
fn from(err: io::Error) -> Error {
65+
Error {
66+
err: ErrorType::Io(err),
67+
url: None,
68+
}
69+
}
3170
}
3271

3372
impl From<hyper::status::StatusCode> for Error {
34-
fn from(code: hyper::status::StatusCode) -> Error { Error::StatusCode(code) }
73+
fn from(code: hyper::status::StatusCode) -> Error {
74+
Error {
75+
err: {
76+
if code == hyper::status::StatusCode::Unauthorized {
77+
ErrorType::Unauthorized
78+
} else {
79+
ErrorType::StatusCode(code)
80+
}
81+
},
82+
url: None,
83+
}
84+
}
3585
}
3686

3787
impl From<ApiError> for Error {
38-
fn from(err: ApiError) -> Error { Error::Api(err) }
88+
fn from(err: ApiError) -> Error {
89+
Error {
90+
err: ErrorType::Api(err),
91+
url: None,
92+
}
93+
}
3994
}
4095

4196
impl fmt::Display for Error {
4297
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43-
match *self {
98+
match self.err {
4499
SerdeJson(ref err) => fmt::Display::fmt(err, f),
45100
Hyper(ref err) => fmt::Display::fmt(err, f),
46101
Io(ref err) => fmt::Display::fmt(err, f),
47102
StatusCode(ref status) => fmt::Display::fmt(status, f),
48103
Api(ref err) => fmt::Display::fmt(err, f),
104+
Unauthorized => write!(f, "access not authorized: token expired, username/password incorrect or no login provided"),
49105
}
50106
}
51107
}
52108

53109
impl StdError for Error {
54110
fn description(&self) -> &str {
55-
match *self {
111+
match self.err {
56112
SerdeJson(ref err) => err.description(),
57113
Hyper(ref err) => err.description(),
58114
Io(ref err) => err.description(),
@@ -81,16 +137,18 @@ impl StdError for Error {
81137
return "status code error";
82138
},
83139
Api(ref err) => err.description(),
140+
Unauthorized => "access not authorized: token expired, username/password incorrect or no login provided",
84141
}
85142
}
86143

87144
fn cause(&self) -> Option<&StdError> {
88-
match *self {
145+
match self.err {
89146
SerdeJson(ref err) => Some(err),
90147
Hyper(ref err) => Some(err),
91148
Io(ref err) => Some(err),
92149
StatusCode(_) => None,
93150
Api(ref err) => Some(err),
151+
Unauthorized => None,
94152
}
95153
}
96154
}

Diff for: src/lib.rs

+44-18
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pub struct LoginDetails<'a> {
2222

2323
impl<'a> LoginDetails<'a> {
2424
/// Create a new login details with the given username and password
25-
pub fn new<'b, T: Into<Cow<'b, str>>>(email: T, password: T) -> LoginDetails<'b> {
25+
pub fn new<'b, T1: Into<Cow<'b, str>>, T2: Into<Cow<'b, str>>>(email: T1, password: T2) -> LoginDetails<'b> {
2626
LoginDetails {
2727
email: email.into(),
2828
password: password.into()
@@ -36,40 +36,63 @@ struct LoginResponse {
3636
token: Option<String>,
3737
}
3838

39+
/// API Object, stores the current API token and allows access to making requests.
3940
#[derive(Debug)]
4041
pub struct API<'a> {
41-
token: Option<Cow<'a, str>>,
42+
pub url: hyper::Url,
43+
client: &'a hyper::Client,
44+
pub token: Option<String>,
4245
}
4346

4447
impl<'a> API<'a> {
45-
pub fn new_anonymous() -> API<'static> {
46-
API { token: None }
48+
pub fn new<'b>(client: &'b hyper::Client) -> API<'b> {
49+
API {
50+
//"https://httpbin.org/post"
51+
url: hyper::Url::parse("https://screeps.com/api").expect("expected pre-set url to parse, parsing failed"),
52+
client: client,
53+
token: None,
54+
}
4755
}
4856

49-
pub fn new_logged_in<'b>(client: &hyper::Client, login_details: &LoginDetails) -> Result<API<'b>> {
50-
let mut api = API::new_anonymous();
51-
52-
api.get_token(client, login_details)?;
53-
54-
Ok(api)
57+
pub fn with_url<'b, T: hyper::client::IntoUrl>(client: &'b hyper::Client, url: T) -> Result<API<'b>> {
58+
Ok(API {
59+
url: url.into_url()?,
60+
client: client,
61+
token: None,
62+
})
5563
}
5664

57-
fn get_token(&mut self, client: &hyper::Client, login_details: &LoginDetails) -> Result<()> {
58-
let body = serde_json::to_string(&login_details)?;
65+
fn make_request<T: serde::Serialize, R: serde::Deserialize>(&mut self, endpoint: &str, request_text: &T) -> Result<R> {
66+
let body = serde_json::to_string(request_text)?;
5967

6068
let mut headers = Headers::new();
6169
headers.set(ContentType::json());
70+
if let Some(ref token) = self.token {
71+
headers.set_raw("X-Token", vec![token.as_bytes().to_vec()]);
72+
headers.set_raw("X-Username", vec![token.as_bytes().to_vec()]);
73+
}
6274

63-
let mut response = client.post("https://screeps.com/api/auth/signin")
75+
let mut response = self.client.post(self.url.join(endpoint)?)
6476
.body(&body)
6577
.headers(headers)
6678
.send()?;
6779

6880
if !response.status.is_success() {
69-
return Err(response.status.into());
81+
return Err(Error::new(response.status, Some(response.url.clone())));
7082
}
7183

72-
let result: LoginResponse = serde_json::from_reader(&mut response)?;
84+
let result: R = match serde_json::from_reader(&mut response) {
85+
Ok(v) => v,
86+
Err(e) => {
87+
return Err(Error::new(e, Some(response.url.clone())))
88+
}
89+
};
90+
91+
Ok(result)
92+
}
93+
94+
pub fn login(&mut self, login_details: &LoginDetails) -> Result<()> {
95+
let result: LoginResponse = self.make_request("/api/auth/signin", login_details)?;
7396

7497
if result.ok != 1 {
7598
return Err(ApiError::NotOk(result.ok).into());
@@ -87,24 +110,27 @@ impl<'a> API<'a> {
87110
#[cfg(test)]
88111
mod tests {
89112
use ::{API, LoginDetails};
113+
use ::error::{Error, ErrorType};
90114
extern crate hyper;
91115
extern crate hyper_rustls;
92116
use hyper::client::Client;
93117
use hyper::net::HttpsConnector;
94118

95119
#[test]
96120
fn anonymous_creation() {
97-
let _unused = API::new_anonymous();
121+
let client = Client::with_connector(HttpsConnector::new(hyper_rustls::TlsClient::new()));
122+
let _unused = API::new(&client);
98123
}
99124

100125
#[test]
101126
fn login_creation_auth_failure() {
102127
let client = Client::with_connector(HttpsConnector::new(hyper_rustls::TlsClient::new()));
103128
let login = LoginDetails::new("username", "password");
104-
let result = API::new_logged_in(&client, &login);
129+
let mut api = API::new(&client);
130+
let result = api.login(&login);
105131

106132
match result {
107-
Err(::error::Error::StatusCode(hyper::status::StatusCode::Unauthorized)) => println!("Success!"),
133+
Err(Error { err: ErrorType::Unauthorized, .. }) => println!("Success!"),
108134
other => panic!("Expected unauthorized error, found {:?}", other),
109135
}
110136
}

Diff for: tests/authenticated_tests.rs

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
extern crate screeps_api;
2+
extern crate hyper;
3+
extern crate hyper_rustls;
4+
extern crate dotenv;
5+
6+
use hyper::client::Client;
7+
use hyper::net::HttpsConnector;
8+
9+
fn env(var: &str) -> String {
10+
dotenv::dotenv().ok();
11+
match ::std::env::var(var) {
12+
Ok(value) => value,
13+
Err(_) => panic!("must have `{}` defined", var),
14+
}
15+
}
16+
17+
fn create_secure_client() -> hyper::Client {
18+
Client::with_connector(HttpsConnector::new(hyper_rustls::TlsClient::new()))
19+
}
20+
21+
fn logged_in<'a>(client: &'a hyper::Client) -> screeps_api::API<'a> {
22+
let username = env("SCREEPS_API_USERNAME");
23+
let password = env("SCREEPS_API_PASSWORD");
24+
let mut api = screeps_api::API::new(client);
25+
26+
if let Err(err) = api.login(&screeps_api::LoginDetails::new(username, password)) {
27+
panic!("Error logging in: {:?}", err);
28+
}
29+
30+
api
31+
}
32+
33+
#[test]
34+
fn test_logging_in() {
35+
let client = create_secure_client();
36+
let _api = logged_in(&client);
37+
}

0 commit comments

Comments
 (0)