supabase_function_rs/
client.rs

1use crate::errors::{FunctionsError};
2use crate::models::{FunctionInvokeOptions, FunctionRegion, FunctionsResponse, HttpMethod, InvokeBody, ResponseData};
3use reqwest::Client;
4use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
5use std::collections::HashMap;
6use std::convert::TryFrom;
7
8#[derive(Debug, Clone)]
9pub struct FunctionsClient {
10    url: String,
11    headers: HashMap<String, String>,
12    region: FunctionRegion,
13    client: Client,
14}
15
16impl FunctionsClient {
17    pub fn new(url: String, headers: Option<HashMap<String, String>>, region: Option<FunctionRegion>) -> Self {
18        Self {
19            url,
20            headers: headers.unwrap_or_default(),
21            region: region.unwrap_or(FunctionRegion::Any),
22            client: Client::new(),
23        }
24    }
25
26    pub fn set_auth(&mut self, token: String) {
27        self.headers.insert("Authorization".to_string(), format!("Bearer {}", token));
28    }
29
30    pub async fn invoke(
31        &self,
32        function_name: &str,
33        options: Option<FunctionInvokeOptions>,
34    ) -> Result<FunctionsResponse, FunctionsError> {
35        let options = options.unwrap_or_default();
36        let headers = self.headers.clone();
37
38        let mut req_headers = HeaderMap::new();
39        for (key, value) in headers {
40            req_headers.insert(
41                HeaderName::try_from(key.as_str()).map_err(|_| FunctionsError::FetchError("Invalid header name".into()))?,
42                HeaderValue::from_str(&value).map_err(|_| FunctionsError::FetchError("Invalid header value".into()))?,
43            );
44        }
45
46        if let Some(region) = options.region {
47            if region != FunctionRegion::Any {
48                req_headers.insert(
49                    HeaderName::from_static("x-region"),
50                    HeaderValue::from_str(region.to_string().as_str()).map_err(|_| FunctionsError::FetchError("Invalid region value".into()))?,
51                );
52            }
53        }
54
55        let method = options.method.unwrap_or(HttpMethod::Post);
56        let method_str = method.as_str();
57        let url = format!("{}/{}", self.url, function_name);
58
59
60        let request_builder = match options.body {
61            Some(InvokeBody::File(ref file)) |
62            Some(InvokeBody::Blob(ref file)) |
63            Some(InvokeBody::ArrayBuffer(ref file)) => {
64                req_headers.insert("Content-Type", HeaderValue::from_static("application/octet-stream"));
65                self.client.request(method_str.parse().unwrap(), &url).headers(req_headers).body(file.clone())
66            }
67            Some(InvokeBody::String(ref s)) => {
68                req_headers.insert("Content-Type", HeaderValue::from_static("text/plain"));
69                self.client.request(method_str.parse().unwrap(), &url).headers(req_headers).body(s.clone())
70            }
71            Some(InvokeBody::FormData(ref form_data)) => {
72                let form = reqwest::multipart::Form::new();
73                let form = form_data.iter().fold(form, |form, (key, value)| {
74                    form.text(key.clone(), value.clone())
75                });
76                self.client.request(method_str.parse().unwrap(), &url).headers(req_headers).multipart(form)
77            }
78            Some(InvokeBody::Json(ref json)) => {
79                req_headers.insert("Content-Type", HeaderValue::from_static("application/json"));
80                self.client.request(method_str.parse().unwrap(), &url).headers(req_headers).json(json)
81            }
82            None => self.client.request(method_str.parse().unwrap(), &url).headers(req_headers),
83        };
84
85        let response = request_builder.send().await.map_err(|e| FunctionsError::FetchError(e.to_string()))?;
86
87
88        if let Some(is_relay_error) = response.headers().get("x-relay-error") {
89            if is_relay_error == "true" {
90                return Err(FunctionsError::RelayError("Relay Error invoking the Edge Function".into()));
91            }
92        }
93
94        if !response.status().is_success() {
95            return Err(FunctionsError::HttpError(response.status().to_string()));
96        }
97
98        let content_type = response
99            .headers()
100            .get(reqwest::header::CONTENT_TYPE)
101            .and_then(|v| v.to_str().ok())
102            .unwrap_or("text/plain")
103            .split(';')
104            .next()
105            .unwrap_or("text/plain");
106
107        let data = match content_type {
108            "application/json" => {
109                let json_data = response.json::<serde_json::Value>().await.map_err(|e| FunctionsError::FetchError(e.to_string()))?;
110                ResponseData::Json(json_data)
111            },
112            "application/octet-stream" => {
113                let bytes_data = response.bytes().await.map_err(|e| FunctionsError::FetchError(e.to_string()))?;
114                ResponseData::Bytes(bytes_data)
115            },
116            "text/event-stream" => {
117                let text_data = response.text().await.map_err(|e| FunctionsError::FetchError(e.to_string()))?;
118                ResponseData::Text(text_data)
119            },
120            "multipart/form-data" => {
121                let form_data = response.json::<HashMap<String, String>>().await.map_err(|e| FunctionsError::FetchError(e.to_string()))?;
122                ResponseData::FormData(form_data)
123            },
124            _ => {
125                let text_data = response.text().await.map_err(|e| FunctionsError::FetchError(e.to_string()))?;
126                ResponseData::Text(text_data)
127            }
128        };
129
130        Ok(FunctionsResponse::Success { data })
131    }
132}