use std::collections::HashMap;
use std::fmt;
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json;
use crate::common::*;
use crate::{auth, error, links};
mod fun;
pub use self::fun::*;
#[derive(Debug, Clone, Deserialize)]
pub struct Place {
pub id: String,
pub attributes: HashMap<String, String>,
#[serde(deserialize_with = "deserialize_bounding_box")]
pub bounding_box: Vec<(f64, f64)>,
pub country: String,
pub country_code: String,
pub full_name: String,
pub name: String,
pub place_type: PlaceType,
pub contained_within: Option<Vec<Place>>,
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub enum PlaceType {
#[serde(rename = "poi")]
PointOfInterest,
#[serde(rename = "neighborhood")]
Neighborhood,
#[serde(rename = "city")]
City,
#[serde(rename = "admin")]
Admin,
#[serde(rename = "country")]
Country,
}
#[derive(Debug, Copy, Clone)]
pub enum Accuracy {
Meters(f64),
Feet(f64),
}
pub struct SearchResult {
pub url: String,
pub results: Vec<Place>,
}
impl<'de> Deserialize<'de> for SearchResult {
fn deserialize<D>(deser: D) -> Result<SearchResult, D::Error>
where
D: Deserializer<'de>,
{
let raw: serde_json::Value = serde_json::Value::deserialize(deser)?;
let url = raw
.get("query")
.and_then(|obj| obj.get("url"))
.ok_or_else(|| D::Error::custom("Malformed search result"))?
.to_string();
let results = raw
.get("result")
.and_then(|obj| obj.get("places"))
.and_then(|arr| <Vec<Place>>::deserialize(arr).ok())
.ok_or_else(|| D::Error::custom("Malformed search result"))?;
Ok(SearchResult { url, results })
}
}
pub struct GeocodeBuilder {
coordinate: (f64, f64),
accuracy: Option<Accuracy>,
granularity: Option<PlaceType>,
max_results: Option<u32>,
}
impl GeocodeBuilder {
fn new(latitude: f64, longitude: f64) -> Self {
GeocodeBuilder {
coordinate: (latitude, longitude),
accuracy: None,
granularity: None,
max_results: None,
}
}
pub fn accuracy(self, accuracy: Accuracy) -> Self {
GeocodeBuilder {
accuracy: Some(accuracy),
..self
}
}
pub fn granularity(self, granularity: PlaceType) -> Self {
GeocodeBuilder {
granularity: Some(granularity),
..self
}
}
pub fn max_results(self, max_results: u32) -> Self {
GeocodeBuilder {
max_results: Some(max_results),
..self
}
}
pub async fn call(&self, token: &auth::Token) -> Result<Response<SearchResult>, error::Error> {
let params = ParamList::new()
.add_param("lat", self.coordinate.0.to_string())
.add_param("long", self.coordinate.1.to_string())
.add_opt_param("accuracy", self.accuracy.map_string())
.add_opt_param("granularity", self.granularity.map_string())
.add_opt_param(
"max_results",
self.max_results.map(|count| {
let count = if count == 0 || count > 20 { 20 } else { count };
count.to_string()
}),
);
let req = get(links::place::REVERSE_GEOCODE, token, Some(¶ms));
request_with_json_response(req).await
}
}
enum PlaceQuery {
LatLon(f64, f64),
Query(CowStr),
IPAddress(CowStr),
}
pub struct SearchBuilder {
query: PlaceQuery,
accuracy: Option<Accuracy>,
granularity: Option<PlaceType>,
max_results: Option<u32>,
contained_within: Option<String>,
attributes: Option<HashMap<String, String>>,
}
impl SearchBuilder {
fn new(query: PlaceQuery) -> Self {
SearchBuilder {
query: query,
accuracy: None,
granularity: None,
max_results: None,
contained_within: None,
attributes: None,
}
}
pub fn accuracy(self, accuracy: Accuracy) -> Self {
SearchBuilder {
accuracy: Some(accuracy),
..self
}
}
pub fn granularity(self, granularity: PlaceType) -> Self {
SearchBuilder {
granularity: Some(granularity),
..self
}
}
pub fn max_results(self, max_results: u32) -> Self {
SearchBuilder {
max_results: Some(max_results),
..self
}
}
pub fn contained_within(self, contained_id: String) -> Self {
SearchBuilder {
contained_within: Some(contained_id),
..self
}
}
pub fn attribute(self, attribute_key: String, attribute_value: String) -> Self {
let mut attrs = self.attributes.unwrap_or_default();
attrs.insert(attribute_key, attribute_value);
SearchBuilder {
attributes: Some(attrs),
..self
}
}
pub async fn call(&self, token: &auth::Token) -> Result<Response<SearchResult>, error::Error> {
let mut params = match &self.query {
PlaceQuery::LatLon(lat, long) => ParamList::new()
.add_param("lat", lat.to_string())
.add_param("long", long.to_string()),
PlaceQuery::Query(text) => ParamList::new().add_param("query", text.to_string()),
PlaceQuery::IPAddress(text) => ParamList::new().add_param("ip", text.to_string()),
}
.add_opt_param("accuracy", self.accuracy.map_string())
.add_opt_param("granularity", self.granularity.map_string())
.add_opt_param("max_results", self.max_results.map_string())
.add_opt_param("contained_within", self.contained_within.map_string());
if let Some(ref attrs) = self.attributes {
for (k, v) in attrs {
params.add_param_ref(format!("attribute:{}", k), v.clone());
}
}
let req = get(links::place::SEARCH, token, Some(¶ms));
request_with_json_response(req).await
}
}
impl fmt::Display for PlaceType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let quoted = serde_json::to_string(self).unwrap();
let inner = "ed[1..quoted.len() - 1]; write!(f, "{}", inner)
}
}
impl fmt::Display for Accuracy {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Accuracy::Meters(dist) => write!(f, "{}", dist),
Accuracy::Feet(dist) => write!(f, "{}ft", dist),
}
}
}
fn deserialize_bounding_box<'de, D>(ser: D) -> Result<Vec<(f64, f64)>, D::Error>
where
D: Deserializer<'de>,
{
let s = serde_json::Value::deserialize(ser)?;
s.get("coordinates")
.and_then(|arr| arr.get(0).cloned())
.ok_or_else(|| D::Error::custom("Malformed 'bounding_box' attribute"))
.and_then(|inner_arr| {
serde_json::from_value::<Vec<(f64, f64)>>(inner_arr).map_err(|e| D::Error::custom(e))
})
}