Skip to content

Commit

Permalink
Move serialization and requests from Rail Client to Trait
Browse files Browse the repository at this point in the history
  • Loading branch information
emma-k-alexandra committed Sep 29, 2019
1 parent 23b4e13 commit 8e92fc0
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 61 deletions.
8 changes: 5 additions & 3 deletions src/bus/urls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub enum URLs {
PathDetails,
RouteSchedule,
NextBuses,
StopSchedule
StopSchedule,
}

impl ToString for URLs {
Expand All @@ -18,8 +18,10 @@ impl ToString for URLs {
URLs::Positions => "https://api.wmata.com/Bus.svc/json/jBusPositions".to_string(),
URLs::PathDetails => "https://api.wmata.com/Bus.svc/json/jRouteDetails".to_string(),
URLs::RouteSchedule => "https://api.wmata.com/Bus.svc/json/jRouteSchedule".to_string(),
URLs::NextBuses => "https://api.wmata.com/NextBusService.svc/json/jPredictions".to_string(),
URLs::NextBuses => {
"https://api.wmata.com/NextBusService.svc/json/jPredictions".to_string()
}
URLs::StopSchedule => "https://api.wmata.com/Bus.svc/json/jStopSchedul".to_string(),
}
}
}
}
7 changes: 7 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,10 @@ impl error::Error for Error {
None
}
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct ErrorResponse<'a> {
pub message: &'a str,
}

1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod bus;
pub mod rail;

pub mod traits;
pub mod error;

pub use rail::client::Client as RailClient;
82 changes: 24 additions & 58 deletions src/rail/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ use crate::error::Error;
use crate::rail::line::LineCode;
use crate::rail::station::StationCode;
use crate::rail::urls::URLs;
use crate::traits::{ApiKey, Fetch};
use std::str::FromStr;

use reqwest;
use serde::{de::DeserializeOwned, Serialize};
use serde_json;

pub struct Client {
pub api_key: String,
}

impl ApiKey for Client {
fn api_key(&self) -> &str {
&self.api_key
}
}

