lark-channel 0.6.0

Lark/Feishu Channel SDK for Rust
Documentation
//! Minimal Feishu/Lark OpenAPI subset used by `lark-channel`.
//!
//! This module contains the OpenAPI client foundation used by this crate:
//! authentication, transport abstraction, response parsing, and the OpenAPI
//! resources needed by Channel workflows.
//!
//! This is not a complete OpenAPI SDK. Low-level OpenAPI types are exposed
//! through this dedicated namespace so they remain distinguishable from the
//! Channel SDK API and replaceable by a future external `lark-openapi` crate
//! or an official Rust OpenAPI SDK adapter.

mod auth;
mod card;
mod media;
mod message;
mod response;
#[cfg(test)]
pub(crate) mod test_support;
mod transport;
mod ws;

use std::fmt;
use std::sync::Arc;

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

use crate::{ChannelConfig, Result};

pub use auth::{AppAccessTokenResponse, TenantAccessTokenResponse};
pub use card::CardUpdateOptions;
pub use media::{
    FileCreateRequest, FileKey, FileType, ImageCreateRequest, ImageKey, ImageType,
    MAX_FILE_UPLOAD_BYTES, MAX_IMAGE_UPLOAD_BYTES, MAX_MESSAGE_RESOURCE_BYTES, MessageResourceType,
};
pub use message::{MessageCreateOptions, MessageReplyOptions};
#[cfg(feature = "reqwest-transport")]
pub use transport::ReqwestOpenApiTransport;
pub use transport::{
    BinaryHttpResponse, BoxFuture, HttpMethod, HttpRequest, HttpResponse, MultipartPart,
    MultipartRequest, OpenApiBinaryTransport, OpenApiMultipartTransport, OpenApiTransport,
};
#[cfg(feature = "websocket")]
pub(crate) use ws::WebSocketConnectionItem;
#[cfg(feature = "websocket")]
pub use ws::{TokioTungsteniteWebSocketTransport, WebSocketConnection};
pub use ws::{
    WebSocketClientConfig, WebSocketEndpoint, WebSocketEvent, WebSocketEventAck,
    WebSocketEventFrame, WebSocketFrame, WebSocketFrameMethod, WebSocketHeader,
    WebSocketMessageType,
};

use auth::AccessTokenCache;
use response::parse_openapi_response;

#[derive(Clone)]
pub struct OpenApiClient<T> {
    config: ChannelConfig,
    transport: T,
    app_access_token_cache: Arc<AccessTokenCache>,
    tenant_access_token_cache: Arc<AccessTokenCache>,
}

impl<T> fmt::Debug for OpenApiClient<T> {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("OpenApiClient")
            .field("config", &self.config)
            .field("transport_type", &std::any::type_name::<T>())
            .finish_non_exhaustive()
    }
}

impl<T> OpenApiClient<T>
where
    T: OpenApiTransport,
{
    pub fn new(config: ChannelConfig, transport: T) -> Self {
        Self {
            config,
            transport,
            app_access_token_cache: Arc::new(AccessTokenCache::default()),
            tenant_access_token_cache: Arc::new(AccessTokenCache::default()),
        }
    }

    pub fn config(&self) -> &ChannelConfig {
        &self.config
    }

    pub(crate) fn transport(&self) -> &T {
        &self.transport
    }

    pub async fn post_openapi_json<B, R>(&self, path: &str, body: &B) -> Result<R>
    where
        B: Serialize + ?Sized,
        R: DeserializeOwned,
    {
        let request = self.post_json_request(path, body)?;
        let response = self.transport.send_json(request).await?;
        parse_openapi_response(response)
    }

    pub async fn post_tenant_json<B, R>(&self, path: &str, body: &B) -> Result<R>
    where
        B: Serialize + ?Sized,
        R: DeserializeOwned,
    {
        self.tenant_json(HttpMethod::Post, path, body).await
    }

    /// Sends an authenticated tenant JSON request with HTTP PUT.
    pub async fn put_tenant_json<B, R>(&self, path: &str, body: &B) -> Result<R>
    where
        B: Serialize + ?Sized,
        R: DeserializeOwned,
    {
        self.tenant_json(HttpMethod::Put, path, body).await
    }

    /// Sends an authenticated tenant JSON request with HTTP PATCH.
    pub async fn patch_tenant_json<B, R>(&self, path: &str, body: &B) -> Result<R>
    where
        B: Serialize + ?Sized,
        R: DeserializeOwned,
    {
        self.tenant_json(HttpMethod::Patch, path, body).await
    }

    async fn tenant_json<B, R>(&self, method: HttpMethod, path: &str, body: &B) -> Result<R>
    where
        B: Serialize + ?Sized,
        R: DeserializeOwned,
    {
        let request = self.json_request(method, path, body)?;
        let token = self.tenant_access_token().await?;
        let request = request.with_bearer_auth(token);
        let response = self.transport.send_json(request).await?;
        parse_openapi_response(response)
    }

    fn post_json_request<B>(&self, path: &str, body: &B) -> Result<HttpRequest>
    where
        B: Serialize + ?Sized,
    {
        self.json_request(HttpMethod::Post, path, body)
    }

    fn json_request<B>(&self, method: HttpMethod, path: &str, body: &B) -> Result<HttpRequest>
    where
        B: Serialize + ?Sized,
    {
        let url = self.config.base_url().join(path)?;
        let body = serde_json::to_value(body)?;
        Ok(HttpRequest::json(method, url, body))
    }
}

#[cfg(test)]
mod tests;