pixeluvw_supabase 0.1.0

A production-ready, high-performance Supabase SDK for Rust with middleware, retry logic, and Arc<Inner> architecture
Documentation
//! Edge Functions client for Supabase.
//!
//! This module provides functionality for invoking Supabase Edge Functions.
//!
//! # Example
//!
//! ```rust,no_run
//! use pixeluvw_supabase::SupabaseClient;
//! use serde_json::json;
//!
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
//! dotenv::dotenv().ok();
//! let client = SupabaseClient::from_env()?;
//!
//! // Invoke a function with a body
//! let result: serde_json::Value = client.functions()
//!     .invoke("process-payment", Some(json!({"amount": 100, "currency": "USD"})))
//!     .await?;
//!
//! println!("Result: {:?}", result);
//!
//! // Invoke a function without a body
//! let result: serde_json::Value = client.functions()
//!     .invoke::<()>("health-check", None)
//!     .await?;
//! # Ok(())
//! # }
//! ```

use crate::core::SupabaseClient;
use crate::error::{Result, SupaError};
use serde::Serialize;
use serde_json::Value;

/// Client for invoking Supabase Edge Functions.
///
/// Access via `client.functions()`.
///
/// # Example
///
/// ```rust,no_run
/// # async fn example(client: pixeluvw_supabase::SupabaseClient) -> Result<(), Box<dyn std::error::Error>> {
/// let functions = client.functions();
///
/// let result = functions
///     .invoke("my-function", Some(serde_json::json!({"key": "value"})))
///     .await?;
/// # Ok(())
/// # }
/// ```
#[derive(Clone)]
pub struct FunctionsClient {
    client: SupabaseClient,
}

impl FunctionsClient {
    pub(crate) fn new(client: SupabaseClient) -> Self {
        Self { client }
    }

    /// Invoke a Supabase Edge Function.
    ///
    /// # Arguments
    ///
    /// * `function_name` - The name of the function to invoke
    /// * `body` - Optional request body (will be serialized as JSON)
    ///
    /// # Returns
    ///
    /// The function's response as a `serde_json::Value`. If the response
    /// is not valid JSON, it will be returned as a `Value::String`.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use serde_json::json;
    ///
    /// # async fn example(client: pixeluvw_supabase::SupabaseClient) -> Result<(), Box<dyn std::error::Error>> {
    /// // With a body
    /// let result = client.functions()
    ///     .invoke("send-email", Some(json!({
    ///         "to": "user@example.com",
    ///         "subject": "Hello",
    ///         "body": "Welcome!"
    ///     })))
    ///     .await?;
    ///
    /// // Without a body
    /// let result = client.functions()
    ///     .invoke::<()>("cron-job", None)
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn invoke<T: Serialize>(
        &self,
        function_name: &str,
        body: Option<T>,
    ) -> Result<Value> {
        let url = self
            .client
            .inner
            .url
            .join(&format!("functions/v1/{}", function_name))?;

        let mut req = self
            .client
            .inner
            .http
            .post(url)
            .header("apikey", &self.client.inner.key)
            .header("Authorization", self.client.auth_header());

        if let Some(b) = body {
            req = req.json(&b);
        }

        let resp = req.send().await?;
        let status = resp.status();

        if !status.is_success() {
            let error_text = resp.text().await.unwrap_or_default();
            return Err(SupaError::FunctionsError {
                message: format!("Function invocation failed: {}", error_text),
            });
        }

        let text = resp.text().await?;
        if text.is_empty() {
            return Ok(Value::Null);
        }

        match serde_json::from_str::<Value>(&text) {
            Ok(v) => Ok(v),
            Err(_) => {
                // If not JSON, return as string wrapped in Value
                Ok(Value::String(text))
            }
        }
    }
}