1use std::{collections::BTreeMap, rc::Rc};
4
5use lenso_app_plan::{ExecutionClassId, ResolvedAppPlan};
6use lenso_kernel::{
7 ModuleLifecycle, NativeEndpointSet, NativeEventEndpoint, NativeExecutionAdapter,
8 NativeRequestEndpoint, NativeStreamEndpoint, NoopModuleLifecycle, PreparedBinding,
9 PreparedEventBinding, PreparedNativeApp, PreparedNativeModule, PreparedStreamBinding,
10 RuntimeFailure,
11};
12
13#[derive(Debug)]
15pub struct NativeModuleInstance {
16 endpoints: NativeEndpointSet,
17 lifecycle: Rc<dyn ModuleLifecycle>,
18}
19
20impl NativeModuleInstance {
21 pub fn new(endpoints: Vec<Rc<dyn NativeRequestEndpoint>>) -> Self {
23 Self::with_lifecycle(endpoints, NoopModuleLifecycle)
24 }
25
26 pub fn with_lifecycle(
28 endpoints: Vec<Rc<dyn NativeRequestEndpoint>>,
29 lifecycle: impl ModuleLifecycle,
30 ) -> Self {
31 Self {
32 endpoints: NativeEndpointSet::new(endpoints, Vec::new(), Vec::new()),
33 lifecycle: Rc::new(lifecycle),
34 }
35 }
36
37 pub fn with_endpoints(
39 endpoints: Vec<Rc<dyn NativeRequestEndpoint>>,
40 stream_endpoints: Vec<Rc<dyn NativeStreamEndpoint>>,
41 lifecycle: impl ModuleLifecycle,
42 ) -> Self {
43 Self {
44 endpoints: NativeEndpointSet::new(endpoints, stream_endpoints, Vec::new()),
45 lifecycle: Rc::new(lifecycle),
46 }
47 }
48
49 pub fn with_stream_endpoints(
51 stream_endpoints: Vec<Rc<dyn NativeStreamEndpoint>>,
52 lifecycle: impl ModuleLifecycle,
53 ) -> Self {
54 Self::with_endpoints(Vec::new(), stream_endpoints, lifecycle)
55 }
56
57 pub fn with_event_endpoints(
59 event_endpoints: Vec<Rc<dyn NativeEventEndpoint>>,
60 lifecycle: impl ModuleLifecycle,
61 ) -> Self {
62 Self {
63 endpoints: NativeEndpointSet::new(Vec::new(), Vec::new(), event_endpoints),
64 lifecycle: Rc::new(lifecycle),
65 }
66 }
67
68 pub fn with_all_endpoints(
70 endpoints: Vec<Rc<dyn NativeRequestEndpoint>>,
71 stream_endpoints: Vec<Rc<dyn NativeStreamEndpoint>>,
72 event_endpoints: Vec<Rc<dyn NativeEventEndpoint>>,
73 lifecycle: impl ModuleLifecycle,
74 ) -> Self {
75 Self {
76 endpoints: NativeEndpointSet::new(endpoints, stream_endpoints, event_endpoints),
77 lifecycle: Rc::new(lifecycle),
78 }
79 }
80
81 pub fn lifecycle(&self) -> Rc<dyn ModuleLifecycle> {
83 self.lifecycle.clone()
84 }
85
86 pub fn endpoints(&self) -> &[Rc<dyn NativeRequestEndpoint>] {
88 self.endpoints.request()
89 }
90
91 pub fn stream_endpoints(&self) -> &[Rc<dyn NativeStreamEndpoint>] {
93 self.endpoints.stream()
94 }
95
96 pub fn event_endpoints(&self) -> &[Rc<dyn NativeEventEndpoint>] {
98 self.endpoints.event()
99 }
100}
101
102impl Default for NativeModuleInstance {
103 fn default() -> Self {
104 Self::new(Vec::new())
105 }
106}
107
108pub trait NativeModuleFactory: std::fmt::Debug + 'static {
110 fn package_id(&self) -> &'static str;
112 fn package_version(&self) -> &'static str {
114 ""
115 }
116 fn instantiate(
118 &self,
119 context: NativeModuleFactoryContext<'_>,
120 ) -> Result<NativeModuleInstance, RuntimeFailure>;
121}
122
123#[derive(Clone, Copy, Debug)]
125pub struct NativeModuleFactoryContext<'a> {
126 instance_key: &'a str,
127 entrypoint: &'a str,
128 configuration: &'a str,
129}
130
131impl<'a> NativeModuleFactoryContext<'a> {
132 fn from_plan(instance: &'a lenso_app_plan::ModuleInstancePlan) -> Self {
133 Self {
134 instance_key: instance.instance_key(),
135 entrypoint: instance.entrypoint(),
136 configuration: instance.configuration(),
137 }
138 }
139
140 pub const fn instance_key(self) -> &'a str {
142 self.instance_key
143 }
144
145 pub const fn entrypoint(self) -> &'a str {
147 self.entrypoint
148 }
149
150 pub const fn configuration(self) -> &'a str {
152 self.configuration
153 }
154}
155
156#[derive(Debug, Default)]
158pub struct NativeModuleRegistry {
159 factories: Vec<Rc<dyn NativeModuleFactory>>,
160}
161
162type NativeInstances = BTreeMap<String, NativeModuleInstance>;
163type PreparedGenerations = BTreeMap<String, PreparedNativeModule>;
164type NativeBindings = (
165 Vec<PreparedBinding>,
166 Vec<PreparedStreamBinding>,
167 Vec<PreparedEventBinding>,
168);
169
170fn factory_matches(
171 factory: &dyn NativeModuleFactory,
172 expected: &lenso_app_plan::ModuleInstancePlan,
173) -> bool {
174 factory.package_id() == expected.package_id()
175 && (expected.package_revision().is_empty()
176 || factory.package_version() == expected.package_revision())
177}
178
179impl NativeModuleRegistry {
180 pub fn new() -> Self {
182 Self::default()
183 }
184 #[must_use]
186 pub fn with_factory(mut self, factory: impl NativeModuleFactory) -> Self {
187 self.factories.push(Rc::new(factory));
188 self
189 }
190
191 fn prepare_instances(
192 &self,
193 plan: &ResolvedAppPlan,
194 ) -> Result<(NativeInstances, PreparedGenerations), RuntimeFailure> {
195 let mut instances = BTreeMap::new();
196 let mut generations = BTreeMap::new();
197 for expected in plan
198 .module_instances()
199 .iter()
200 .filter(|instance| instance.execution_class() == &ExecutionClassId::native_rust())
201 {
202 let matching_factories: Vec<_> = self
203 .factories
204 .iter()
205 .filter(|factory| factory_matches(factory.as_ref(), expected))
206 .collect();
207 let factory = match matching_factories.as_slice() {
208 [] => {
209 return Err(RuntimeFailure::MissingModuleFactory {
210 instance: expected.instance_key().to_owned(),
211 package_id: expected.package_id().to_owned(),
212 });
213 }
214 [factory] => *factory,
215 _ => {
216 return invalid(format!(
217 "multiple statically linked factories declare package `{}`",
218 expected.package_id()
219 ));
220 }
221 };
222 let generation =
223 factory.instantiate(NativeModuleFactoryContext::from_plan(expected))?;
224 generations.insert(
225 expected.instance_key().to_owned(),
226 PreparedNativeModule::with_endpoint_set_lifecycle(
227 generation.endpoints.clone(),
228 generation.lifecycle(),
229 ),
230 );
231 if instances
232 .insert(expected.instance_key().to_owned(), generation)
233 .is_some()
234 {
235 return invalid(format!(
236 "duplicate Module Instance `{}`",
237 expected.instance_key()
238 ));
239 }
240 }
241 Ok((instances, generations))
242 }
243}
244
245impl NativeExecutionAdapter for NativeModuleRegistry {
246 fn prepare(&self, plan: &ResolvedAppPlan) -> Result<PreparedNativeApp, RuntimeFailure> {
247 plan.validate()
248 .map_err(|error| RuntimeFailure::InvalidResolvedPlan {
249 detail: error.to_string(),
250 })?;
251
252 let (instances, generations) = self.prepare_instances(plan)?;
253 let (bindings, stream_bindings, event_bindings) = prepare_bindings(plan, &instances)?;
254 Ok(PreparedNativeApp::new(bindings, generations)
255 .with_stream_bindings(stream_bindings)
256 .with_event_bindings(event_bindings))
257 }
258
259 fn recreate(
260 &self,
261 plan: &ResolvedAppPlan,
262 instance_key: &str,
263 ) -> Result<PreparedNativeModule, RuntimeFailure> {
264 let expected = plan
265 .module_instances()
266 .iter()
267 .find(|instance| instance.instance_key() == instance_key)
268 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
269 detail: format!("unknown Module Instance `{instance_key}`"),
270 })?;
271 let matching_factories: Vec<_> = self
272 .factories
273 .iter()
274 .filter(|factory| factory_matches(factory.as_ref(), expected))
275 .collect();
276 let factory = match matching_factories.as_slice() {
277 [] => {
278 return Err(RuntimeFailure::MissingModuleFactory {
279 instance: expected.instance_key().to_owned(),
280 package_id: expected.package_id().to_owned(),
281 });
282 }
283 [factory] => *factory,
284 _ => {
285 return invalid(format!(
286 "multiple statically linked factories declare package `{}`",
287 expected.package_id()
288 ));
289 }
290 };
291 let generation = factory.instantiate(NativeModuleFactoryContext::from_plan(expected))?;
292 Ok(PreparedNativeModule::with_endpoint_set_lifecycle(
293 generation.endpoints.clone(),
294 generation.lifecycle(),
295 ))
296 }
297}
298
299fn prepare_bindings(
300 plan: &ResolvedAppPlan,
301 instances: &NativeInstances,
302) -> Result<NativeBindings, RuntimeFailure> {
303 let mut bindings = Vec::new();
304 let mut stream_bindings = Vec::new();
305 let mut event_bindings = Vec::new();
306 for binding in plan.capability_bindings() {
307 if !instances.contains_key(binding.provider_instance()) {
308 continue;
309 }
310 let provider = plan
311 .module_instance(binding.provider_instance())
312 .expect("validated binding provider should exist");
313 let descriptor = provider
314 .provided_capabilities()
315 .iter()
316 .find(|descriptor| descriptor.capability_id() == binding.capability_id())
317 .expect("validated binding descriptor should exist");
318 if !descriptor.request_operations().is_empty() {
319 let endpoint = instances
320 .get(binding.provider_instance())
321 .and_then(|instance| {
322 instance.endpoints.request().iter().find(|endpoint| {
323 endpoint.capability_id() == binding.capability_id()
324 && endpoint.descriptor_version() == binding.descriptor_version()
325 })
326 })
327 .cloned()
328 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
329 detail: format!(
330 "Capability `{}` version `{}` has no request endpoint on provider `{}`",
331 binding.capability_id(),
332 binding.descriptor_version(),
333 binding.provider_instance()
334 ),
335 })?;
336 bindings.push(PreparedBinding::new(
337 binding.consumer_instance(),
338 binding.provider_instance(),
339 endpoint,
340 ));
341 }
342 if !descriptor.stream_operations().is_empty() {
343 let endpoint = instances
344 .get(binding.provider_instance())
345 .and_then(|instance| {
346 instance.endpoints.stream().iter().find(|endpoint| {
347 endpoint.capability_id() == binding.capability_id()
348 && endpoint.descriptor_version() == binding.descriptor_version()
349 })
350 })
351 .cloned()
352 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
353 detail: format!(
354 "Capability `{}` version `{}` has no stream endpoint on provider `{}`",
355 binding.capability_id(),
356 binding.descriptor_version(),
357 binding.provider_instance()
358 ),
359 })?;
360 stream_bindings.push(PreparedStreamBinding::new(
361 binding.consumer_instance(),
362 binding.provider_instance(),
363 endpoint,
364 ));
365 }
366 if !descriptor.event_operations().is_empty() {
367 let endpoint = instances
368 .get(binding.provider_instance())
369 .and_then(|instance| {
370 instance.endpoints.event().iter().find(|endpoint| {
371 endpoint.capability_id() == binding.capability_id()
372 && endpoint.descriptor_version() == binding.descriptor_version()
373 })
374 })
375 .cloned()
376 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
377 detail: format!(
378 "Capability `{}` version `{}` has no Event endpoint on provider `{}`",
379 binding.capability_id(),
380 binding.descriptor_version(),
381 binding.provider_instance()
382 ),
383 })?;
384 event_bindings.push(PreparedEventBinding::new(
385 binding.consumer_instance(),
386 binding.provider_instance(),
387 endpoint,
388 ));
389 }
390 }
391 Ok((bindings, stream_bindings, event_bindings))
392}
393
394fn invalid<T>(detail: String) -> Result<T, RuntimeFailure> {
395 Err(RuntimeFailure::InvalidResolvedPlan { detail })
396}