nominatim-rs 0.2.2

Bindings to the Nominatim web API for rust
Documentation
use crate::client::Client;
use crate::error::Error;
use crate::serde_utils::{
    serialize_as_string_opt, serialize_bool_as_string,
    serialize_vector_as_string_opt,
};
use crate::types::Response;
use crate::util::RequestBuilderHelper;
use derive_builder::Builder;
use serde::Serialize;

#[derive(Debug, Default, Clone, Serialize)]
#[serde(into = "String")]
pub struct Street {
    pub house_number: String,
    pub street_name: String,
}

impl From<Street> for String {
    fn from(street: Street) -> Self {
        format!("{} {}", street.house_number, street.street_name)
    }
}

/// Represents the different types of way that nominatim can request for a
/// location.
#[derive(Debug, Clone, Serialize)]
#[serde(untagged)]
pub enum LocationQuery {
    /// Free-form query string to search for. Free-form queries are
    /// processed first left-to-right and then right-to-left if that fails.
    /// So you may search for `pilkington avenue, birmingham` as well as
    /// for `birmingham, pikington avenue`. Commas are optional but
    /// improve performance by reducing the complexity of the search.
    Generalised { q: String },
    /// Alternative query string format split into several parameters
    /// for structured requests. Structured requests are faster but
    /// are less robust against alternative OSM tagging schemas.
    Structured {
        street: Option<Street>,
        city: Option<String>,
        county: Option<String>,
        state: Option<String>,
        country: Option<String>,
        #[serde(rename = "postalcode")]
        postal_code: Option<String>,
    },
}

#[derive(Builder, Debug, Clone, Serialize)]
pub struct SearchQuery {
    #[serde(flatten)]
    pub location_query: LocationQuery,
    /// Include a breakdown of the address into elements
    #[serde(rename = "addressdetails")]
    #[serde(serialize_with = "serialize_bool_as_string")]
    pub address_details: bool,
    /// Include additional information if the result is available
    #[builder(default)]
    #[serde(rename = "extratags")]
    #[serde(serialize_with = "serialize_bool_as_string")]
    pub extra_tags: bool,
    /// Include a list of alternative names in the results. This may include
    /// language variants, references, operator and brand.
    #[builder(default)]
    #[serde(rename = "namedetails")]
    #[serde(serialize_with = "serialize_bool_as_string")]
    pub name_details: bool,
    /// Preferred language order for showing search results, overrides
    /// the value specified in the "Accept-Languague" HTTP header.
    /// Either use a standard RFC2616 accept-language string or
    /// a simple comma-separated list of language codes.
    #[builder(default)]
    #[serde(rename = "accept-language")]
    #[serde(serialize_with = "serialize_vector_as_string_opt")]
    pub accept_language: Option<Vec<String>>,
    /// Include addition information if the result is available
    /// Limit search results to one of more countries. The country code must
    /// be the
    /// [ISO-3166-1alpha2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
    /// code, e.g. `gb` for the United Kingdom, `de` for Germany.
    ///
    /// Each place in Nominatim is assigned to one country code based of OSM
    /// country borders. In rare cases a place may not be in any country at
    /// all, for example, in international waters.
    #[builder(default)]
    #[serde(rename = "countrycodes")]
    #[serde(serialize_with = "serialize_vector_as_string_opt")]
    pub country_codes: Option<Vec<String>>,
    /// If you do not want certain OSM objects to appear in the search
    /// result, give a comma separated list of the `place_id`s you want to
    /// skip. This can be used to retrieve additional search results.
    /// For example, if a previous query only returned a few results, then
    /// including those here would cause the search to return other, less
    /// accurate, matches (if possible.)
    #[builder(default)]
    #[serde(serialize_with = "serialize_vector_as_string_opt")]
    pub exclude_place_ids: Option<Vec<u64>>,
    /// Limits the number of returned results. (Default: 10, Maximum: 50.)
    #[builder(default)]
    #[serde(serialize_with = "serialize_as_string_opt")]
    pub limit: Option<u8>,
    /// The preferred area to find search results. Any two corner
    /// points of the box are accepted as long as they span a real box.
    ///
    /// ```http
    /// viewbox=<x1>,<y1>,<x2>,<y2>
    /// ```
    #[builder(default)]
    #[serde(serialize_with = "serialize_vector_as_string_opt")]
    pub viewbox: Option<[f64; 4]>,
    /// Sometimes you have several objects in OSM identifying the same place
    /// or object in reality. The simplest case is a street being split into
    /// many different OSM ways due to different characteristics. Nominatim
    /// will attempt to detect such duplicates and only return on match
    /// unless this parameter is set to `false`. (Default: `true`),
    #[builder(default = "true")]
    #[serde(serialize_with = "serialize_bool_as_string")]
    pub dedupe: bool,
}

impl Client {
    /// The search API allows you to look up a location from a textual
    /// description or addrses. Nominatim supports structured and
    /// free-form search queries.
    pub async fn search(
        &self,
        query: SearchQuery,
    ) -> Result<Vec<Response>, Error> {
        let mut url = self.base_url.join("search")?;
        url.set_query(Some(&serde_urlencoded::to_string(&query).unwrap()));

        let builder = self.client.get(url).query_s("format", "json");
        let response = builder.send().await?;

        let status = response.status();
        if status != reqwest::StatusCode::OK {
            return Err(Error::ResponseCode(status));
        }

        let text = response.text().await?;

        Ok(serde_json::from_str(&text)?)
    }
}