brewfatherlog 0.1.6

Log temperatures from grainfather fermenters to brewfather
Documentation
use reqwest::StatusCode;
use reqwest::header::CONTENT_TYPE;
use serde::{Deserialize, Serialize};
use std::time::Duration;

const CONNECTION_TIMEOUT: Duration = Duration::from_secs(60);
const REQUEST_TIMEOUT: Duration = Duration::from_secs(30);

#[derive(thiserror::Error, Debug)]
pub enum BrewfatherError {
    #[error("transport error: {error}")]
    Transport {
        #[from]
        error: reqwest::Error,
    },
    #[error("unexpected status code {status_code}: \"{payload}\"")]
    UnexpectedStatusCode { status_code: StatusCode, payload: String },
    #[error("unexpected result: \"{result}\"")]
    UnexpectedResult { result: String },
    #[error("no result")]
    NoResult,
}

#[derive(Serialize)]
pub struct BrewfatherLoggingEvent<'a> {
    pub name: &'a str,
    pub temp: f32,
}

pub struct Brewfather {
    logging_id: String,
    client: reqwest::Client,
}

impl Brewfather {
    pub fn new(logging_id: impl Into<String>) -> Result<Brewfather, BrewfatherError> {
        let client = reqwest::ClientBuilder::new()
            .connect_timeout(CONNECTION_TIMEOUT)
            .timeout(REQUEST_TIMEOUT)
            .build()?;

        Ok(Brewfather { logging_id: logging_id.into(), client })
    }

    pub async fn log(&self, event: BrewfatherLoggingEvent<'_>) -> Result<(), BrewfatherError> {
        #[derive(Deserialize)]
        struct Response {
            result: Option<String>,
        }

        let url = format!("https://log.brewfather.net/stream?id={}", self.logging_id);

        let http_response = self
            .client
            .post(url)
            .header(CONTENT_TYPE, "application/json")
            .json(&event)
            .send()
            .await?;

        match http_response.status() {
            StatusCode::OK => {
                let response = http_response.json::<Response>().await?;

                match response.result.as_deref() {
                    Some("OK" | "success") => Ok(()),
                    Some(result) => {
                        Err(BrewfatherError::UnexpectedResult { result: result.to_owned() })
                    }
                    None => Err(BrewfatherError::NoResult),
                }
            }
            status_code => {
                let payload = http_response.text().await?;
                Err(BrewfatherError::UnexpectedStatusCode { status_code, payload })
            }
        }
    }
}