workos-rust 0.2.1

unofficial rust sdk for interacting with the workos api
use crate::directory_sync::{
    Group, ListDirectoriesOptions, ListDirectoriesResponse, ListUsersOptions, ListUsersResponse,
    User,
};
use crate::error::WorkOSError;
use crate::organizations::Organization;
use anyhow::{anyhow, Context, Result};
use futures::stream::{self};
use futures::{StreamExt, TryStreamExt};
use std::collections::HashMap;
use surf::{Response, StatusCode};

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

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

impl Client {
    pub fn new(custom_base_url: Option<String>, api_key: String) -> Self {
        Client {
            base_url: custom_base_url.unwrap_or_else(|| DEFAULT_BASE_URL.to_string()),
            api_key,
        }
    }
}

impl Client {
    pub async fn list_directories(
        &self,
        options: ListDirectoriesOptions,
    ) -> Result<ListDirectoriesResponse, WorkOSError> {
        let mut req = surf::get(format!("{}/directories", &self.base_url))
            .header("Authorization", format!("Bearer {}", &self.api_key))
            .build();

        req.set_query(&options)
            .map_err(|e| anyhow!(e))
            .context("unable to add query parameters")
            .map_err(WorkOSError::Unknown)?;

        let mut res = surf::client()
            .send(req)
            .await
            .map_err(|e| anyhow!(e))
            .context("unable to perform request")
            .map_err(WorkOSError::Unknown)?;

        examine_response(&mut res, None).await?;

        res.body_json()
            .await
            .map_err(|e| anyhow!(e))
            .context("unable to parse response")
            .map_err(WorkOSError::Unknown)
    }

    pub async fn delete_directory(&self, directory_id: &str) -> Result<(), WorkOSError> {
        let mut res = surf::delete(format!("{}/directories/{}", &self.base_url, directory_id))
            .header("Authorization", format!("Bearer {}", &self.api_key))
            .await
            .map_err(|e| anyhow!(e))
            .context("unable to perform request")
            .map_err(WorkOSError::Unknown)?;

        examine_response(&mut res, Some(directory_id.to_string())).await
    }

    pub async fn get_user(&self, user_id: &str) -> Result<User, WorkOSError> {
        let mut res = surf::get(format!("{}/directory_users/{}", &self.base_url, user_id))
            .header("Authorization", format!("Bearer {}", &self.api_key))
            .await
            .map_err(|e| anyhow!(e))
            .context("unable to perform request")
            .map_err(WorkOSError::Unknown)?;

        examine_response(&mut res, Some(user_id.to_string())).await?;

        res.body_json()
            .await
            .map_err(|e| anyhow!(e))
            .context("unable to parse response")
            .map_err(WorkOSError::Unknown)
    }

    pub async fn get_users(
        &self,
        user_ids: Vec<String>,
    ) -> Result<HashMap<String, User>, WorkOSError> {
        stream::iter(user_ids)
            .then(|id| async move {
                let user = self.get_user(&id).await.context("unable to get user")?;
                Ok::<User, WorkOSError>(user)
            })
            .try_fold(HashMap::new(), |mut users, user| async move {
                users.insert(user.id.clone(), user);
                Ok(users)
            })
            .await
    }

    pub async fn list_users(
        &self,
        options: ListUsersOptions,
    ) -> Result<ListUsersResponse, WorkOSError> {
        let mut req = surf::get(format!("{}/directory_users", &self.base_url))
            .header("Authorization", format!("Bearer {}", &self.api_key))
            .build();

        req.set_query(&options)
            .map_err(|e| anyhow!(e))
            .context("unable to add query parameters")
            .map_err(WorkOSError::Unknown)?;

        let mut res = surf::client()
            .send(req)
            .await
            .map_err(|e| anyhow!(e))
            .context("unable to perform request")
            .map_err(WorkOSError::Unknown)?;

        examine_response(&mut res, None).await?;

        res.body_json()
            .await
            .map_err(|e| anyhow!(e))
            .context("unable to parse response")
            .map_err(WorkOSError::Unknown)
    }

    pub async fn get_group(&self, group_id: &str) -> Result<Group, WorkOSError> {
        let mut res = surf::get(format!("{}/directory_groups/{}", &self.base_url, group_id))
            .header("Authorization", format!("Bearer {}", &self.api_key))
            .await
            .map_err(|e| anyhow!(e))
            .context("unable to perform request")
            .map_err(WorkOSError::Unknown)?;

        examine_response(&mut res, Some(group_id.to_string())).await?;

        res.body_json()
            .await
            .map_err(|e| anyhow!(e))
            .context("unable to parse response")
            .map_err(WorkOSError::Unknown)
    }

    pub async fn get_groups(
        &self,
        group_ids: Vec<String>,
    ) -> Result<HashMap<String, Group>, WorkOSError> {
        stream::iter(group_ids)
            .then(|id| async move {
                let group = self.get_group(&id).await.context("unable to get group")?;
                Ok::<Group, WorkOSError>(group)
            })
            .try_fold(HashMap::new(), |mut groups, group| async move {
                groups.insert(group.id.clone(), group);
                Ok(groups)
            })
            .await
    }

    pub async fn get_organization(
        &self,
        organization_id: &str,
    ) -> Result<Organization, WorkOSError> {
        let mut res = surf::get(format!(
            "{}/organizations/{}",
            &self.base_url, organization_id
        ))
        .header("Authorization", format!("Bearer {}", &self.api_key))
        .await
        .map_err(|e| anyhow!(e))
        .context("unable to perform request")
        .map_err(WorkOSError::Unknown)?;

        examine_response(&mut res, Some(organization_id.to_string())).await?;

        res.body_json()
            .await
            .map_err(|e| anyhow!(e))
            .context("unable to parse response")
            .map_err(WorkOSError::Unknown)
    }
}

async fn examine_response(response: &mut Response, id: Option<String>) -> Result<(), WorkOSError> {
    let status = response.status();
    match status {
        StatusCode::Ok => Ok(()),
        StatusCode::BadRequest => Err(WorkOSError::NotAcceptable),
        StatusCode::Unauthorized => Err(WorkOSError::InvalidApiKey),
        StatusCode::Forbidden => Err(WorkOSError::IncorrectPermissions),
        StatusCode::NotFound => Err(WorkOSError::NotFound(
            id.unwrap_or_else(|| "unknown".to_string()),
        )),
        _ => Err(WorkOSError::Unknown(anyhow!(format!(
            "received unexpected status '{}' : {}",
            status,
            response
                .body_string()
                .await
                .unwrap_or_else(|_| "unable to read response".to_string())
        )))),
    }
}