// Constructor
impl Client {
fn new(api_key: &str) -> Self {
Expand All @@ -27,7 +30,7 @@ impl Client {
// No Station or Line Codes
impl Client {
pub fn lines(&self) -> Result<responses::Lines, Error> {
self.request_and_deserialize::<responses::Lines, [(); 0]>(&URLs::Lines.to_string(), None)
self.fetch::<responses::Lines, [(); 0]>(&URLs::Lines.to_string(), None)
}

pub fn entrances(
Expand All @@ -36,25 +39,25 @@ impl Client {
longitude: f64,
radius: f64,
) -> Result<responses::StationEntrances, Error> {
self.request_and_deserialize(
self.fetch(
&URLs::Entrances.to_string(),
Some(&[("Lat", latitude), ("Lon", longitude), ("Radius", radius)]),
)
}

pub fn positions(&self) -> Result<responses::TrainPositions, Error> {
self.request_and_deserialize(
self.fetch(
&URLs::Positions.to_string(),
Some(&[("contentType", "json")]),
)
}

pub fn routes(&self) -> Result<responses::StandardRoutes, Error> {
self.request_and_deserialize(&URLs::Routes.to_string(), Some(&[("contentType", "json")]))
self.fetch(&URLs::Routes.to_string(), Some(&[("contentType", "json")]))
}

pub fn circuits(&self) -> Result<responses::TrackCircuits, Error> {
self.request_and_deserialize(
self.fetch(
&URLs::Circuits.to_string(),
Some(&[("contentType", "json")]),
)
Expand All @@ -79,9 +82,9 @@ impl Client {
}

if !query.is_empty() {
self.request_and_deserialize(&URLs::StationToStation.to_string(), Some(&query))
self.fetch(&URLs::StationToStation.to_string(), Some(&query))
} else {
self.request_and_deserialize::<responses::StationToStationInfos, [(); 0]>(
self.fetch::<responses::StationToStationInfos, [(); 0]>(
&URLs::StationToStation.to_string(),
None,
)
Expand All @@ -99,12 +102,12 @@ impl Client {
}

if !query.is_empty() {
self.request_and_deserialize(
self.fetch(
&URLs::ElevatorAndEscalatorIncidents.to_string(),
Some(&query),
)
} else {
self.request_and_deserialize::<responses::ElevatorAndEscalatorIncidents, [(); 0]>(
self.fetch::<responses::ElevatorAndEscalatorIncidents, [(); 0]>(
&URLs::ElevatorAndEscalatorIncidents.to_string(),
None,
)
Expand All @@ -121,14 +124,14 @@ impl Client {
query.push(("StationCode", station_code.to_string()));
}

self.request_and_deserialize(&URLs::Incidents.to_string(), Some(&query))
self.fetch(&URLs::Incidents.to_string(), Some(&query))
}

pub fn next_trains(
&self,
station_code: StationCode,
) -> Result<responses::RailPredictions, Error> {
self.request_and_deserialize::<responses::RailPredictions, [(); 0]>(
self.fetch::<responses::RailPredictions, [(); 0]>(
&[URLs::NextTrains.to_string(), station_code.to_string()].join("/"),
None,
)
Expand All @@ -138,7 +141,7 @@ impl Client {
&self,
station_code: StationCode,
) -> Result<responses::StationInformation, Error> {
self.request_and_deserialize(
self.fetch(
&URLs::Information.to_string(),
Some(&[("StationCode", station_code.to_string())]),
)
Expand All @@ -148,7 +151,7 @@ impl Client {
&self,
station_code: StationCode,
) -> Result<responses::StationsParking, Error> {
self.request_and_deserialize(
self.fetch(
&URLs::ParkingInformation.to_string(),
Some(&[("StationCode", station_code.to_string())]),
)
Expand All @@ -159,7 +162,7 @@ impl Client {
from_station: StationCode,
to_station: StationCode,
) -> Result<responses::PathBetweenStations, Error> {
self.request_and_deserialize(
self.fetch(
&URLs::Path.to_string(),
Some(&[
("FromStationCode", from_station.to_string()),
Expand All @@ -169,7 +172,7 @@ impl Client {
}

pub fn timings(&self, station_code: StationCode) -> Result<responses::StationTimings, Error> {
self.request_and_deserialize(
self.fetch(
&URLs::Timings.to_string(),
Some(&[("StationCode", station_code.to_string())]),
)
Expand All @@ -186,50 +189,13 @@ impl Client {
}

if !query.is_empty() {
self.request_and_deserialize(&URLs::Stations.to_string(), Some(&query))
self.fetch(&URLs::Stations.to_string(), Some(&query))
} else {
self.request_and_deserialize::<responses::Stations, [(); 0]>(
&URLs::Stations.to_string(),
None,
)
self.fetch::<responses::Stations, [(); 0]>(&URLs::Stations.to_string(), None)
}
}
}

// Internal helper methods
impl Client {
fn request_and_deserialize<T, U>(&self, path: &str, query: Option<U>) -> Result<T, Error>
where
T: DeserializeOwned,
U: Serialize + Sized,
{
fn deserialize<T>(response: String) -> Result<T, Error>
where
T: DeserializeOwned,
{
serde_json::from_str::<T>(&response).or_else(|_| {
match serde_json::from_str::<responses::Error>(&response) {
Ok(json) => Err(Error::new(json.message.to_string())),
Err(err) => Err(Error::new(err.to_string())),
}
})
}

let mut request = reqwest::Client::new().get(path);

if let Some(some_query) = query {
request = request.query(&some_query)
}

request
.header("api_key", &self.api_key)
.send()
.and_then(|mut response| response.text())
.map_err(|err| Error::new(err.to_string()))
.and_then(deserialize)
}
}

impl FromStr for Client {
type Err = Error;

Expand Down
58 changes: 58 additions & 0 deletions src/traits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use crate::error::{Error, ErrorResponse};

use reqwest;
use serde::{de::DeserializeOwned, Serialize};
use serde_json;

pub trait ApiKey {
fn api_key(&self) -> &str;
}

pub trait Fetch: Requester + Deserializer {
fn fetch<U, V>(&self, path: &str, query: Option<V>) -> Result<U, Error>
where
U: DeserializeOwned,
V: Serialize + Sized,
{
self.request(path, query).and_then(Self::deserialize)
}
}

pub trait Requester: ApiKey {
fn request<T>(&self, path: &str, query: Option<T>) -> Result<String, Error>
where
T: Serialize + Sized,
{
let mut request = reqwest::Client::new().get(path);

if let Some(some_query) = query {
request = request.query(&some_query)
}

request
.header("api_key", self.api_key())
.send()
.and_then(|mut response| response.text())
.map_err(|err| Error::new(err.to_string()))
}
}

pub trait Deserializer {
fn deserialize<T>(response: String) -> Result<T, Error>
where
T: DeserializeOwned,
{
serde_json::from_str::<T>(&response).or_else(|_| {
match serde_json::from_str::<ErrorResponse>(&response) {
Ok(json) => Err(Error::new(json.message.to_string())),
Err(err) => Err(Error::new(err.to_string())),
}
})
}
}

impl<T> Requester for T where T: ApiKey {}

impl<T> Deserializer for T where T: ApiKey {}

impl<T> Fetch for T where T: ApiKey {}

0 comments on commit 8e92fc0

Please sign in to comment.