dalloriam-cloud-client 0.3.0

client library for personal cloud
Documentation
use dalloriam_cloud_protocol::jd::{
    Area, AreaList, Category, CategoryList, Item, ItemsList, NewItem,
};
use reqwest::{Response, StatusCode};
use snafu::{ResultExt, Snafu};

use crate::error::AuthError;
use crate::{service, Credentials};

#[derive(Debug, Snafu)]
pub enum JdError {
    TokenValidationError {
        source: AuthError,
    },
    AccessDenied,
    #[snafu(display("unexpected status {}", status))]
    UnexpectedError {
        status: StatusCode,
    },
    ResponseDeserializationError {
        source: reqwest::Error,
    },

    // Call errors
    CreateArea {
        source: reqwest::Error,
    },
    ListAreas {
        source: reqwest::Error,
    },
    CreateCategory {
        source: reqwest::Error,
    },
    ListCategories {
        source: reqwest::Error,
    },
    CreateItem {
        source: reqwest::Error,
    },
    Search {
        source: reqwest::Error,
    },
}

type Result<T> = std::result::Result<T, JdError>;

pub struct JdClient {
    client: reqwest::Client,
    credentials: Credentials,
}

impl JdClient {
    pub async fn new(credentials: Credentials) -> Result<Self> {
        // Make sure all our calls are as fast as possible.
        let credentials = credentials
            .ensure_token()
            .await
            .context(TokenValidationSnafu)?;

        Ok(Self {
            client: reqwest::Client::default(),
            credentials,
        })
    }

    fn check_error(&self, resp: &Response) -> Result<()> {
        if !resp.status().is_success() {
            return match resp.status() {
                StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => Err(JdError::AccessDenied),
                _ => Err(JdError::UnexpectedError {
                    status: resp.status(),
                }),
            };
        }

        Ok(())
    }

    pub async fn create_area(&self, area: &Area) -> Result<()> {
        let resp = self
            .client
            .post(&format!("{}/area/{}", service::JD_HOST, area.id))
            .query(&[("name", &area.name)])
            .header("Authorization", self.credentials.to_string())
            .send()
            .await
            .context(CreateAreaSnafu)?;

        self.check_error(&resp)
    }

    pub async fn list_areas(&self) -> Result<AreaList> {
        let resp = self
            .client
            .get(&format!("{}/area", service::JD_HOST))
            .header("Authorization", self.credentials.to_string())
            .send()
            .await
            .context(ListAreasSnafu)?;

        self.check_error(&resp)?;

        resp.json::<AreaList>()
            .await
            .context(ResponseDeserializationSnafu)
    }

    pub async fn create_category(&self, category: &Category) -> Result<()> {
        let resp = self
            .client
            .post(&format!("{}/category/{}", service::JD_HOST, category.id))
            .query(&[("name", &category.name)])
            .header("Authorization", self.credentials.to_string())
            .send()
            .await
            .context(CreateCategorySnafu)?;

        self.check_error(&resp)
    }

    pub async fn list_categories(&self) -> Result<CategoryList> {
        let resp = self
            .client
            .get(&format!("{}/category", service::JD_HOST))
            .header("Authorization", self.credentials.to_string())
            .send()
            .await
            .context(ListCategoriesSnafu)?;

        self.check_error(&resp)?;

        resp.json::<CategoryList>()
            .await
            .context(ResponseDeserializationSnafu)
    }

    pub async fn create_item(&self, item: &NewItem) -> Result<Item> {
        let resp = self
            .client
            .post(&format!(
                "{}/category/{}/item",
                service::JD_HOST,
                item.category_id
            ))
            .query(&[("name", &item.name)])
            .header("Authorization", self.credentials.to_string())
            .send()
            .await
            .context(CreateItemSnafu)?;

        self.check_error(&resp)?;

        resp.json::<Item>()
            .await
            .context(ResponseDeserializationSnafu)
    }

    pub async fn put_item(&self, item: &Item) -> Result<()> {
        let resp = self
            .client
            .post(&format!(
                "{}/category/{}/item/{}",
                service::JD_HOST,
                item.category_id,
                item.id
            ))
            .query(&[("name", &item.name)])
            .header("Authorization", self.credentials.to_string())
            .send()
            .await
            .context(CreateItemSnafu)?;
        self.check_error(&resp)
    }

    pub async fn search(
        &self,
        category: Option<usize>,
        query: Option<String>,
    ) -> Result<ItemsList> {
        let mut qstr = vec![];

        if let Some(cat) = category {
            qstr.push(("category", cat.to_string()));
        }

        if let Some(q) = query {
            qstr.push(("q", q));
        }

        let resp = self
            .client
            .post(&format!("{}/search", service::JD_HOST))
            .query(&qstr)
            .header("Authorization", self.credentials.to_string())
            .send()
            .await
            .context(SearchSnafu)?;

        self.check_error(&resp)?;

        resp.json::<ItemsList>()
            .await
            .context(ResponseDeserializationSnafu)
    }
}