#![doc(html_root_url = "https://docs.rs/screeps-api/0.6.0")]
#![deny(missing_docs)]
#![recursion_limit = "512"]
#![cfg_attr(feature = "protocol-docs", feature(external_doc))]
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde_derive;
#[cfg_attr(test, macro_use)]
extern crate serde_json;
mod connecting;
mod data;
mod decoders;
#[cfg(feature = "protocol-docs")]
pub mod docs;
mod endpoints;
pub mod error;
#[cfg(feature = "sync")]
pub mod sync;
pub mod websocket;
#[cfg(feature = "sync")]
pub use crate::sync::SyncApi;
pub use crate::{
data::*,
endpoints::*,
error::{Error, ErrorKind, NoToken},
};
use std::{
borrow::Cow,
convert::AsRef,
marker::PhantomData,
sync::{Arc, PoisonError, RwLock},
};
use bytes::Bytes;
use futures::Future;
use hyper::header::{HeaderValue, CONTENT_TYPE};
use url::Url;
use crate::connecting::FutureResponse;
pub(crate) trait EndpointResult: Sized + 'static {
type RequestResult: for<'de> serde::Deserialize<'de>;
type ErrorResult: for<'de> serde::Deserialize<'de> + Into<Error>;
fn from_raw(data: Self::RequestResult) -> Result<Self, Error>;
}
pub type Token = Bytes;
#[derive(Clone, Debug, Default)]
pub struct TokenStorage(Arc<RwLock<Option<Token>>>);
impl TokenStorage {
pub fn set(&self, token: Bytes) {
*self.0.write().unwrap_or_else(PoisonError::into_inner) = Some(token);
}
pub fn get(&self) -> Option<Bytes> {
self.0
.read()
.unwrap_or_else(PoisonError::into_inner)
.clone()
}
}
#[derive(Debug)]
pub struct Api<C> {
pub url: Url,
auth_token: TokenStorage,
client: hyper::Client<C>,
}
impl<C> Clone for Api<C> {
fn clone(&self) -> Self {
Api {
url: self.url.clone(),
auth_token: self.auth_token.clone(),
client: self.client.clone(),
}
}
}
pub static DEFAULT_OFFICIAL_API_URL: &'static str = "https://screeps.com/api/";
fn default_url() -> Url {
Url::parse(DEFAULT_OFFICIAL_API_URL).expect("expected pre-set url to parse, parsing failed")
}
impl<C> Api<C> {
#[inline]
pub fn new(client: hyper::Client<C>) -> Self {
Api {
url: default_url(),
client,
auth_token: TokenStorage::default(),
}
}
#[inline]
pub fn set_url<U: AsRef<str>>(&mut self, url: U) -> Result<(), url::ParseError> {
self.url = Url::parse(url.as_ref())?;
Ok(())
}
#[inline]
pub fn with_url<U: AsRef<str>>(mut self, url: U) -> Result<Self, url::ParseError> {
self.set_url(url)?;
Ok(self)
}
#[inline]
pub fn set_token<T: Into<Token>>(&mut self, token: T) {
self.auth_token.set(token.into());
}
#[inline]
pub fn with_token<T: Into<Token>>(mut self, token: T) -> Self {
self.set_token(token);
self
}
#[inline]
pub fn token_storage(&self) -> &TokenStorage {
&self.auth_token
}
}
impl<C: hyper::client::connect::Connect + 'static> Api<C> {
#[inline]
fn request<'a, R, S>(&'a self, endpoint: &'a str) -> PartialRequest<'a, C, R, NoAuthRequired, S>
where
R: EndpointResult,
S: serde::Serialize,
{
PartialRequest {
client: self,
endpoint,
post_body: None,
query_params: None,
_phantom: PhantomData,
}
}
#[inline]
fn get<'a, R>(
&'a self,
endpoint: &'a str,
) -> PartialRequest<'a, C, R, NoAuthRequired, &'static str>
where
R: EndpointResult,
{
self.request(endpoint)
}
#[inline]
fn post<'a, U, R>(
&'a self,
endpoint: &'a str,
request_text: U,
) -> PartialRequest<'a, C, R, NoAuthRequired, U>
where
U: serde::Serialize,
R: EndpointResult,
{
self.request(endpoint).post(request_text)
}
pub fn login<'b, U, V>(
&self,
username: U,
password: V,
) -> impl Future<Item = LoggedIn, Error = Error>
where
U: Into<Cow<'b, str>>,
V: Into<Cow<'b, str>>,
{
self.post("auth/signin", LoginArgs::new(username, password))
.send()
}
pub fn register(
&self,
details: RegistrationArgs,
) -> impl Future<Item = RegistrationSuccess, Error = Error> {
self.post("register/submit", details).send()
}
pub fn my_info(&self) -> Result<impl Future<Item = MyInfo, Error = Error>, NoToken> {
self.get("auth/me").auth().send()
}
pub fn world_start_room(
&self,
) -> Result<impl Future<Item = WorldStartRoom, Error = Error>, NoToken> {
self.get("user/world-start-room").auth().send()
}
pub fn shard_start_room<'b, U>(
&self,
shard: U,
) -> Result<impl Future<Item = WorldStartRoom, Error = Error>, NoToken>
where
U: Into<Cow<'b, str>>,
{
self.get("user/world-start-room")
.params(&[("shard", shard.into().into_owned())])
.auth()
.send()
}
pub fn map_stats<'a, U, V>(
&self,
shard: &'a str,
rooms: &'a V,
) -> Result<impl Future<Item = MapStats, Error = Error>, NoToken>
where
U: AsRef<str>,
&'a V: IntoIterator<Item = U>,
{
let args = MapStatsArgs::new(shard, rooms, MapStatName::RoomOwner);
self.post("game/map-stats", args).auth().send()
}
pub fn room_overview<'b, U, V>(
&self,
shard: U,
room_name: V,
request_interval: u32,
) -> Result<impl Future<Item = RoomOverview, Error = Error>, NoToken>
where
U: Into<Cow<'b, str>>,
V: Into<Cow<'b, str>>,
{
self.get("game/room-overview")
.params(&[
("shard", shard.into().into_owned()),
("room", room_name.into().into_owned()),
("interval", request_interval.to_string()),
])
.auth()
.send()
}
pub fn room_terrain<'b, U, V>(
&self,
shard: Option<U>,
room_name: V,
) -> impl Future<Item = RoomTerrain, Error = Error>
where
U: Into<Cow<'b, str>>,
V: Into<Cow<'b, str>>,
{
match shard {
Some(shard) => self
.get("game/room-terrain")
.params(&[
("shard", shard.into().into_owned()),
("room", room_name.into().into_owned()),
("encoded", true.to_string()),
])
.send(),
None => self
.get("game/room-terrain")
.params(&[
("room", room_name.into().into_owned()),
("encoded", true.to_string()),
])
.send(),
}
}
pub fn shard_list(&self) -> impl Future<Item = Vec<ShardInfo>, Error = Error> {
self.get("game/shards/info").send()
}
pub fn room_status<'b, U>(
&self,
room_name: U,
) -> Result<impl Future<Item = RoomStatus, Error = Error>, NoToken>
where
U: Into<Cow<'b, str>>,
{
self.get("game/room-status")
.params(&[("room", room_name.into().into_owned())])
.auth()
.send()
}
pub fn recent_pvp(
&self,
details: RecentPvpArgs,
) -> impl Future<Item = RecentPvp, Error = Error> {
let args = match details {
RecentPvpArgs::WithinLast { ticks } => [("interval", ticks.to_string())],
RecentPvpArgs::Since { time } => [("start", time.to_string())],
};
self.get("experimental/pvp").params(&args).send()
}
pub fn leaderboard_season_list(
&self,
) -> Result<impl Future<Item = Vec<LeaderboardSeason>, Error = Error>, NoToken> {
self.get("leaderboard/seasons").auth().send()
}
pub fn find_season_leaderboard_rank<'b, U, V>(
&self,
leaderboard_type: LeaderboardType,
username: U,
season: V,
) -> Result<impl Future<Item = FoundUserRank, Error = Error>, NoToken>
where
U: Into<Cow<'b, str>>,
V: Into<Cow<'b, str>>,
{
self.get("leaderboard/find")
.auth()
.params(&[
("mode", leaderboard_type.api_representation().to_string()),
("season", season.into().into_owned()),
("username", username.into().into_owned()),
])
.send()
}
pub fn find_leaderboard_ranks<'b, U>(
&self,
leaderboard_type: LeaderboardType,
username: U,
) -> Result<impl Future<Item = Vec<FoundUserRank>, Error = Error>, NoToken>
where
U: Into<Cow<'b, str>>,
{
self.get("leaderboard/find")
.auth()
.params(&[
("mode", leaderboard_type.api_representation().to_string()),
("username", username.into().into_owned()),
])
.send()
}
pub fn leaderboard_page<'b, U>(
&self,
leaderboard_type: LeaderboardType,
season: U,
limit: u32,
offset: u32,
) -> Result<impl Future<Item = LeaderboardPage, Error = Error>, NoToken>
where
U: Into<Cow<'b, str>>,
{
self.get("leaderboard/list")
.auth()
.params(&[
("mode", leaderboard_type.api_representation().to_string()),
("season", season.into().into_owned()),
("limit", limit.to_string()),
("offset", offset.to_string()),
])
.send()
}
pub fn memory_segment<'b, U>(
&self,
shard: Option<U>,
segment: u32,
) -> Result<impl Future<Item = String, Error = Error>, NoToken>
where
U: Into<Cow<'b, str>>,
{
match shard {
Some(shard) => self
.get::<MemorySegment>("user/memory-segment")
.params(&[
("segment", segment.to_string()),
("shard", shard.into().into_owned()),
])
.auth()
.send(),
None => self
.get::<MemorySegment>("user/memory-segment")
.params(&[("segment", segment.to_string())])
.auth()
.send(),
}
.map(|fut| fut.map(|res| res.data))
}
pub fn set_memory_segment<'b, U, V>(
&self,
shard: Option<U>,
segment: u32,
data: V,
) -> Result<impl Future<Item = (), Error = Error>, NoToken>
where
U: Into<Cow<'b, str>>,
V: Into<Cow<'b, str>>,
{
let args = SetMemorySegmentArgs {
segment,
shard: shard.map(Into::into),
data: data.into(),
};
self.post("user/memory-segment", args)
.auth()
.send()
.map(|fut| fut.map(|_: SetMemorySegment| ()))
}
}
trait PartialRequestAuth<T> {
type Result;
fn token_or_result(token: Option<Token>) -> Result<Option<Token>, Self::Result>;
fn successful_result(success: T) -> Self::Result;
}
struct NoAuthRequired;
impl<T> PartialRequestAuth<T> for NoAuthRequired {
type Result = T;
fn token_or_result(_token: Option<Token>) -> Result<Option<Token>, T> {
Ok(None)
}
fn successful_result(success: T) -> T {
success
}
}
struct AuthRequired;
impl<T> PartialRequestAuth<T> for AuthRequired {
type Result = Result<T, NoToken>;
fn token_or_result(token: Option<Token>) -> Result<Option<Token>, Result<T, NoToken>> {
match token {
Some(v) => Ok(Some(v)),
None => Err(Err(NoToken)),
}
}
fn successful_result(success: T) -> Result<T, NoToken> {
Ok(success)
}
}
struct PartialRequest<'a, C, R, A = NoAuthRequired, S = &'static str>
where
C: hyper::client::connect::Connect,
R: EndpointResult,
A: PartialRequestAuth<FutureResponse<R>>,
S: serde::Serialize + 'a,
{
client: &'a Api<C>,
endpoint: &'a str,
query_params: Option<&'a [(&'static str, String)]>,
post_body: Option<S>,
_phantom: PhantomData<(R, A)>,
}
impl<'a, C, R, S> PartialRequest<'a, C, R, NoAuthRequired, S>
where
C: hyper::client::connect::Connect + 'static,
R: EndpointResult,
S: serde::Serialize,
{
#[inline]
fn auth(self) -> PartialRequest<'a, C, R, AuthRequired, S> {
PartialRequest {
client: self.client,
endpoint: self.endpoint,
query_params: self.query_params,
post_body: self.post_body,
_phantom: PhantomData,
}
}
}
impl<'a, C, R, S> PartialRequest<'a, C, R, AuthRequired, S>
where
C: hyper::client::connect::Connect + 'static,
R: EndpointResult,
S: serde::Serialize,
{
#[allow(dead_code)]
#[inline]
fn no_auth(self) -> PartialRequest<'a, C, R, NoAuthRequired, S> {
PartialRequest {
client: self.client,
endpoint: self.endpoint,
query_params: self.query_params,
post_body: self.post_body,
_phantom: PhantomData,
}
}
}
impl<'a, C, R, A, S> PartialRequest<'a, C, R, A, S>
where
C: hyper::client::connect::Connect + 'static,
R: EndpointResult,
A: PartialRequestAuth<FutureResponse<R>>,
S: serde::Serialize,
{
#[inline]
fn params(mut self, params: &'a [(&'static str, String)]) -> Self {
self.query_params = Some(params);
self
}
#[inline]
fn post(mut self, body: S) -> Self {
self.post_body = Some(body);
self
}
fn send(self) -> A::Result {
let PartialRequest {
client,
endpoint,
query_params,
post_body,
_phantom: _,
} = self;
let auth_token = match A::token_or_result(client.auth_token.get()) {
Ok(token_option) => token_option,
Err(return_value) => return return_value,
};
let method = match post_body {
Some(_) => hyper::Method::POST,
None => hyper::Method::GET,
};
let url = {
let mut temp = client
.url
.join(endpoint)
.expect("expected pre-set endpoint url text to succeed, but it failed.");
if let Some(pairs) = query_params {
temp.query_pairs_mut().extend_pairs(pairs).finish();
}
temp
};
let mut request = hyper::Request::builder();
request.method(method).uri(url.as_str());
request.header(CONTENT_TYPE, HeaderValue::from_static("application/json"));
if let Some(token) = auth_token {
request.header("X-Token", token.clone());
}
let request = if let Some(ref serializable) = post_body {
request.body(hyper::Body::from(
serde_json::to_string(serializable).expect(
"expected serde_json::to_string to unfailingly succeed, but it failed.",
),
))
} else {
request.body(hyper::Body::empty())
};
let request = request.expect("building http request should never fail");
let hyper_future = client.client.request(request);
let finished = connecting::interpret(client.auth_token.clone(), url, hyper_future);
A::successful_result(finished)
}
}
#[inline]
pub fn gcl_calc(gcl_points: u64) -> u64 {
const GCL_INV_MULTIPLY: f64 = 1.0 / 1_000_000f64;
const GCL_INV_POW: f64 = 1.0 / 2.4f64;
((gcl_points as f64) * GCL_INV_MULTIPLY)
.powf(GCL_INV_POW)
.floor() as u64
+ 1
}
#[cfg(test)]
mod tests {
use super::gcl_calc;
#[test]
fn parse_gcl_1() {
assert_eq!(gcl_calc(0), 1);
assert_eq!(gcl_calc(900_000), 1);
}
#[test]
fn parse_gcl_2() {
assert_eq!(gcl_calc(1_000_000), 2);
assert_eq!(gcl_calc(5_278_000), 2);
}
#[test]
fn parse_gcl_3() {
assert_eq!(gcl_calc(5_278_032), 3);
}
#[test]
fn parse_gcl_late_15() {
assert_eq!(gcl_calc(657_254_041), 15);
}
}