Skip to main content

platform_module_remote/
event.rs

1use crate::config::{RemoteModuleConfig, RemoteModuleTransport};
2use crate::protocol::{
3    RemoteEventHandleRequest, RemoteEventHandleResponse, RemoteEventResultAction,
4};
5use crate::response::{ResponseBodyPolicy, decode_json_response_with_policy};
6use crate::validation::validate_path_segment;
7use platform_core::{
8    ActorContext, AppError, AppResult, ClaimedOutboxEvent, CorrelationId, ErrorCode, EventHandler,
9    trace_context_from_headers,
10};
11use platform_runtime::{EnqueueFunctionRequest, FunctionRegistry, RuntimeClient};
12use std::collections::BTreeSet;
13use std::sync::Arc;
14use std::time::Duration;
15
16const MAX_EVENT_HANDLER_RESPONSE_BYTES: u64 = 1024 * 1024;
17const MAX_EVENT_HANDLER_RESULT_ACTIONS: usize = 1;
18
19#[derive(Debug, Clone)]
20pub struct RemoteEventHandler {
21    client: reqwest::Client,
22    config: RemoteModuleConfig,
23    handler_name: String,
24    event_name: String,
25    action_runner: Arc<dyn RemoteEventActionRunner>,
26}
27
28impl RemoteEventHandler {
29    pub fn new(
30        config: RemoteModuleConfig,
31        handler_name: impl Into<String>,
32        event_name: impl Into<String>,
33    ) -> AppResult<Self> {
34        let handler_name = handler_name.into();
35        let event_name = event_name.into();
36        validate_event_handler_name(&handler_name)?;
37        validate_event_name(&event_name)?;
38        let client = reqwest::Client::builder()
39            .timeout(Duration::from_millis(config.timeout_ms))
40            .build()
41            .map_err(|error| {
42                AppError::new(
43                    ErrorCode::Internal,
44                    format!("failed to build remote event handler client: {error}"),
45                )
46            })?;
47        Ok(Self {
48            client,
49            config,
50            handler_name,
51            event_name,
52            action_runner: Arc::new(RejectingRemoteEventActionRunner),
53        })
54    }
55
56    #[must_use]
57    pub fn with_host_action_runner(mut self, action_runner: RemoteEventHostActionRunner) -> Self {
58        self.action_runner = Arc::new(action_runner);
59        self
60    }
61
62    pub async fn invoke(&self, event: &ClaimedOutboxEvent) -> AppResult<()> {
63        let request_body = RemoteEventHandleRequest {
64            request_id: format!("{}:{}", event.id, self.handler_name),
65            outbox_event_id: event.id.clone(),
66            handler_name: self.handler_name.clone(),
67            event_name: event.event_name.clone(),
68            event_version: event.event_version,
69            source_module: event.source_module.clone(),
70            aggregate_type: event.aggregate_type.clone(),
71            aggregate_id: event.aggregate_id.clone(),
72            correlation_id: event.correlation_id.clone(),
73            causation_id: event.causation_id.clone(),
74            occurred_at: event.occurred_at.to_rfc3339(),
75            actor: actor_from_event(event),
76            trace: trace_context_from_headers(&event.headers),
77            payload: event.payload.clone(),
78            headers: event.headers.clone(),
79        };
80        if self.config.transport == RemoteModuleTransport::Grpc {
81            let response = crate::grpc::handle_event(&self.config, &request_body).await?;
82            self.action_runner
83                .run_actions(event, &self.handler_name, response.actions)
84                .await?;
85            return Ok(());
86        }
87
88        let mut request = self.client.post(self.invoke_url()).json(&request_body);
89        if let Some(token) = &self.config.auth_token {
90            request = request.bearer_auth(token);
91        }
92
93        let response = request.send().await.map_err(|error| {
94            AppError::new(
95                ErrorCode::ExternalDependency,
96                format!(
97                    "remote event handler {} request failed: {error}",
98                    self.handler_name
99                ),
100            )
101            .retryable()
102        })?;
103
104        let response = decode_json_response_with_policy::<RemoteEventHandleResponse>(
105            response,
106            "event handler invoke",
107            false,
108            ResponseBodyPolicy {
109                max_bytes: Some(MAX_EVENT_HANDLER_RESPONSE_BYTES),
110                require_json_content_type: true,
111                allow_empty_success: true,
112            },
113        )
114        .await?;
115        if let Some(response) = response {
116            self.action_runner
117                .run_actions(event, &self.handler_name, response.actions)
118                .await?;
119        }
120        Ok(())
121    }
122
123    fn invoke_url(&self) -> String {
124        format!(
125            "{}/events/handlers/{}/invoke",
126            self.config.base_url, self.handler_name
127        )
128    }
129}
130
131#[async_trait::async_trait]
132impl EventHandler for RemoteEventHandler {
133    fn event_name(&self) -> &str {
134        &self.event_name
135    }
136
137    async fn handle(&self, event: &ClaimedOutboxEvent) -> AppResult<()> {
138        self.invoke(event).await
139    }
140}
141
142#[async_trait::async_trait]
143trait RemoteEventActionRunner: std::fmt::Debug + Send + Sync {
144    async fn run_actions(
145        &self,
146        event: &ClaimedOutboxEvent,
147        handler_name: &str,
148        actions: Vec<RemoteEventResultAction>,
149    ) -> AppResult<()>;
150}
151
152#[derive(Debug, Clone)]
153pub struct RemoteEventHostActionRunner {
154    runtime: RuntimeClient,
155    function_registry: Arc<FunctionRegistry>,
156    allowed_function_names: BTreeSet<String>,
157}
158
159impl RemoteEventHostActionRunner {
160    #[must_use]
161    pub fn new(
162        runtime: RuntimeClient,
163        function_registry: Arc<FunctionRegistry>,
164        allowed_function_names: impl IntoIterator<Item = String>,
165    ) -> Self {
166        Self {
167            runtime,
168            function_registry,
169            allowed_function_names: allowed_function_names.into_iter().collect(),
170        }
171    }
172}
173
174#[async_trait::async_trait]
175impl RemoteEventActionRunner for RemoteEventHostActionRunner {
176    async fn run_actions(
177        &self,
178        event: &ClaimedOutboxEvent,
179        handler_name: &str,
180        actions: Vec<RemoteEventResultAction>,
181    ) -> AppResult<()> {
182        if actions.len() > MAX_EVENT_HANDLER_RESULT_ACTIONS {
183            return Err(AppError::new(
184                ErrorCode::Validation,
185                format!(
186                    "remote event handler {handler_name} returned too many result actions: {}",
187                    actions.len()
188                ),
189            ));
190        }
191
192        for (index, action) in actions.into_iter().enumerate() {
193            match action {
194                RemoteEventResultAction::EnqueueFunction {
195                    function_name,
196                    input,
197                } => {
198                    self.enqueue_function(event, handler_name, index, function_name, input)
199                        .await?;
200                }
201            }
202        }
203
204        Ok(())
205    }
206}
207
208impl RemoteEventHostActionRunner {
209    async fn enqueue_function(
210        &self,
211        event: &ClaimedOutboxEvent,
212        handler_name: &str,
213        action_index: usize,
214        function_name: String,
215        input: serde_json::Value,
216    ) -> AppResult<()> {
217        if !self.allowed_function_names.contains(&function_name) {
218            return Err(AppError::new(
219                ErrorCode::Validation,
220                format!(
221                    "remote event handler {handler_name} requested runtime function {function_name} that is not declared by its module"
222                ),
223            ));
224        }
225
226        let definition = self.function_registry.get(&function_name).ok_or_else(|| {
227            AppError::new(
228                ErrorCode::Internal,
229                format!("remote event handler {handler_name} requested unregistered runtime function {function_name}"),
230            )
231        })?;
232        let run_id = self
233            .runtime
234            .enqueue_function(EnqueueFunctionRequest {
235                function_name: function_name.clone(),
236                input_json: input,
237                correlation_id: CorrelationId::new(event.correlation_id.clone()),
238                actor: actor_from_event(event),
239                tenant_id: tenant_from_event(event),
240                tenancy_mode: platform_runtime::FunctionTenancyMode::Optional,
241                trace: trace_context_from_headers(&event.headers),
242                causation_id: Some(format!(
243                    "remote_event_handler:{}:{handler_name}:{action_index}",
244                    event.id
245                )),
246                max_attempts: Some(runtime_max_attempts_for_enqueue(
247                    definition.retry_policy.max_attempts,
248                )),
249            })
250            .await?;
251
252        tracing::info!(
253            outbox_event_id = %event.id,
254            handler_name = %handler_name,
255            function_name = %function_name,
256            function_run_id = %run_id,
257            "remote event handler enqueued runtime function"
258        );
259
260        Ok(())
261    }
262}
263
264fn tenant_from_event(event: &ClaimedOutboxEvent) -> Option<platform_core::TenantId> {
265    event
266        .headers
267        .get("tenant_id")
268        .cloned()
269        .and_then(|value| serde_json::from_value(value).ok())
270}
271
272#[derive(Debug)]
273struct RejectingRemoteEventActionRunner;
274
275#[async_trait::async_trait]
276impl RemoteEventActionRunner for RejectingRemoteEventActionRunner {
277    async fn run_actions(
278        &self,
279        _event: &ClaimedOutboxEvent,
280        handler_name: &str,
281        actions: Vec<RemoteEventResultAction>,
282    ) -> AppResult<()> {
283        if actions.is_empty() {
284            return Ok(());
285        }
286
287        Err(AppError::new(
288            ErrorCode::Validation,
289            format!(
290                "remote event handler {handler_name} returned result actions but host actions are not configured"
291            ),
292        ))
293    }
294}
295
296fn actor_from_event(event: &ClaimedOutboxEvent) -> ActorContext {
297    event
298        .headers
299        .get("actor")
300        .cloned()
301        .and_then(|actor| serde_json::from_value(actor).ok())
302        .unwrap_or_default()
303}
304
305pub(crate) fn validate_event_handler_name(value: &str) -> AppResult<()> {
306    validate_path_segment(
307        value,
308        "remote event handler name must be a stable path segment",
309    )
310}
311
312pub(crate) fn validate_event_name(value: &str) -> AppResult<()> {
313    validate_path_segment(value, "remote event name must be a stable path segment")
314}
315
316fn runtime_max_attempts_for_enqueue(max_attempts: u32) -> i32 {
317    i32::try_from(max_attempts).unwrap_or(i32::MAX)
318}