extern crate reqwest;
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
pub mod error;
#[derive(Deserialize, Serialize, PartialEq)]
pub struct Person {
name: String,
craft: String,
}
impl Person {
pub fn new(name: &str, craft: &str) -> Person {
Person {
name: String::from(name),
craft: String::from(craft),
}
}
pub fn name(&self) -> &str {
self.name.as_str()
}
pub fn craft(&self) -> &str {
self.craft.as_str()
}
}
#[derive(Deserialize, Serialize)]
pub struct Astros {
message: String,
#[serde(default)]
reason: String,
#[serde(default)]
number: i32,
#[serde(default)]
people: Vec<Person>,
}
impl Astros {
pub fn people(&self) -> &Vec<Person> {
&self.people
}
}
#[derive(Default, Deserialize, Serialize)]
struct IssPosition {
latitude: f32,
longitude: f32,
}
#[derive(Deserialize, Serialize)]
pub struct IssNow {
message: String,
#[serde(default)]
reason: String,
#[serde(default)]
timestamp: i64,
#[serde(default)]
iss_position: IssPosition,
}
impl IssNow {
pub fn timestamp(&self) -> i64 {
self.timestamp
}
pub fn latitude(&self) -> f32 {
self.iss_position.latitude
}
pub fn longitude(&self) -> f32 {
self.iss_position.longitude
}
}
pub fn astros() -> Result<Astros, error::OpenNotificationError> {
astro_from_json(&reqwest::get("http://api.open-notify.org/astros.json")?.text()?)
}
fn astro_from_json(data: &str) -> Result<Astros, error::OpenNotificationError> {
let astros: Astros = serde_json::from_str(data)?;
if astros.number as usize != astros.people.len() {
return Err(error::OpenNotificationError::Data(String::from(
"attribute 'number' does not match length of people field",
)));
}
if astros.message != "success" {
return Err(error::OpenNotificationError::Data(astros.reason));
}
Ok(astros)
}
pub fn iss_now() -> Result<IssNow, error::OpenNotificationError> {
iss_now_from_json(&reqwest::get("http://api.open-notify.org/iss-now.json")?.text()?)
}
fn iss_now_from_json(data: &str) -> Result<IssNow, error::OpenNotificationError> {
let iss_now: IssNow = serde_json::from_str(data)?;
if iss_now.message != "success" {
return Err(error::OpenNotificationError::Data(iss_now.reason));
}
Ok(iss_now)
}
#[derive(Default, Deserialize, Serialize)]
struct IssPassTimesRequest {
latitude: f32,
longitude: f32,
altitude: f32,
passes: u32,
datetime: i64,
}
#[derive(Deserialize, Serialize)]
pub struct IssPassTime {
risetime: i64,
duration: i64,
}
impl IssPassTime {
pub fn rise(&self) -> i64 {
self.risetime
}
pub fn duration(&self) -> i64 {
self.duration
}
}
#[derive(Deserialize, Serialize)]
pub struct IssPassTimes {
message: String,
#[serde(default)]
reason: String,
#[serde(default)]
request: IssPassTimesRequest,
#[serde(default)]
response: Vec<IssPassTime>,
}
impl IssPassTimes {
pub fn passes(&self) -> &[IssPassTime] {
&self.response
}
}
pub fn iss_pass_times(
lat: f32,
lon: f32,
alt: f32,
n: u32,
) -> Result<IssPassTimes, error::OpenNotificationError> {
iss_pass_times_from_json(&reqwest::get(
format!(
"http://api.open-notify.org/iss-pass.json?lat={}&lon={}&alt={}&n={}",
lat, lon, alt, n,
).as_str(),
)?.text()?)
}
fn iss_pass_times_from_json(data: &str) -> Result<IssPassTimes, error::OpenNotificationError> {
let iss_pass_times: IssPassTimes = serde_json::from_str(data)?;
if iss_pass_times.message != "success" {
return Err(error::OpenNotificationError::Data(iss_pass_times.reason));
}
Ok(iss_pass_times)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn astro_parse_successful_data() {
let input_data = r#"{
"message": "success",
"number": 6,
"people": [
{"name": "Anton Shkaplerov", "craft": "ISS"},
{"name": "Scott Tingle", "craft": "ISS"},
{"name": "Norishige Kanai", "craft": "ISS"},
{"name": "Oleg Artemyev", "craft": "Soyuz MS-08"},
{"name": "Andrew Feustel", "craft": "Soyuz MS-08"},
{"name": "Richard Arnold", "craft": "Soyuz MS-08"}]
}"#;
let expected_people = vec![
Person::new("Anton Shkaplerov", "ISS"),
Person::new("Scott Tingle", "ISS"),
Person::new("Norishige Kanai", "ISS"),
Person::new("Oleg Artemyev", "Soyuz MS-08"),
Person::new("Andrew Feustel", "Soyuz MS-08"),
Person::new("Richard Arnold", "Soyuz MS-08"),
];
if let Ok(astros) = astro_from_json(input_data) {
assert_eq!(astros.people().len(), 6);
for person in expected_people.iter() {
assert!(astros.people().contains(&person));
}
} else {
assert!(false);
}
}
#[test]
fn astro_parse_missing_data() {
let input_data = r#"{
"message": "success",
"number": 6,
"people": [
{"name": "Anton Shkaplerov", "craft": "ISS"},
{"name": "Scott Tingle", "craft": "ISS"},
{"name": "Norishige Kanai", "craft": "ISS"},
{"name": "Oleg Artemyev" },
{"name": "Andrew Feustel", "craft": "Soyuz MS-08"},
{"name": "Richard Arnold", "craft": "Soyuz MS-08"}]
}"#;
match astro_from_json(input_data) {
Err(error::OpenNotificationError::Parsing(_)) => assert!(true),
Err(_) => assert!(false),
Ok(_) => assert!(false),
}
}
#[test]
fn astro_parse_inconsistent_data() {
let input_data = r#"{
"message": "success",
"number": 5,
"people": [
{"name": "Anton Shkaplerov", "craft": "ISS"},
{"name": "Scott Tingle", "craft": "ISS"},
{"name": "Norishige Kanai", "craft": "ISS"},
{"name": "Oleg Artemyev", "craft": "Soyuz MS-08"},
{"name": "Andrew Feustel", "craft": "Soyuz MS-08"},
{"name": "Richard Arnold", "craft": "Soyuz MS-08"}]
}"#;
match astro_from_json(input_data) {
Err(error::OpenNotificationError::Data(_)) => assert!(true),
Err(_) => assert!(false),
Ok(_) => assert!(false),
}
}
#[test]
fn astro_parse_unsuccessfull_data() {
let input_data = r#"{
"message": "failure",
"reason": "something went wrong"
}"#;
use error::OpenNotificationError::Data;
match astro_from_json(input_data) {
Err(Data(msg)) => assert_eq!(msg, "something went wrong"),
Err(_) => assert!(false),
Ok(_) => assert!(false),
}
}
#[test]
fn iss_now_parse_successful_data() {
let input_data = r#"{
"iss_position": {"longitude": 73.5964, "latitude": -34.6445},
"message": "success",
"timestamp": 1521971230}"#;
if let Ok(iss_now) = iss_now_from_json(input_data) {
assert_eq!(iss_now.timestamp(), 1521971230);
assert_eq!(iss_now.latitude(), -34.6445);
assert_eq!(iss_now.longitude(), 73.5964);
} else {
assert!(false);
}
}
#[test]
fn iss_now_parse_unsuccessfull_data() {
let input_data = r#"{
"message": "failure",
"reason": "something went wrong"
}"#;
use error::OpenNotificationError::Data;
match iss_now_from_json(input_data) {
Err(Data(msg)) => assert_eq!(msg, "something went wrong"),
Err(_) => assert!(false),
Ok(_) => assert!(false),
}
}
}