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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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)?)
    }
}