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