latlng 0.1.0

Rust SDK for the latlng.work geocoding and places API
Documentation
use crate::client::LatlngClient;
use crate::models::{AutosuggestResponse, CategoriesResponse, NearbyResponse, SearchResponse};
use crate::Result;

/// Places API sub-client.
#[derive(Debug, Clone, Copy)]
pub struct PlacesClient<'a> {
    client: &'a LatlngClient,
}

impl<'a> PlacesClient<'a> {
    pub(crate) fn new(client: &'a LatlngClient) -> Self {
        Self { client }
    }

    /// Creates a nearby places request.
    pub fn nearby(self, lat: f64, lon: f64) -> NearbyRequest<'a> {
        NearbyRequest {
            client: self.client,
            lat,
            lon,
            radius: None,
            category: None,
            limit: None,
        }
    }

    /// Creates a full-text place search request.
    pub fn search(self, query: impl Into<String>) -> SearchRequest<'a> {
        SearchRequest {
            client: self.client,
            query: query.into(),
            lat: None,
            lon: None,
            category: None,
            country: None,
            limit: None,
        }
    }

    /// Creates an autosuggest request.
    ///
    /// Autosuggest uses `https://suggest.latlng.work/autosuggest` by default.
    pub fn autosuggest(self, query: impl Into<String>) -> AutosuggestRequest<'a> {
        AutosuggestRequest {
            client: self.client,
            query: query.into(),
            lat: None,
            lon: None,
            radius: None,
            country: None,
            bbox: None,
            limit: None,
        }
    }

    /// Lists all available place categories.
    pub async fn categories(self) -> Result<CategoriesResponse> {
        self.client.get_json("/v1/places/categories", &[]).await
    }
}

/// Builder for `GET /v1/places/nearby`.
#[derive(Debug, Clone)]
pub struct NearbyRequest<'a> {
    client: &'a LatlngClient,
    lat: f64,
    lon: f64,
    radius: Option<u32>,
    category: Option<String>,
    limit: Option<u32>,
}

impl<'a> NearbyRequest<'a> {
    /// Sets the search radius in meters.
    pub fn radius(mut self, radius: u32) -> Self {
        self.radius = Some(radius);
        self
    }

    /// Filters by place category.
    pub fn category(mut self, category: impl Into<String>) -> Self {
        self.category = Some(category.into());
        self
    }

    /// Sets the maximum number of results.
    pub fn limit(mut self, limit: u32) -> Self {
        self.limit = Some(limit);
        self
    }

    /// Sends the request.
    pub async fn send(self) -> Result<NearbyResponse> {
        let mut params = vec![("lat", self.lat.to_string()), ("lon", self.lon.to_string())];
        push_param(&mut params, "radius", self.radius);
        push_param(&mut params, "type", self.category);
        push_param(&mut params, "limit", self.limit);

        self.client.get_json("/v1/places/nearby", &params).await
    }
}

/// Builder for `GET /v1/places/search`.
#[derive(Debug, Clone)]
pub struct SearchRequest<'a> {
    client: &'a LatlngClient,
    query: String,
    lat: Option<f64>,
    lon: Option<f64>,
    category: Option<String>,
    country: Option<String>,
    limit: Option<u32>,
}

impl<'a> SearchRequest<'a> {
    /// Biases results near a coordinate.
    pub fn bias(mut self, lat: f64, lon: f64) -> Self {
        self.lat = Some(lat);
        self.lon = Some(lon);
        self
    }

    /// Filters by place category.
    pub fn category(mut self, category: impl Into<String>) -> Self {
        self.category = Some(category.into());
        self
    }

    /// Filters by country code.
    pub fn country(mut self, country: impl Into<String>) -> Self {
        self.country = Some(country.into());
        self
    }

    /// Sets the maximum number of results.
    pub fn limit(mut self, limit: u32) -> Self {
        self.limit = Some(limit);
        self
    }

    /// Sends the request.
    pub async fn send(self) -> Result<SearchResponse> {
        let mut params = vec![("q", self.query.to_string())];
        push_param(&mut params, "lat", self.lat);
        push_param(&mut params, "lon", self.lon);
        push_param(&mut params, "type", self.category);
        push_param(&mut params, "country", self.country);
        push_param(&mut params, "limit", self.limit);

        self.client.get_json("/v1/places/search", &params).await
    }
}

/// Builder for `GET /autosuggest` on `https://suggest.latlng.work`.
#[derive(Debug, Clone)]
pub struct AutosuggestRequest<'a> {
    client: &'a LatlngClient,
    query: String,
    lat: Option<f64>,
    lon: Option<f64>,
    radius: Option<u32>,
    country: Option<String>,
    bbox: Option<String>,
    limit: Option<u32>,
}

impl<'a> AutosuggestRequest<'a> {
    /// Biases suggestions near a coordinate.
    pub fn bias(mut self, lat: f64, lon: f64) -> Self {
        self.lat = Some(lat);
        self.lon = Some(lon);
        self
    }

    /// Filters suggestions to a radius around the biased coordinate.
    pub fn radius(mut self, radius: u32) -> Self {
        self.radius = Some(radius);
        self
    }

    /// Filters suggestions by country code, such as `us` or `gb`.
    pub fn country(mut self, country: impl Into<String>) -> Self {
        self.country = Some(country.into());
        self
    }

    /// Filters suggestions to a bounding box: `min_lon,min_lat,max_lon,max_lat`.
    pub fn bbox(mut self, bbox: impl Into<String>) -> Self {
        self.bbox = Some(bbox.into());
        self
    }

    /// Sets the maximum number of suggestions.
    pub fn limit(mut self, limit: u32) -> Self {
        self.limit = Some(limit);
        self
    }

    /// Sends the request.
    pub async fn send(self) -> Result<AutosuggestResponse> {
        let mut params = vec![("q", self.query.to_string())];
        push_param(&mut params, "lat", self.lat);
        push_param(&mut params, "lon", self.lon);
        push_param(&mut params, "radius", self.radius);
        push_param(&mut params, "country", self.country);
        push_param(&mut params, "bbox", self.bbox);
        push_param(&mut params, "limit", self.limit);

        self.client
            .get_autosuggest_json("/autosuggest", &params)
            .await
    }
}

fn push_param<'a, T>(params: &mut Vec<(&'a str, String)>, key: &'a str, value: Option<T>)
where
    T: ToString,
{
    if let Some(value) = value {
        params.push((key, value.to_string()));
    }
}