foursquare-async 0.1.2

Foursquare API for async Rust
Documentation
use arcstr::ArcStr;
use itertools::Itertools;
use reqwest::header::HeaderName;
use reqwest::Method;

mod error;
use error::Error;
mod model;
pub use model::*;

pub struct Client {
	http: reqwest::Client,
	api_key: ArcStr
}

const API_ROOT: &str = "https://api.foursquare.com/v3/";
const PLACE_SEARCH_PATH: &str = "places/search";

impl Client {
	pub fn new(api_key: &str) -> Self {
		Self{
			http: reqwest::Client::new(),
			api_key: api_key.into()
		}
	}

	async fn request(&self, method: Method, path: &str) -> Result<String, reqwest::Error> /* {{{ */ {
		let url = {
			let mut s = String::from(API_ROOT);
			s.push_str(path);
			s
		};
		let response = self.http.request(method, &url)
			.header(HeaderName::from_static("authorization"), self.api_key.as_str())
			.send()
			.await?
			.text()
			.await?;
		Ok(response)
	} // }}}

	#[inline]
	async fn get(&self, path: &str) -> Result<String, reqwest::Error> {
		self.request(Method::GET, path).await
	}

	#[inline]
	pub async fn search_places(&self, params: &[(&'static str, String)]) -> Result<PlaceResponse, Error> {
		let path = {
			let mut s = String::from(PLACE_SEARCH_PATH);
			s.push('?');
			s.push_str(&serde_urlencoded::to_string(params)?);
			s
		};
		let body = self.get(&path).await?;
		Ok(serde_json::from_str(&body)?)
	}

	pub async fn get_places_near(&self, latitude: f32, longitude: f32, radius_in_meters: u16) -> Result<PlaceResponse, Error> {
		self.search_places(&[
			("ll", format!("{},{}", latitude, longitude)),
			("radius", radius_in_meters.to_string())
		]).await
	}

	pub async fn get_places_near_with_category(&self, latitude: f32, longitude: f32, radius_in_meters: u16, categories: &[u16]) -> Result<PlaceResponse, Error> {
		self.search_places(&[
			("ll", format!("{},{}", latitude, longitude)),
			("radius", radius_in_meters.to_string()),
			("categories", categories.iter().map(|v| v.to_string()).join(","))
		]).await
	}
}

#[cfg(test)]
mod tests {
	use super::Client;

	fn new_client() -> Client /* {{{ */ {
		Client{
			http: reqwest::Client::new(),
			api_key: arcstr::literal!(env!("FOURSQUARE_API_KEY"))
		}
	} // }}}

	#[tokio::test]
	async fn places_near_udairy() {
		let client = new_client();
		let places = client.get_places_near(39.66509724690995, -75.75047266260954, 100).await.unwrap();
		println!("{}", serde_json::to_string_pretty(&places).unwrap());
		assert!(places.results().iter().any(|p| p.id() == "80b74fd504fc4e6fd23d1d53"));
		assert!(places.results().iter().any(|p| p.name() == "University of Delaware Ice Arena"));
	}

	#[tokio::test]
	async fn places_near_rummur_lounge() {
		let client = new_client();
		let places = client.get_places_near(39.52702076582365, -75.81214777855061, 10).await.unwrap();
		println!("{}", serde_json::to_string_pretty(&places).unwrap());
		assert!(places.results().iter().any(|p| p.id() == "17b21bcfc2b94276b7654ec3"));
		assert!(places.results().iter().any(|p| p.name() == "Rummur Lounge"));
	}

	#[tokio::test]
	async fn city_halls_near_philly() {
		let client = new_client();
		let places = client.get_places_near_with_category(39.957231008535786, -75.18099479230888, 10000, &[12066]).await.unwrap();
		println!("{}", serde_json::to_string_pretty(&places).unwrap());
		assert!(places.results().iter().any(|p| p.name() == "Philadelphia City Hall"));
		assert!(places.results().iter().any(|p| p.name() == "Camden City Hall"));
		assert!(places.results().iter().any(|p| p.name() == "Upper Darby Township Building"));
		assert!(places.results().iter().any(|p| p.name() == "East Lansdowne Borough Hall"));
	}
}