1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
//! # apiety
//!
//! Thin wrapper around GGG's API for [Path of Exile](http://www.pathofexile.com/)
//!
//! # Examples
//!
//! ## Get Public Stash Tab Api with id 0
//!
//! ```rust,no_run
//!
//! let endpoint = apiety::stash::public::Endpoint::new("0");
//! let body = apiety::get_body(&endpoint).unwrap();
//! let _response = apiety::stash::public::Endpoint::deserialize(body.as_str()).unwrap();
//! ```
//!
//! ## Try stuff on multiple response bodies and cache them
//!
//! ```rust,no_run
//! let mut endpoint = apiety::stash::public::Endpoint::new("0");
//! for _ in 0..100 {
//!     // can be later changed to get_body for production ready code
//!     let body = apiety::cache_body(&endpoint).unwrap();
//!     let response = apiety::stash::public::Endpoint::deserialize(body.as_str()).unwrap();
//!     endpoint = apiety::stash::public::Endpoint::new(response.next_change_id.as_str());
//!
//!     for stash in response.stashes {
//!         //try stuff
//!         println!("{}", stash.id);
//!     }
//! }
//! ```
//!


#[macro_use]
extern crate error_chain;
extern crate reqwest;
#[macro_use]
extern crate serde_derive;

extern crate serde_json;

mod errors;
use errors::*;

use std::io::prelude::*;
use std::fs;
use std::fs::{File, OpenOptions};
use std::path::Path;

pub mod stash;

const BASE_URL: &'static str = "http://api.pathofexile.com";

/// Path relative to the current working directory, which will be used by fn cache_body
const CACHE_DIR: &'static str = "cache";

/// Provides the second part of a url required to perform the request for the endpoint which
/// implements this trait
pub trait Endpoint {
    fn get_url(&self) -> &str;
}

/// Checks the folder ./data for a cached response, if it can't find it, it will
/// perform the request and save it to ./cache
pub fn cache_body<T: Endpoint>(endpoint: &T) -> Result<String> {
    let mut cached_file = CACHE_DIR.to_string();
    cached_file.push_str(endpoint.get_url());
    cached_file = cached_file.replace("?", "");
    let cached_file = Path::new(cached_file.as_str());
    if cached_file.exists() {
        // serve cached file
        let mut file = File::open(cached_file)?;
        let mut body = String::new();
        file.read_to_string(&mut body)?;
        Ok(body)
    } else {
        // get response from endpoint and save it
        let body = get_body(endpoint)?;
        if let Some(parent) = cached_file.parent() {
            if !parent.exists() {
                //create cache dir
                fs::create_dir_all(cached_file.parent().unwrap())
                    .chain_err(|| "could not create cache directory")?;
            }
        }
        let mut file = OpenOptions::new()
            .create_new(true)
            .write(true)
            .open(cached_file)
            .chain_err(|| "could not create cache file")?;
        file.write_all(body.as_bytes())?;
        Ok(body)
    }
}

/// Sends a request and returns the response body in a String
pub fn get_body<T: Endpoint>(endpoint: &T) -> Result<String> {
    let mut result = String::new();

    // combine base url and endpoint url
    let mut url = BASE_URL.to_string();
    url.push_str(endpoint.get_url());

    // perform request and then store the response body in the string result
    let mut response = reqwest::get(url.as_str()).chain_err(|| "could not get response")?;
    response.read_to_string(&mut result).chain_err(|| "could not read body")?;

    Ok(result)
}