Skip to main content

alien_core/bindings/
function.rs

1//! Function binding definitions for cross-function communication
2//!
3//! This module defines the binding parameters for function invocation services:
4//! - AWS Lambda (using function ARN/name for direct invocation)
5//! - GCP Cloud Run (using private service URL)
6//! - Azure Container Apps (using private container app URL)
7
8use super::BindingValue;
9use serde::{Deserialize, Serialize};
10
11/// Represents a function binding for cross-function communication
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(tag = "service", rename_all = "lowercase")]
14pub enum FunctionBinding {
15    /// AWS Lambda binding
16    Lambda(LambdaFunctionBinding),
17    /// GCP Cloud Run binding
18    CloudRun(CloudRunFunctionBinding),
19    /// Azure Container Apps binding
20    ContainerApp(ContainerAppFunctionBinding),
21    /// Kubernetes function binding
22    Kubernetes(KubernetesFunctionBinding),
23    /// Local function binding
24    Local(LocalFunctionBinding),
25}
26
27/// AWS Lambda function binding configuration
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(rename_all = "camelCase")]
30pub struct LambdaFunctionBinding {
31    /// The Lambda function name or ARN for invocation
32    pub function_name: BindingValue<String>,
33    /// The AWS region where the function is located
34    pub region: BindingValue<String>,
35    /// Optional public URL if function has public ingress
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub url: Option<BindingValue<String>>,
38}
39
40/// GCP Cloud Run function binding configuration
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(rename_all = "camelCase")]
43pub struct CloudRunFunctionBinding {
44    /// The GCP project ID
45    pub project_id: BindingValue<String>,
46    /// The Cloud Run service name
47    pub service_name: BindingValue<String>,
48    /// The location/region where the service is deployed
49    pub location: BindingValue<String>,
50    /// Private service URL for direct invocation
51    pub private_url: BindingValue<String>,
52    /// Optional public URL if function has public ingress
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub public_url: Option<BindingValue<String>>,
55}
56
57/// Azure Container Apps function binding configuration
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59#[serde(rename_all = "camelCase")]
60pub struct ContainerAppFunctionBinding {
61    /// The Azure subscription ID
62    pub subscription_id: BindingValue<String>,
63    /// The resource group name
64    pub resource_group_name: BindingValue<String>,
65    /// The container app name
66    pub container_app_name: BindingValue<String>,
67    /// Private app URL for direct invocation within managed environment
68    pub private_url: BindingValue<String>,
69    /// Optional public URL if function has public ingress
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub public_url: Option<BindingValue<String>>,
72}
73
74/// Kubernetes function binding configuration
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
76#[serde(rename_all = "camelCase")]
77pub struct KubernetesFunctionBinding {
78    /// The function name
79    pub name: BindingValue<String>,
80    /// The Kubernetes namespace
81    pub namespace: BindingValue<String>,
82    /// The Kubernetes Service name
83    pub service_name: BindingValue<String>,
84    /// The Service port
85    pub service_port: BindingValue<u16>,
86    /// Optional public URL if function has public ingress
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub public_url: Option<BindingValue<String>>,
89}
90
91/// Local function binding configuration
92#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
93#[serde(rename_all = "camelCase")]
94pub struct LocalFunctionBinding {
95    /// The HTTP URL where the function is accessible
96    pub function_url: BindingValue<String>,
97}
98
99impl FunctionBinding {
100    /// Creates an AWS Lambda function binding
101    pub fn lambda(
102        function_name: impl Into<BindingValue<String>>,
103        region: impl Into<BindingValue<String>>,
104    ) -> Self {
105        Self::Lambda(LambdaFunctionBinding {
106            function_name: function_name.into(),
107            region: region.into(),
108            url: None,
109        })
110    }
111
112    /// Creates an AWS Lambda function binding with public URL
113    pub fn lambda_with_url(
114        function_name: impl Into<BindingValue<String>>,
115        region: impl Into<BindingValue<String>>,
116        url: impl Into<BindingValue<String>>,
117    ) -> Self {
118        Self::Lambda(LambdaFunctionBinding {
119            function_name: function_name.into(),
120            region: region.into(),
121            url: Some(url.into()),
122        })
123    }
124
125    /// Creates a GCP Cloud Run function binding
126    pub fn cloud_run(
127        project_id: impl Into<BindingValue<String>>,
128        service_name: impl Into<BindingValue<String>>,
129        location: impl Into<BindingValue<String>>,
130        private_url: impl Into<BindingValue<String>>,
131    ) -> Self {
132        Self::CloudRun(CloudRunFunctionBinding {
133            project_id: project_id.into(),
134            service_name: service_name.into(),
135            location: location.into(),
136            private_url: private_url.into(),
137            public_url: None,
138        })
139    }
140
141    /// Creates a GCP Cloud Run function binding with public URL
142    pub fn cloud_run_with_public_url(
143        project_id: impl Into<BindingValue<String>>,
144        service_name: impl Into<BindingValue<String>>,
145        location: impl Into<BindingValue<String>>,
146        private_url: impl Into<BindingValue<String>>,
147        public_url: impl Into<BindingValue<String>>,
148    ) -> Self {
149        Self::CloudRun(CloudRunFunctionBinding {
150            project_id: project_id.into(),
151            service_name: service_name.into(),
152            location: location.into(),
153            private_url: private_url.into(),
154            public_url: Some(public_url.into()),
155        })
156    }
157
158    /// Creates an Azure Container Apps function binding
159    pub fn container_app(
160        subscription_id: impl Into<BindingValue<String>>,
161        resource_group_name: impl Into<BindingValue<String>>,
162        container_app_name: impl Into<BindingValue<String>>,
163        private_url: impl Into<BindingValue<String>>,
164    ) -> Self {
165        Self::ContainerApp(ContainerAppFunctionBinding {
166            subscription_id: subscription_id.into(),
167            resource_group_name: resource_group_name.into(),
168            container_app_name: container_app_name.into(),
169            private_url: private_url.into(),
170            public_url: None,
171        })
172    }
173
174    /// Creates an Azure Container Apps function binding with public URL
175    pub fn container_app_with_public_url(
176        subscription_id: impl Into<BindingValue<String>>,
177        resource_group_name: impl Into<BindingValue<String>>,
178        container_app_name: impl Into<BindingValue<String>>,
179        private_url: impl Into<BindingValue<String>>,
180        public_url: impl Into<BindingValue<String>>,
181    ) -> Self {
182        Self::ContainerApp(ContainerAppFunctionBinding {
183            subscription_id: subscription_id.into(),
184            resource_group_name: resource_group_name.into(),
185            container_app_name: container_app_name.into(),
186            private_url: private_url.into(),
187            public_url: Some(public_url.into()),
188        })
189    }
190
191    /// Creates a local function binding
192    pub fn local(function_url: impl Into<BindingValue<String>>) -> Self {
193        Self::Local(LocalFunctionBinding {
194            function_url: function_url.into(),
195        })
196    }
197
198    /// Creates a Kubernetes function binding
199    pub fn kubernetes(
200        name: impl Into<BindingValue<String>>,
201        namespace: impl Into<BindingValue<String>>,
202        service_name: impl Into<BindingValue<String>>,
203        service_port: impl Into<BindingValue<u16>>,
204    ) -> Self {
205        Self::Kubernetes(KubernetesFunctionBinding {
206            name: name.into(),
207            namespace: namespace.into(),
208            service_name: service_name.into(),
209            service_port: service_port.into(),
210            public_url: None,
211        })
212    }
213
214    /// Creates a Kubernetes function binding with public URL
215    pub fn kubernetes_with_public_url(
216        name: impl Into<BindingValue<String>>,
217        namespace: impl Into<BindingValue<String>>,
218        service_name: impl Into<BindingValue<String>>,
219        service_port: impl Into<BindingValue<u16>>,
220        public_url: impl Into<BindingValue<String>>,
221    ) -> Self {
222        Self::Kubernetes(KubernetesFunctionBinding {
223            name: name.into(),
224            namespace: namespace.into(),
225            service_name: service_name.into(),
226            service_port: service_port.into(),
227            public_url: Some(public_url.into()),
228        })
229    }
230
231    /// Gets the public URL if available for any platform
232    pub fn get_public_url(&self) -> Option<&BindingValue<String>> {
233        match self {
234            FunctionBinding::Lambda(binding) => binding.url.as_ref(),
235            FunctionBinding::CloudRun(binding) => binding.public_url.as_ref(),
236            FunctionBinding::ContainerApp(binding) => binding.public_url.as_ref(),
237            FunctionBinding::Kubernetes(binding) => binding.public_url.as_ref(),
238            FunctionBinding::Local(binding) => Some(&binding.function_url),
239        }
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    #[test]
248    fn test_lambda_binding() {
249        let binding = FunctionBinding::lambda("my-function", "us-east-1");
250
251        if let FunctionBinding::Lambda(lambda_binding) = binding {
252            assert_eq!(
253                lambda_binding.function_name,
254                BindingValue::Value("my-function".to_string())
255            );
256            assert_eq!(
257                lambda_binding.region,
258                BindingValue::Value("us-east-1".to_string())
259            );
260            assert!(lambda_binding.url.is_none());
261        } else {
262            panic!("Expected Lambda binding");
263        }
264    }
265
266    #[test]
267    fn test_lambda_binding_with_url() {
268        let binding = FunctionBinding::lambda_with_url(
269            "my-function",
270            "us-east-1",
271            "https://abc123.lambda-url.us-east-1.on.aws/",
272        );
273
274        if let FunctionBinding::Lambda(lambda_binding) = binding {
275            assert_eq!(
276                lambda_binding.function_name,
277                BindingValue::Value("my-function".to_string())
278            );
279            assert_eq!(
280                lambda_binding.region,
281                BindingValue::Value("us-east-1".to_string())
282            );
283            assert_eq!(
284                lambda_binding.url,
285                Some(BindingValue::Value(
286                    "https://abc123.lambda-url.us-east-1.on.aws/".to_string()
287                ))
288            );
289        } else {
290            panic!("Expected Lambda binding");
291        }
292    }
293
294    #[test]
295    fn test_cloud_run_binding() {
296        let binding = FunctionBinding::cloud_run(
297            "my-project",
298            "my-service",
299            "us-central1",
300            "https://my-service-abc123.a.run.app",
301        );
302
303        if let FunctionBinding::CloudRun(cloudrun_binding) = binding {
304            assert_eq!(
305                cloudrun_binding.project_id,
306                BindingValue::Value("my-project".to_string())
307            );
308            assert_eq!(
309                cloudrun_binding.service_name,
310                BindingValue::Value("my-service".to_string())
311            );
312            assert_eq!(
313                cloudrun_binding.location,
314                BindingValue::Value("us-central1".to_string())
315            );
316            assert_eq!(
317                cloudrun_binding.private_url,
318                BindingValue::Value("https://my-service-abc123.a.run.app".to_string())
319            );
320            assert!(cloudrun_binding.public_url.is_none());
321        } else {
322            panic!("Expected CloudRun binding");
323        }
324    }
325
326    #[test]
327    fn test_container_app_binding() {
328        let binding = FunctionBinding::container_app(
329            "sub-123",
330            "my-rg",
331            "my-app",
332            "https://my-app.internal.env.region.azurecontainerapps.io",
333        );
334
335        if let FunctionBinding::ContainerApp(container_app_binding) = binding {
336            assert_eq!(
337                container_app_binding.subscription_id,
338                BindingValue::Value("sub-123".to_string())
339            );
340            assert_eq!(
341                container_app_binding.resource_group_name,
342                BindingValue::Value("my-rg".to_string())
343            );
344            assert_eq!(
345                container_app_binding.container_app_name,
346                BindingValue::Value("my-app".to_string())
347            );
348            assert_eq!(
349                container_app_binding.private_url,
350                BindingValue::Value(
351                    "https://my-app.internal.env.region.azurecontainerapps.io".to_string()
352                )
353            );
354            assert!(container_app_binding.public_url.is_none());
355        } else {
356            panic!("Expected ContainerApp binding");
357        }
358    }
359
360    #[test]
361    fn test_binding_value_expressions() {
362        use serde_json::json;
363
364        let binding = FunctionBinding::Lambda(LambdaFunctionBinding {
365            function_name: BindingValue::Expression(json!({"Fn::Ref": "MyFunction"})),
366            region: BindingValue::Value("us-east-1".to_string()),
367            url: Some(BindingValue::Expression(
368                json!({"Fn::GetAtt": ["MyFunction", "FunctionUrl"]}),
369            )),
370        });
371
372        let serialized = serde_json::to_string(&binding).unwrap();
373        let deserialized: FunctionBinding = serde_json::from_str(&serialized).unwrap();
374        assert_eq!(binding, deserialized);
375    }
376
377    #[test]
378    fn test_get_public_url() {
379        let lambda_binding = FunctionBinding::lambda_with_url(
380            "my-function",
381            "us-east-1",
382            "https://abc123.lambda-url.us-east-1.on.aws/",
383        );
384        assert!(lambda_binding.get_public_url().is_some());
385
386        let lambda_binding_no_url = FunctionBinding::lambda("my-function", "us-east-1");
387        assert!(lambda_binding_no_url.get_public_url().is_none());
388    }
389
390    #[test]
391    fn test_local_binding() {
392        let binding = FunctionBinding::local("http://localhost:3000");
393
394        if let FunctionBinding::Local(local_binding) = binding {
395            assert_eq!(
396                local_binding.function_url,
397                BindingValue::Value("http://localhost:3000".to_string())
398            );
399        } else {
400            panic!("Expected Local binding");
401        }
402    }
403
404    #[test]
405    fn test_local_binding_public_url() {
406        let binding = FunctionBinding::local("http://localhost:3000");
407        let url = binding.get_public_url();
408        assert!(url.is_some());
409        assert_eq!(
410            url.unwrap(),
411            &BindingValue::Value("http://localhost:3000".to_string())
412        );
413    }
414}