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
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() } }