serpnode 0.1.1

Serpnode API client for Rust
Documentation
use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT};
use serde::Serialize;

const DEFAULT_BASE_URL: &str = "https://api.serpnode.com/v1";

#[derive(thiserror::Error, Debug)]
pub enum SerpnodeError {
    #[error("http error: {0}")]
    Http(#[from] reqwest::Error),
    #[error("api error: {0}")]
    Api(String),
    #[error("serialization error: {0}")]
    Serde(#[from] serde_json::Error),
}

#[derive(Clone)]
pub struct Client {
    http: reqwest::Client,
    api_key: String,
    base_url: String,
    auth_in_query: bool,
}

impl Client {
    pub fn new<S: Into<String>>(api_key: S) -> Self {
        let mut headers = HeaderMap::new();
        headers.insert(USER_AGENT, HeaderValue::from_static("serpnode-rs/0.1"));
        let http = reqwest::Client::builder()
            .default_headers(headers)
            .build()
            .expect("client");
        Self { http, api_key: api_key.into(), base_url: DEFAULT_BASE_URL.to_string(), auth_in_query: false }
    }

    pub fn with_base_url<S: Into<String>>(mut self, base_url: S) -> Self { self.base_url = base_url.into(); self }
    pub fn auth_in_query(mut self, v: bool) -> Self { self.auth_in_query = v; self }

    pub async fn status(&self) -> Result<serde_json::Value, SerpnodeError> {
        self.get_json("/status", None::<&[(&str, &str)]>).await
    }

    pub async fn search<T: Serialize + ?Sized>(&self, params: &T) -> Result<serde_json::Value, SerpnodeError> {
        self.get_json("/search", Some(params)).await
    }

    pub async fn options<T: Serialize + ?Sized>(&self, params: &T) -> Result<serde_json::Value, SerpnodeError> {
        self.get_json("/options", Some(params)).await
    }

    pub async fn locations<T: Serialize + ?Sized>(&self, params: &T) -> Result<serde_json::Value, SerpnodeError> {
        self.get_json("/locations", Some(params)).await
    }

    async fn get_json<T: Serialize + ?Sized>(&self, path: &str, params: Option<&T>) -> Result<serde_json::Value, SerpnodeError> {
        let url = format!("{}/{}", self.base_url.trim_end_matches('/'), path.trim_start_matches('/'));
        let mut req = self.http.get(url);

        if self.auth_in_query {
            req = req.query(&[("apikey", self.api_key.as_str())]);
        } else {
            req = req.header("apikey", &self.api_key);
        }

        if let Some(p) = params { req = req.query(p); }

        let resp = req.send().await?;
        let status = resp.status();
        let text = resp.text().await?;
        if !status.is_success() {
            return Err(SerpnodeError::Api(format!("HTTP {}: {}", status, text)));
        }
        let v: serde_json::Value = serde_json::from_str(&text)?;
        Ok(v)
    }
}