w3w_api/
lib.rs

1use std::io;
2
3use derivative::Derivative;
4use serde::de::DeserializeOwned;
5use ureq::OrAnyStatus;
6
7pub mod api;
8
9use crate::api::{ApiResponse, GeoCoords};
10pub use crate::api::{AvailableLanguages, Coords};
11
12#[derive(Debug, thiserror::Error)]
13#[allow(clippy::large_enum_variant)]
14pub enum Error {
15    #[error("API returned an error: {0:?}")]
16    Api(crate::api::ErrorResponse),
17    #[error("HTTP transport error")]
18    HttpTransport(#[from] ureq::Transport),
19    #[error("JSON error")]
20    Json(#[from] io::Error),
21    #[error("URL error")]
22    Url(#[from] url::ParseError),
23}
24
25#[derive(Derivative)]
26#[derivative(Debug)]
27pub struct Client {
28    #[derivative(Debug = "ignore")]
29    key: String,
30    pub base_url: url::Url,
31}
32
33impl Client {
34    pub fn new(key: &str) -> Self {
35        let client = Self {
36            key: key.to_string(),
37            base_url: url::Url::parse("https://api.what3words.com/v3/").unwrap(),
38        };
39        tracing::debug!(%client.base_url, "creating new client");
40        client
41    }
42
43    #[tracing::instrument(skip(self))]
44    fn prepare_request(&self, path: &str) -> Result<ureq::Request, Error> {
45        let url = self.base_url.join(path)?;
46        let request = ureq::get(url.as_str()).query("format", "json");
47        tracing::trace!(url = ?request.url());
48        Ok(request.query("key", &self.key))
49    }
50
51    #[tracing::instrument(skip(self, request), err)]
52    fn send<T: DeserializeOwned>(&self, request: ureq::Request) -> Result<ApiResponse<T>, Error> {
53        let response = request
54            .call()
55            .or_any_status()
56            .map_err(Error::HttpTransport)?;
57        let (status, status_text) = (response.status(), response.status_text());
58        tracing::trace!(response.status = status, response.status_text = status_text);
59        response.into_json().map_err(Error::Json)
60    }
61
62    #[tracing::instrument(skip(self), err)]
63    pub fn convert_to_coordinates(&self, words: &str) -> Result<Coords, Error> {
64        let request = self
65            .prepare_request("convert-to-coordinates")?
66            .query("words", words);
67        self.send(request)?.into()
68    }
69
70    #[tracing::instrument(skip(self), err)]
71    pub fn convert_to_3wa(&self, coordinates: &GeoCoords) -> Result<Coords, Error> {
72        let request = self
73            .prepare_request("convert-to-3wa")?
74            .query("coordinates", &coordinates.to_string());
75        self.send(request)?.into()
76    }
77
78    #[tracing::instrument(skip(self), err)]
79    pub fn available_languages(&self) -> Result<AvailableLanguages, Error> {
80        let request = self.prepare_request("available-languages")?;
81        self.send(request)?.into()
82    }
83}