w3w-api 0.1.1

Rust library for what3words public API
Documentation
use std::io;

use derivative::Derivative;
use serde::de::DeserializeOwned;
use ureq::OrAnyStatus;

pub mod api;

use crate::api::{ApiResponse, GeoCoords};
pub use crate::api::{AvailableLanguages, Coords};

#[derive(Debug, thiserror::Error)]
#[allow(clippy::large_enum_variant)]
pub enum Error {
    #[error("API returned an error: {0:?}")]
    Api(crate::api::ErrorResponse),
    #[error("HTTP transport error")]
    HttpTransport(#[from] ureq::Transport),
    #[error("JSON error")]
    Json(#[from] io::Error),
    #[error("URL error")]
    Url(#[from] url::ParseError),
}

#[derive(Derivative)]
#[derivative(Debug)]
pub struct Client {
    #[derivative(Debug = "ignore")]
    key: String,
    pub base_url: url::Url,
}

impl Client {
    pub fn new(key: &str) -> Self {
        let client = Self {
            key: key.to_string(),
            base_url: url::Url::parse("https://api.what3words.com/v3/").unwrap(),
        };
        tracing::debug!(%client.base_url, "creating new client");
        client
    }

    #[tracing::instrument(skip(self))]
    fn prepare_request(&self, path: &str) -> Result<ureq::Request, Error> {
        let url = self.base_url.join(path)?;
        let request = ureq::get(url.as_str()).query("format", "json");
        tracing::trace!(url = ?request.url());
        Ok(request.query("key", &self.key))
    }

    #[tracing::instrument(skip(self, request), err)]
    fn send<T: DeserializeOwned>(&self, request: ureq::Request) -> Result<ApiResponse<T>, Error> {
        let response = request
            .call()
            .or_any_status()
            .map_err(Error::HttpTransport)?;
        let (status, status_text) = (response.status(), response.status_text());
        tracing::trace!(response.status = status, response.status_text = status_text);
        response.into_json().map_err(Error::Json)
    }

    #[tracing::instrument(skip(self), err)]
    pub fn convert_to_coordinates(&self, words: &str) -> Result<Coords, Error> {
        let request = self
            .prepare_request("convert-to-coordinates")?
            .query("words", words);
        self.send(request)?.into()
    }

    #[tracing::instrument(skip(self), err)]
    pub fn convert_to_3wa(&self, coordinates: &GeoCoords) -> Result<Coords, Error> {
        let request = self
            .prepare_request("convert-to-3wa")?
            .query("coordinates", &coordinates.to_string());
        self.send(request)?.into()
    }

    #[tracing::instrument(skip(self), err)]
    pub fn available_languages(&self) -> Result<AvailableLanguages, Error> {
        let request = self.prepare_request("available-languages")?;
        self.send(request)?.into()
    }
}