supabase-function-rs 0.1.1

A Rust client for the Supabase Function API
Documentation
use crate::errors::{FunctionsError};
use crate::models::{FunctionInvokeOptions, FunctionRegion, FunctionsResponse, HttpMethod, InvokeBody, ResponseData};
use reqwest::Client;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
use std::collections::HashMap;
use std::convert::TryFrom;

#[derive(Debug, Clone)]
pub struct FunctionsClient {
    url: String,
    headers: HashMap<String, String>,
    region: FunctionRegion,
    client: Client,
}

impl FunctionsClient {
    pub fn new(url: String, headers: Option<HashMap<String, String>>, region: Option<FunctionRegion>) -> Self {
        Self {
            url,
            headers: headers.unwrap_or_default(),
            region: region.unwrap_or(FunctionRegion::Any),
            client: Client::new(),
        }
    }

    pub fn set_auth(&mut self, token: String) {
        self.headers.insert("Authorization".to_string(), format!("Bearer {}", token));
    }

    pub async fn invoke(
        &self,
        function_name: &str,
        options: Option<FunctionInvokeOptions>,
    ) -> Result<FunctionsResponse, FunctionsError> {
        let options = options.unwrap_or_default();
        let headers = self.headers.clone();

        let mut req_headers = HeaderMap::new();
        for (key, value) in headers {
            req_headers.insert(
                HeaderName::try_from(key.as_str()).map_err(|_| FunctionsError::FetchError("Invalid header name".into()))?,
                HeaderValue::from_str(&value).map_err(|_| FunctionsError::FetchError("Invalid header value".into()))?,
            );
        }

        if let Some(region) = options.region {
            if region != FunctionRegion::Any {
                req_headers.insert(
                    HeaderName::from_static("x-region"),
                    HeaderValue::from_str(region.to_string().as_str()).map_err(|_| FunctionsError::FetchError("Invalid region value".into()))?,
                );
            }
        }

        let method = options.method.unwrap_or(HttpMethod::Post);
        let method_str = method.as_str();
        let url = format!("{}/{}", self.url, function_name);


        let request_builder = match options.body {
            Some(InvokeBody::File(ref file)) |
            Some(InvokeBody::Blob(ref file)) |
            Some(InvokeBody::ArrayBuffer(ref file)) => {
                req_headers.insert("Content-Type", HeaderValue::from_static("application/octet-stream"));
                self.client.request(method_str.parse().unwrap(), &url).headers(req_headers).body(file.clone())
            }
            Some(InvokeBody::String(ref s)) => {
                req_headers.insert("Content-Type", HeaderValue::from_static("text/plain"));
                self.client.request(method_str.parse().unwrap(), &url).headers(req_headers).body(s.clone())
            }
            Some(InvokeBody::FormData(ref form_data)) => {
                let form = reqwest::multipart::Form::new();
                let form = form_data.iter().fold(form, |form, (key, value)| {
                    form.text(key.clone(), value.clone())
                });
                self.client.request(method_str.parse().unwrap(), &url).headers(req_headers).multipart(form)
            }
            Some(InvokeBody::Json(ref json)) => {
                req_headers.insert("Content-Type", HeaderValue::from_static("application/json"));
                self.client.request(method_str.parse().unwrap(), &url).headers(req_headers).json(json)
            }
            None => self.client.request(method_str.parse().unwrap(), &url).headers(req_headers),
        };

        let response = request_builder.send().await.map_err(|e| FunctionsError::FetchError(e.to_string()))?;


        if let Some(is_relay_error) = response.headers().get("x-relay-error") {
            if is_relay_error == "true" {
                return Err(FunctionsError::RelayError("Relay Error invoking the Edge Function".into()));
            }
        }

        if !response.status().is_success() {
            return Err(FunctionsError::HttpError(response.status().to_string()));
        }

        let content_type = response
            .headers()
            .get(reqwest::header::CONTENT_TYPE)
            .and_then(|v| v.to_str().ok())
            .unwrap_or("text/plain")
            .split(';')
            .next()
            .unwrap_or("text/plain");

        let data = match content_type {
            "application/json" => {
                let json_data = response.json::<serde_json::Value>().await.map_err(|e| FunctionsError::FetchError(e.to_string()))?;
                ResponseData::Json(json_data)
            },
            "application/octet-stream" => {
                let bytes_data = response.bytes().await.map_err(|e| FunctionsError::FetchError(e.to_string()))?;
                ResponseData::Bytes(bytes_data)
            },
            "text/event-stream" => {
                let text_data = response.text().await.map_err(|e| FunctionsError::FetchError(e.to_string()))?;
                ResponseData::Text(text_data)
            },
            "multipart/form-data" => {
                let form_data = response.json::<HashMap<String, String>>().await.map_err(|e| FunctionsError::FetchError(e.to_string()))?;
                ResponseData::FormData(form_data)
            },
            _ => {
                let text_data = response.text().await.map_err(|e| FunctionsError::FetchError(e.to_string()))?;
                ResponseData::Text(text_data)
            }
        };

        Ok(FunctionsResponse::Success { data })
    }
}