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 }),
150 None,
151 )
152 .expect("remote binding should build");
153
154 let mut registry = FunctionRegistry::default();
155 binding.register_functions(&mut registry);
156
157 let definition = registry
158 .get("remote_crm.sync_contact.v1")
159 .expect("remote function should register");
160 assert_eq!(definition.version, 1);
161 assert_eq!(definition.queue, "remote-crm");
162 assert_eq!(definition.retry_policy.max_attempts, 3);
163 assert_eq!(
164 definition.retry_policy.initial_delay,
165 Duration::from_millis(1000)
166 );
167 }
168
169 #[test]
170 fn remote_binding_rejects_invalid_function_name() {
171 let error = RemoteBinding::from_surfaces(
172 RemoteModuleConfig::new("remote-crm", "http://127.0.0.1:4100/lenso/module/v1"),
173 Some(&RuntimeSurface {
174 functions: vec![RuntimeFunctionDeclaration {
175 name: "remote_crm/sync_contact.v1".to_owned(),
176 version: 1,
177 queue: "remote-crm".to_owned(),
178 input_schema: None,
179 retry_policy: None,
180 operation: None,
181 }],
182 schedules: vec![],
183 }),
184 None,
185 )
186 .expect_err("invalid function name should fail");
187
188 assert_eq!(error.code, platform_core::ErrorCode::Validation);
189 }
190
191 #[test]
192 fn remote_binding_registers_declared_event_handlers() {
193 let binding = RemoteBinding::from_surfaces(
194 RemoteModuleConfig::new("remote-crm", "http://127.0.0.1:4100/lenso/module/v1"),
195 None,
196 Some(&EventSurface {
197 handlers: vec![EventHandlerDeclaration {
198 name: "sync_contact_on_user_registered".to_owned(),
199 event_name: "identity.user_registered.v1".to_owned(),
200 operation: None,
201 }],
202 }),
203 )
204 .expect("remote binding should build");
205
206 let mut registry = EventHandlerRegistry::default();
207 binding.register_event_handlers(&mut registry, &EventHandlerRegistrationContext::empty());
208
209 assert_eq!(registry.handler_count("identity.user_registered.v1"), 1);
210 }
211
212 #[test]
213 fn remote_binding_rejects_invalid_event_handler_name() {
214 let error = RemoteBinding::from_surfaces(
215 RemoteModuleConfig::new("remote-crm", "http://127.0.0.1:4100/lenso/module/v1"),
216 None,
217 Some(&EventSurface {
218 handlers: vec![EventHandlerDeclaration {
219 name: "sync/contact".to_owned(),
220 event_name: "identity.user_registered.v1".to_owned(),
221 operation: None,
222 }],
223 }),
224 )
225 .expect_err("invalid event handler name should fail");
226
227 assert_eq!(error.code, platform_core::ErrorCode::Validation);
228 }
229}