wacht 0.1.0-beta.5

Official Rust SDK for the Wacht platform, providing type-safe API client and authentication middleware
Documentation
use crate::{
    client::WachtClient,
    error::{Error, Result},
    models::{
        ComposioAuthConfigListResponse, ComposioConfigResponse, ComposioToolkitDetailsResponse,
        ComposioToolkitListResponse, EnableComposioAppRequest, UpdateComposioConfigRequest,
    },
};
use serde::Serialize;

#[derive(Debug, Clone)]
pub struct ComposioApi {
    client: WachtClient,
}

impl ComposioApi {
    pub(crate) fn new(client: WachtClient) -> Self {
        Self { client }
    }

    pub fn fetch_config(&self) -> FetchComposioConfigBuilder {
        FetchComposioConfigBuilder::new(self.client.clone())
    }

    pub fn update_config(
        &self,
        request: UpdateComposioConfigRequest,
    ) -> UpdateComposioConfigBuilder {
        UpdateComposioConfigBuilder::new(self.client.clone(), request)
    }

    pub fn list_toolkits(&self) -> ListComposioToolkitsBuilder {
        ListComposioToolkitsBuilder::new(self.client.clone())
    }

    pub fn enable_app(&self, request: EnableComposioAppRequest) -> EnableComposioAppBuilder {
        EnableComposioAppBuilder::new(self.client.clone(), request)
    }

    pub fn disable_app(&self, slug: impl Into<String>) -> DisableComposioAppBuilder {
        DisableComposioAppBuilder::new(self.client.clone(), slug)
    }

    pub fn list_toolkit_auth_configs(
        &self,
        slug: impl Into<String>,
    ) -> ListComposioToolkitAuthConfigsBuilder {
        ListComposioToolkitAuthConfigsBuilder::new(self.client.clone(), slug)
    }

    pub fn fetch_toolkit_auth_details(
        &self,
        slug: impl Into<String>,
    ) -> FetchComposioToolkitAuthDetailsBuilder {
        FetchComposioToolkitAuthDetailsBuilder::new(self.client.clone(), slug)
    }
}

fn api_error(status: reqwest::StatusCode, prefix: &str, body: String) -> Error {
    Error::api_from_text(status, prefix, &body)
}

#[derive(Debug, Default, Serialize)]
struct ListToolkitsQuery {
    #[serde(skip_serializing_if = "Option::is_none")]
    search: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    category: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    cursor: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    limit: Option<u32>,
}

pub struct FetchComposioConfigBuilder {
    client: WachtClient,
}

impl FetchComposioConfigBuilder {
    fn new(client: WachtClient) -> Self {
        Self { client }
    }

    pub async fn send(self) -> Result<ComposioConfigResponse> {
        let response = self
            .client
            .http_client()
            .get(format!(
                "{}/ai/composio/config",
                self.client.config().base_url
            ))
            .send()
            .await?;
        let status = response.status();
        if status.is_success() {
            Ok(response.json().await?)
        } else {
            Err(api_error(
                status,
                "Failed to fetch Composio config",
                response.text().await?,
            ))
        }
    }
}

pub struct UpdateComposioConfigBuilder {
    client: WachtClient,
    request: UpdateComposioConfigRequest,
}

impl UpdateComposioConfigBuilder {
    fn new(client: WachtClient, request: UpdateComposioConfigRequest) -> Self {
        Self { client, request }
    }

    pub async fn send(self) -> Result<ComposioConfigResponse> {
        let response = self
            .client
            .http_client()
            .patch(format!(
                "{}/ai/composio/config",
                self.client.config().base_url
            ))
            .json(&self.request)
            .send()
            .await?;
        let status = response.status();
        if status.is_success() {
            Ok(response.json().await?)
        } else {
            Err(api_error(
                status,
                "Failed to update Composio config",
                response.text().await?,
            ))
        }
    }
}

pub struct ListComposioToolkitsBuilder {
    client: WachtClient,
    query: ListToolkitsQuery,
}

impl ListComposioToolkitsBuilder {
    fn new(client: WachtClient) -> Self {
        Self {
            client,
            query: ListToolkitsQuery::default(),
        }
    }

