Skip to main content

platform_module_remote/
binding.rs

1use crate::config::RemoteModuleConfig;
2use crate::event::{
3    RemoteEventHandler, RemoteEventHostActionRunner, validate_event_handler_name,
4    validate_event_name,
5};
6use crate::runtime::{RemoteRuntimeFunction, validate_function_name};
7use platform_core::{AppResult, EventHandlerRegistry};
8use platform_module::{
9    EventHandlerRegistrationContext, EventSurface, ModuleBinding, RuntimeSurface,
10};
11use platform_runtime::{FunctionDefinition, FunctionRegistry, RetryPolicy};
12use std::sync::Arc;
13use std::time::Duration;
14
15#[derive(Debug, Clone, Default)]
16pub struct RemoteBinding {
17    config: Option<RemoteModuleConfig>,
18    functions: Vec<FunctionDefinition>,
19    event_handlers: Vec<RemoteEventHandlerRegistration>,
20}
21
22#[derive(Debug, Clone)]
23struct RemoteEventHandlerRegistration {
24    name: String,
25    event_name: String,
26}
27
28impl RemoteBinding {
29    pub fn from_surfaces(
30        config: RemoteModuleConfig,
31        runtime: Option<&RuntimeSurface>,
32        events: Option<&EventSurface>,
33    ) -> AppResult<Self> {
34        let functions = runtime
35            .into_iter()
36            .flat_map(|surface| surface.functions.iter())
37            .map(|declaration| {
38                validate_function_name(&declaration.name)?;
39                Ok(FunctionDefinition {
40                    name: declaration.name.clone(),
41                    version: declaration.version,
42                    queue: declaration.queue.clone(),
43                    retry_policy: declaration
44                        .retry_policy
45                        .as_ref()
46                        .map(|policy| {
47                            RetryPolicy::fixed(
48                                policy.max_attempts,
49                                Duration::from_millis(policy.initial_delay_ms),
50                            )
51                        })
52                        .unwrap_or_default(),
53                    handler: Arc::new(RemoteRuntimeFunction::new(
54                        config.clone(),
55                        declaration.name.clone(),
56                    )?),
57                })
58            })
59            .collect::<AppResult<Vec<_>>>()?;
60
61        let event_handlers = events
62            .into_iter()
63            .flat_map(|surface| surface.handlers.iter())
64            .map(|declaration| {
65                validate_event_handler_name(&declaration.name)?;
66                validate_event_name(&declaration.event_name)?;
67                Ok(RemoteEventHandlerRegistration {
68                    name: declaration.name.clone(),
69                    event_name: declaration.event_name.clone(),
70                })
71            })
72            .collect::<AppResult<Vec<_>>>()?;
73
74        Ok(Self {
75            config: Some(config),
76            functions,
77            event_handlers,
78        })
79    }
80}
81
82impl ModuleBinding for RemoteBinding {
83    fn register_functions(&self, registry: &mut FunctionRegistry) {
84        for function in self.functions.iter().cloned() {
85            registry.register(function);
86        }
87    }
88
89    fn register_event_handlers(
90        &self,
91        registry: &mut EventHandlerRegistry,
92        context: &EventHandlerRegistrationContext,
93    ) {
94        let Some(config) = &self.config else {
95            return;
96        };
97        let allowed_function_names = self
98            .functions
99            .iter()
100            .map(|function| function.name.clone())
101            .collect::<Vec<_>>();
102
103        for declaration in &self.event_handlers {
104            let mut handler = RemoteEventHandler::new(
105                config.clone(),
106                declaration.name.clone(),
107                declaration.event_name.clone(),
108            )
109            .expect("remote event handler declaration was validated");
110
111            if let Some(runtime) = context.runtime() {
112                handler = handler.with_host_action_runner(RemoteEventHostActionRunner::new(
113                    runtime.runtime_client.clone(),
114                    runtime.function_registry.clone(),
115                    allowed_function_names.clone(),
116                ));
117            }
118
119            registry.register(std::sync::Arc::new(handler));
120        }
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127    use platform_module::{
128        EventHandlerDeclaration, EventSurface, RuntimeFunctionDeclaration,
129        RuntimeRetryPolicyDeclaration, RuntimeSurface,
130    };
131
132    #[test]
133    fn remote_binding_registers_declared_functions() {
134        let binding = RemoteBinding::from_surfaces(
135            RemoteModuleConfig::new("remote-crm", "http://127.0.0.1:4100/lenso/module/v1"),
136            Some(&RuntimeSurface {
137                functions: vec![RuntimeFunctionDeclaration {
138                    name: "remote_crm.sync_contact.v1".to_owned(),
139                    version: 1,
140                    queue: "remote-crm".to_owned(),
141                    input_schema: Some("remote_crm.sync_contact.v1".to_owned()),
142                    retry_policy: Some(RuntimeRetryPolicyDeclaration {
143                        max_attempts: 3,
144                        initial_delay_ms: 1000,
145                    }),
146                    operation: None,
147                }],
148                schedules: vec![],
149                workflows: vec![],
150            }),
151            None,
152        )
153        .expect("remote binding should build");
154
155        let mut registry = FunctionRegistry::default();
156        binding.register_functions(&mut registry);
157
158        let definition = registry
159            .get("remote_crm.sync_contact.v1")
160            .expect("remote function should register");
161        assert_eq!(definition.version, 1);
162        assert_eq!(definition.queue, "remote-crm");
163        assert_eq!(definition.retry_policy.max_attempts, 3);
164        assert_eq!(
165            definition.retry_policy.initial_delay,
166            Duration::from_millis(1000)
167        );
168    }
169
170    #[test]
171    fn remote_binding_rejects_invalid_function_name() {
172        let error = RemoteBinding::from_surfaces(
173            RemoteModuleConfig::new("remote-crm", "http://127.0.0.1:4100/lenso/module/v1"),
174            Some(&RuntimeSurface {
175                functions: vec![RuntimeFunctionDeclaration {
176                    name: "remote_crm/sync_contact.v1".to_owned(),
177                    version: 1,
178                    queue: "remote-crm".to_owned(),
179                    input_schema: None,
180                    retry_policy: None,
181                    operation: None,
182                }],
183                schedules: vec![],
184                workflows: vec![],
185            }),
186            None,
187        )
188        .expect_err("invalid function name should fail");
189
190        assert_eq!(error.code, platform_core::ErrorCode::Validation);
191    }
192
193    #[test]
194    fn remote_binding_registers_declared_event_handlers() {
195        let binding = RemoteBinding::from_surfaces(
196            RemoteModuleConfig::new("remote-crm", "http://127.0.0.1:4100/lenso/module/v1"),
197            None,
198            Some(&EventSurface {
199                handlers: vec![EventHandlerDeclaration {
200                    name: "sync_contact_on_user_registered".to_owned(),
201                    event_name: "identity.user_registered.v1".to_owned(),
202                    operation: None,
203                }],
204            }),
205        )
206        .expect("remote binding should build");
207
208        let mut registry = EventHandlerRegistry::default();
209        binding.register_event_handlers(&mut registry, &EventHandlerRegistrationContext::empty());
210
211        assert_eq!(registry.handler_count("identity.user_registered.v1"), 1);
212    }
213
214    #[test]
215    fn remote_binding_rejects_invalid_event_handler_name() {
216        let error = RemoteBinding::from_surfaces(
217            RemoteModuleConfig::new("remote-crm", "http://127.0.0.1:4100/lenso/module/v1"),
218            None,
219            Some(&EventSurface {
220                handlers: vec![EventHandlerDeclaration {
221                    name: "sync/contact".to_owned(),
222                    event_name: "identity.user_registered.v1".to_owned(),
223                    operation: None,
224                }],
225            }),
226        )
227        .expect_err("invalid event handler name should fail");
228
229        assert_eq!(error.code, platform_core::ErrorCode::Validation);
230    }
231}