plansolve 0.25.2

Official Rust client library for the PlanSolve optimization API.
Documentation
use std::time::Duration;

use serde::de::DeserializeOwned;
use serde::Serialize;

use crate::error::{Error, Result};

const API_KEY_HEADER: &str = "X-API-KEY";

/// Shared HTTP transport used by every service client. Cloning is cheap —
/// `reqwest::Client` wraps an internal `Arc` and shares a connection pool.
#[derive(Clone)]
pub(crate) struct Transport {
    http: reqwest::Client,
    base_url: String,
    api_key: String,
}

impl Transport {
    pub(crate) fn new(base_url: String, api_key: String) -> Self {
        let http = reqwest::Client::builder()
            .timeout(Duration::from_secs(60))
            .build()
            .expect("failed to build HTTP client");
        Self {
            http,
            base_url,
            api_key,
        }
    }

    pub(crate) async fn post_json<B, R>(&self, path: &str, body: &B) -> Result<R>
    where
        B: Serialize + ?Sized,
        R: DeserializeOwned,
    {
        let mut req = self.http.post(self.url(path)).json(body);
        req = self.with_api_key(req);
        Self::decode(req.send().await?).await
    }

    pub(crate) async fn get_json<R>(&self, path: &str) -> Result<R>
    where
        R: DeserializeOwned,
    {
        let mut req = self.http.get(self.url(path));
        req = self.with_api_key(req);
        Self::decode(req.send().await?).await
    }

    fn url(&self, path: &str) -> String {
        format!("{}{}", self.base_url, path)
    }

    fn with_api_key(&self, req: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
        if self.api_key.is_empty() {
            req
        } else {
            req.header(API_KEY_HEADER, &self.api_key)
        }
    }

    async fn decode<R>(resp: reqwest::Response) -> Result<R>
    where
        R: DeserializeOwned,
    {
        let status = resp.status();
        if !status.is_success() {
            let body = resp.text().await.unwrap_or_default();
            return Err(Error::Api {
                status: status.as_u16(),
                body,
            });
        }
        Ok(resp.json::<R>().await?)
    }
}