Skip to main content

alien_bindings/providers/function/
local.rs

1use crate::error::{ErrorData, Result};
2use crate::traits::{Binding, Function, FunctionInvokeRequest, FunctionInvokeResponse};
3use alien_core::bindings::LocalFunctionBinding;
4use alien_error::{Context, IntoAlienError};
5use async_trait::async_trait;
6use std::collections::BTreeMap;
7
8/// Local function binding implementation for development and testing.
9///
10/// This provides a simple HTTP client for calling local functions
11/// running on HTTP endpoints (e.g., during local development).
12#[derive(Debug)]
13pub struct LocalFunction {
14    binding: LocalFunctionBinding,
15}
16
17impl LocalFunction {
18    /// Create a new local function binding.
19    pub fn new(binding: LocalFunctionBinding) -> Self {
20        Self { binding }
21    }
22
23    /// Get the function URL from the binding, resolving template expressions if needed
24    fn get_function_url(&self) -> Result<String> {
25        self.binding
26            .function_url
27            .clone()
28            .into_value("function", "function_url")
29            .context(ErrorData::BindingConfigInvalid {
30                binding_name: "function".to_string(),
31                reason: "Failed to resolve function_url from binding".to_string(),
32            })
33    }
34}
35
36impl Binding for LocalFunction {}
37
38#[async_trait]
39impl Function for LocalFunction {
40    async fn invoke(&self, request: FunctionInvokeRequest) -> Result<FunctionInvokeResponse> {
41        let function_url = self.get_function_url()?;
42
43        // Build the target URL
44        let target_url = if !request.target_function.is_empty() {
45            format!(
46                "{}/{}",
47                function_url.trim_end_matches('/'),
48                request.target_function
49            )
50        } else {
51            function_url
52        };
53
54        // Add path if provided
55        let full_url = if !request.path.is_empty() {
56            format!(
57                "{}/{}",
58                target_url.trim_end_matches('/'),
59                request.path.trim_start_matches('/')
60            )
61        } else {
62            target_url
63        };
64
65        // Create HTTP client
66        let client = reqwest::Client::new();
67
68        // Build HTTP request
69        let mut http_request = client.request(
70            reqwest::Method::from_bytes(request.method.as_bytes())
71                .into_alien_error()
72                .context(ErrorData::BindingConfigInvalid {
73                    binding_name: "function".to_string(),
74                    reason: format!("Invalid HTTP method: {}", request.method),
75                })?,
76            &full_url,
77        );
78
79        // Add headers
80        for (key, value) in request.headers {
81            http_request = http_request.header(key, value);
82        }
83
84        // Add body if provided
85        if !request.body.is_empty() {
86            http_request = http_request.body(request.body);
87        }
88
89        // Set timeout if provided
90        if let Some(timeout) = request.timeout {
91            http_request = http_request.timeout(timeout);
92        }
93
94        // Send request
95        let response = http_request.send().await.into_alien_error().context(
96            ErrorData::CloudPlatformError {
97                message: format!("Failed to invoke local function at: {}", full_url),
98                resource_id: None,
99            },
100        )?;
101
102        // Extract response
103        let status = response.status().as_u16();
104        let mut headers = BTreeMap::new();
105
106        for (key, value) in response.headers() {
107            if let Ok(value_str) = value.to_str() {
108                headers.insert(key.to_string(), value_str.to_string());
109            }
110        }
111
112        let body = response
113            .bytes()
114            .await
115            .into_alien_error()
116            .context(ErrorData::CloudPlatformError {
117                message: "Failed to read response body from local function".to_string(),
118                resource_id: None,
119            })?
120            .to_vec();
121
122        Ok(FunctionInvokeResponse {
123            status,
124            headers,
125            body,
126        })
127    }
128
129    async fn get_function_url(&self) -> Result<Option<String>> {
130        Ok(Some(self.get_function_url()?))
131    }
132
133    fn as_any(&self) -> &dyn std::any::Any {
134        self
135    }
136}