Skip to main content

alien_bindings/providers/worker/
local.rs

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