    pub fn search(mut self, search: impl Into<String>) -> Self {
        self.query.search = Some(search.into());
        self
    }

    pub fn category(mut self, category: impl Into<String>) -> Self {
        self.query.category = Some(category.into());
        self
    }

    pub fn cursor(mut self, cursor: impl Into<String>) -> Self {
        self.query.cursor = Some(cursor.into());
        self
    }

    pub fn limit(mut self, limit: u32) -> Self {
        self.query.limit = Some(limit);
        self
    }

    pub async fn send(self) -> Result<ComposioToolkitListResponse> {
        let response = self
            .client
            .http_client()
            .get(format!(
                "{}/ai/composio/toolkits",
                self.client.config().base_url
            ))
            .query(&self.query)
            .send()
            .await?;
        let status = response.status();
        if status.is_success() {
            Ok(response.json().await?)
        } else {
            Err(api_error(
                status,
                "Failed to list Composio toolkits",
                response.text().await?,
            ))
        }
    }
}

pub struct EnableComposioAppBuilder {
    client: WachtClient,
    request: EnableComposioAppRequest,
}

impl EnableComposioAppBuilder {
    fn new(client: WachtClient, request: EnableComposioAppRequest) -> Self {
        Self { client, request }
    }

    pub async fn send(self) -> Result<ComposioConfigResponse> {
        let response = self
            .client
            .http_client()
            .post(format!(
                "{}/ai/composio/apps",
                self.client.config().base_url
            ))
            .json(&self.request)
            .send()
            .await?;
        let status = response.status();
        if status.is_success() {
            Ok(response.json().await?)
        } else {
            Err(api_error(
                status,
                "Failed to enable Composio app",
                response.text().await?,
            ))
        }
    }
}

pub struct DisableComposioAppBuilder {
    client: WachtClient,
    slug: String,
}

impl DisableComposioAppBuilder {
    fn new(client: WachtClient, slug: impl Into<String>) -> Self {
        Self {
            client,
            slug: slug.into(),
        }
    }

    pub async fn send(self) -> Result<ComposioConfigResponse> {
        let response = self
            .client
            .http_client()
            .delete(format!(
                "{}/ai/composio/apps/{}",
                self.client.config().base_url,
                self.slug
            ))
            .send()
            .await?;
        let status = response.status();
        if status.is_success() {
            Ok(response.json().await?)
        } else {
            Err(api_error(
                status,
                "Failed to disable Composio app",
                response.text().await?,
            ))
        }
    }
}

pub struct ListComposioToolkitAuthConfigsBuilder {
    client: WachtClient,
    slug: String,
}

impl ListComposioToolkitAuthConfigsBuilder {
    fn new(client: WachtClient, slug: impl Into<String>) -> Self {
        Self {
            client,
            slug: slug.into(),
        }
    }

    pub async fn send(self) -> Result<ComposioAuthConfigListResponse> {
        let response = self
            .client
            .http_client()
            .get(format!(
                "{}/ai/composio/toolkits/{}/auth-configs",
                self.client.config().base_url,
                self.slug
            ))
            .send()
            .await?;
        let status = response.status();
        if status.is_success() {
            Ok(response.json().await?)
        } else {
            Err(api_error(
                status,
                "Failed to list Composio auth configs",
                response.text().await?,
            ))
        }
    }
}

pub struct FetchComposioToolkitAuthDetailsBuilder {
    client: WachtClient,
    slug: String,
}

impl FetchComposioToolkitAuthDetailsBuilder {
    fn new(client: WachtClient, slug: impl Into<String>) -> Self {
        Self {
            client,
            slug: slug.into(),
        }
    }

    pub async fn send(self) -> Result<ComposioToolkitDetailsResponse> {
        let response = self
            .client
            .http_client()
            .get(format!(
                "{}/ai/composio/toolkits/{}/auth-details",
                self.client.config().base_url,
                self.slug
            ))
            .send()
            .await?;
        let status = response.status();
        if status.is_success() {
            Ok(response.json().await?)
        } else {
            Err(api_error(
                status,
                "Failed to fetch Composio auth details",
                response.text().await?,
            ))
        }
    }
}