1use std::time::Duration;
2
3use super::{
4 CancellationToken, EventCapability, InvocationContext, LocalBoxFuture,
5 ModuleEventDependencyHandle, NativeAppRuntime, NativeEndpointBinding, NativeEventHandle,
6 NativeRequestEndpoint, NativeRequestHandle, NativeStreamEndpointBinding, NativeStreamHandle,
7 Rc, RefCell, StreamCapability, Weak,
8};
9
10pub trait RequestCapability: 'static {
11 type Request: 'static;
13 type Response: 'static;
15 type DomainError: 'static;
17 const ID: &'static str;
19 const DESCRIPTOR_VERSION: &'static str;
21
22 #[doc(hidden)]
27 fn invoke_native(
28 endpoint: &dyn NativeRequestEndpoint,
29 operation: &str,
30 request: Self::Request,
31 context: InvocationContext,
32 ) -> NativeRequestFuture<Self>
33 where
34 Self: Sized,
35 {
36 invoke_typed_or_erased_native_request::<Self>(endpoint, operation, request, context)
37 }
38}
39
40#[doc(hidden)]
42pub type NativeRequestFuture<C> = LocalBoxFuture<
43 'static,
44 Result<
45 Result<<C as RequestCapability>::Response, <C as RequestCapability>::DomainError>,
46 RuntimeFailure,
47 >,
48>;
49
50type TypedNativeRequestFn<C> =
51 dyn Fn(&str, <C as RequestCapability>::Request, InvocationContext) -> NativeRequestFuture<C>;
52
53#[doc(hidden)]
59pub struct TypedNativeRequestEndpoint<C: RequestCapability> {
60 invoke: Rc<TypedNativeRequestFn<C>>,
61}
62
63impl<C: RequestCapability> TypedNativeRequestEndpoint<C> {
64 pub fn new(
66 invoke: impl Fn(&str, C::Request, InvocationContext) -> NativeRequestFuture<C> + 'static,
67 ) -> Self {
68 Self {
69 invoke: Rc::new(invoke),
70 }
71 }
72
73 pub fn invoke(
75 &self,
76 operation: &str,
77 request: C::Request,
78 context: InvocationContext,
79 ) -> NativeRequestFuture<C> {
80 (self.invoke)(operation, request, context)
81 }
82}
83
84impl<C: RequestCapability> std::fmt::Debug for TypedNativeRequestEndpoint<C> {
85 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 formatter
87 .debug_struct("TypedNativeRequestEndpoint")
88 .field("capability", &C::ID)
89 .finish_non_exhaustive()
90 }
91}
92
93#[doc(hidden)]
95pub fn invoke_typed_or_erased_native_request<C: RequestCapability>(
96 endpoint: &dyn NativeRequestEndpoint,
97 operation: &str,
98 request: C::Request,
99 context: InvocationContext,
100) -> NativeRequestFuture<C> {
101 if let Some(endpoint) = endpoint
102 .typed_endpoint()
103 .and_then(|endpoint| endpoint.downcast_ref::<TypedNativeRequestEndpoint<C>>())
104 {
105 endpoint.invoke(operation, request, context)
106 } else {
107 invoke_erased_native_request::<C>(endpoint, operation, request, context)
108 }
109}
110
111#[doc(hidden)]
113pub fn invoke_erased_native_request<C: RequestCapability>(
114 endpoint: &dyn NativeRequestEndpoint,
115 operation: &str,
116 request: C::Request,
117 context: InvocationContext,
118) -> NativeRequestFuture<C> {
119 let invocation = endpoint.invoke(operation, Box::new(request), context);
120 Box::pin(async move {
121 match invocation.await? {
122 Ok(value) => value
123 .downcast::<C::Response>()
124 .map(|value| Ok(*value))
125 .map_err(|_| RuntimeFailure::ProtocolViolation { capability: C::ID }),
126 Err(value) => value
127 .downcast::<C::DomainError>()
128 .map(|value| Err(*value))
129 .map_err(|_| RuntimeFailure::ProtocolViolation { capability: C::ID }),
130 }
131 })
132}
133
134pub type RequestId = u64;
136
137#[derive(Clone, Debug, Eq, PartialEq)]
139pub enum RuntimeFailure {
140 Unavailable { capability: &'static str },
142 UnknownOperation {
144 capability: &'static str,
145 operation: String,
146 },
147 AmbiguousBinding {
149 capability: &'static str,
150 providers: usize,
151 },
152 ProtocolViolation { capability: &'static str },
154 MissingModuleFactory {
156 instance: String,
157 package_id: String,
158 },
159 UnavailableExecutionClass {
161 instance_key: String,
162 execution_class: String,
163 },
164 InvalidResolvedPlan { detail: String },
166 AdmissionClosed,
168 ResourceExhausted {
170 capability: &'static str,
171 operation: String,
172 },
173 DeadlineExceeded { request_id: RequestId },
175 Cancelled { request_id: RequestId },
177 Internal { detail: String },
179 ModuleFailure { detail: String },
181 ModuleRestartExhausted { instance: String, attempts: usize },
183}
184
185#[derive(Clone, Copy, Debug, Eq, PartialEq)]
187pub enum ModuleLifecyclePhase {
188 Prepare,
190 Activate,
192 Ready,
194 Deactivate,
196}
197
198#[cfg(test)]
199mod typed_endpoint_tests {
200 use std::any::Any;
201
202 use super::*;
203
204 #[derive(Debug)]
205 struct Echo;
206
207 impl RequestCapability for Echo {
208 type Request = u64;
209 type Response = u64;
210 type DomainError = ();
211 const ID: &'static str = "test.echo@1";
212 const DESCRIPTOR_VERSION: &'static str = "1.0.0";
213 }
214
215 #[derive(Debug)]
216 struct Endpoint {
217 typed: TypedNativeRequestEndpoint<Echo>,
218 }
219
220 impl NativeRequestEndpoint for Endpoint {
221 fn capability_id(&self) -> &'static str {
222 Echo::ID
223 }
224
225 fn descriptor_version(&self) -> &'static str {
226 Echo::DESCRIPTOR_VERSION
227 }
228
229 fn operations(&self) -> &'static [&'static str] {
230 &["echo"]
231 }
232
233 fn typed_endpoint(&self) -> Option<&dyn Any> {
234 Some(&self.typed)
235 }
236
237 fn invoke(
238 &self,
239 _operation: &str,
240 _request: Box<dyn Any>,
241 _context: InvocationContext,
242 ) -> LocalBoxFuture<'static, Result<crate::ErasedDomainResult, RuntimeFailure>> {
243 panic!("typed dispatch must not call the erased endpoint")
244 }
245 }
246
247 #[test]
248 fn default_dispatch_uses_runtime_typed_endpoint() {
249 let endpoint = Endpoint {
250 typed: TypedNativeRequestEndpoint::new(|_, request, _| {
251 Box::pin(futures::future::ready(Ok(Ok(request + 1))))
252 }),
253 };
254 let context = InvocationContext::new(1, None, CancellationToken::new());
255
256 let result =
257 futures::executor::block_on(Echo::invoke_native(&endpoint, "echo", 41, context));
258
259 assert_eq!(result, Ok(Ok(42)));
260 }
261}
262
263#[derive(Clone, Debug)]
265pub struct ModuleDependency {
266 pub(super) capability_id: String,
267 pub(super) provider_instance: String,
268 pub(super) provider_order: usize,
269 pub(super) handle: Option<ModuleDependencyHandle>,
270 pub(super) stream_handle: Option<ModuleStreamDependencyHandle>,
271 pub(super) event_handle: Option<ModuleEventDependencyHandle>,
272}
273
274impl ModuleDependency {
275 pub(super) fn new(
276 capability_id: impl Into<String>,
277 provider_instance: impl Into<String>,
278 provider_order: usize,
279 handle: Option<ModuleDependencyHandle>,
280 stream_handle: Option<ModuleStreamDependencyHandle>,
281 event_handle: Option<ModuleEventDependencyHandle>,
282 ) -> Self {
283 Self {
284 capability_id: capability_id.into(),
285 provider_instance: provider_instance.into(),
286 provider_order,
287 handle,
288 stream_handle,
289 event_handle,
290 }
291 }
292
293 pub fn capability_id(&self) -> &str {
295 &self.capability_id
296 }
297
298 pub fn provider_instance(&self) -> &str {
300 &self.provider_instance
301 }
302
303 pub const fn provider_order(&self) -> usize {
305 self.provider_order
306 }
307
308 pub fn handle(&self) -> Option<ModuleDependencyHandle> {
310 self.handle.clone()
311 }
312
313 pub fn stream_handle(&self) -> Option<ModuleStreamDependencyHandle> {
315 self.stream_handle.clone()
316 }
317
318 pub fn event_handle(&self) -> Option<ModuleEventDependencyHandle> {
320 self.event_handle.clone()
321 }
322}
323
324#[derive(Clone, Debug)]
326pub struct ModuleDependencyHandle {
327 pub(super) binding: NativeEndpointBinding,
328 pub(super) caller_instance: String,
329 pub(super) runtime: Rc<RefCell<Weak<NativeAppRuntime>>>,
330}
331
332#[derive(Clone, Debug)]
334pub struct ModuleStreamDependencyHandle {
335 pub(super) binding: NativeStreamEndpointBinding,
336 pub(super) caller_instance: String,
337 pub(super) runtime: Rc<RefCell<Weak<NativeAppRuntime>>>,
338}
339
340impl ModuleStreamDependencyHandle {
341 pub fn capability_id(&self) -> &'static str {
343 self.binding.state.capability_id
344 }
345
346 pub fn descriptor_version(&self) -> &'static str {
348 self.binding.state.descriptor_version
349 }
350
351 pub fn operations(&self) -> &'static [&'static str] {
353 self.binding.state.operations
354 }
355
356 pub fn typed<C: StreamCapability>(&self) -> Result<NativeStreamHandle<C>, RuntimeFailure> {
358 if self.capability_id() != C::ID || self.descriptor_version() != C::DESCRIPTOR_VERSION {
359 return Err(RuntimeFailure::ProtocolViolation { capability: C::ID });
360 }
361 let runtime = self
362 .runtime
363 .borrow()
364 .upgrade()
365 .ok_or(RuntimeFailure::AdmissionClosed)?;
366 Ok(NativeStreamHandle::from_endpoints(
367 std::slice::from_ref(&self.binding),
368 runtime,
369 &self.caller_instance,
370 true,
371 ))
372 }
373}
374
375impl ModuleDependencyHandle {
376 pub fn capability_id(&self) -> &'static str {
378 self.binding.state.capability_id
379 }
380
381 pub fn descriptor_version(&self) -> &'static str {
383 self.binding.state.descriptor_version
384 }
385
386 pub fn operations(&self) -> &'static [&'static str] {
388 self.binding.state.operations
389 }
390
391 pub fn typed<C: RequestCapability>(&self) -> Result<NativeRequestHandle<C>, RuntimeFailure> {
393 if self.capability_id() != C::ID || self.descriptor_version() != C::DESCRIPTOR_VERSION {
394 return Err(RuntimeFailure::ProtocolViolation { capability: C::ID });
395 }
396 let runtime = self
397 .runtime
398 .borrow()
399 .upgrade()
400 .ok_or(RuntimeFailure::AdmissionClosed)?;
401 Ok(NativeRequestHandle::from_endpoints(
402 std::slice::from_ref(&self.binding),
403 runtime,
404 &self.caller_instance,
405 true,
406 ))
407 }
408}
409
410#[derive(Clone, Debug, Default)]
412pub struct ModuleDependencies {
413 pub(super) bindings: Vec<ModuleDependency>,
414 pub(super) caller_instance: Rc<str>,
415 pub(super) runtime: Rc<RefCell<Weak<NativeAppRuntime>>>,
416}
417
418impl ModuleDependencies {
419 pub(super) fn new(
420 caller_instance: impl Into<String>,
421 runtime: Rc<RefCell<Weak<NativeAppRuntime>>>,
422 ) -> Self {
423 Self {
424 bindings: Vec::new(),
425 caller_instance: Rc::from(caller_instance.into()),
426 runtime,
427 }
428 }
429
430 pub fn bindings(&self) -> &[ModuleDependency] {
432 &self.bindings
433 }
434
435 pub fn len(&self) -> usize {
437 self.bindings.len()
438 }
439
440 pub fn is_empty(&self) -> bool {
442 self.bindings.is_empty()
443 }
444
445 pub fn invocation_context(
451 &self,
452 deadline: Option<Duration>,
453 cancellation: CancellationToken,
454 ) -> Result<InvocationContext, RuntimeFailure> {
455 let runtime = self
456 .runtime
457 .borrow()
458 .upgrade()
459 .ok_or(RuntimeFailure::AdmissionClosed)?;
460 let request_id = runtime.request_ids.get();
461 runtime.request_ids.set(request_id.saturating_add(1));
462 Ok(InvocationContext::new(request_id, deadline, cancellation)
463 .with_shared_caller_instance(self.caller_instance.clone()))
464 }
465
466 pub fn invocation_context_after(
468 &self,
469 timeout: Duration,
470 cancellation: CancellationToken,
471 ) -> Result<InvocationContext, RuntimeFailure> {
472 let runtime = self
473 .runtime
474 .borrow()
475 .upgrade()
476 .ok_or(RuntimeFailure::AdmissionClosed)?;
477 let deadline = (runtime.driver.now)().saturating_add(timeout);
478 drop(runtime);
479 self.invocation_context(Some(deadline), cancellation)
480 }
481
482 pub fn one<C: RequestCapability>(&self) -> Result<NativeRequestHandle<C>, RuntimeFailure> {
484 let handles: Vec<_> = self
485 .bindings
486 .iter()
487 .filter(|binding| binding.capability_id() == C::ID)
488 .filter_map(ModuleDependency::handle)
489 .collect();
490 match handles.as_slice() {
491 [handle] => handle.typed::<C>(),
492 [] => Err(RuntimeFailure::Unavailable { capability: C::ID }),
493 handles => Err(RuntimeFailure::AmbiguousBinding {
494 capability: C::ID,
495 providers: handles.len(),
496 }),
497 }
498 }
499
500 pub fn optional<C: RequestCapability>(
502 &self,
503 ) -> Result<Option<NativeRequestHandle<C>>, RuntimeFailure> {
504 match self
505 .bindings
506 .iter()
507 .filter(|binding| binding.capability_id() == C::ID)
508 .filter_map(ModuleDependency::handle)
509 .collect::<Vec<_>>()
510 .as_slice()
511 {
512 [] => Ok(None),
513 [handle] => handle.typed::<C>().map(Some),
514 handles => Err(RuntimeFailure::AmbiguousBinding {
515 capability: C::ID,
516 providers: handles.len(),
517 }),
518 }
519 }
520
521 pub fn many<C: RequestCapability>(
523 &self,
524 ) -> Result<Vec<NativeRequestHandle<C>>, RuntimeFailure> {
525 self.bindings
526 .iter()
527 .filter(|binding| binding.capability_id() == C::ID)
528 .filter_map(ModuleDependency::handle)
529 .map(|handle| handle.typed::<C>())
530 .collect()
531 }
532
533 pub fn one_stream<C: StreamCapability>(&self) -> Result<NativeStreamHandle<C>, RuntimeFailure> {
535 let handles: Vec<_> = self
536 .bindings
537 .iter()
538 .filter(|binding| binding.capability_id() == C::ID)
539 .filter_map(ModuleDependency::stream_handle)
540 .collect();
541 match handles.as_slice() {
542 [handle] => handle.typed::<C>(),
543 [] => Err(RuntimeFailure::Unavailable { capability: C::ID }),
544 handles => Err(RuntimeFailure::AmbiguousBinding {
545 capability: C::ID,
546 providers: handles.len(),
547 }),
548 }
549 }
550
551 pub fn optional_stream<C: StreamCapability>(
553 &self,
554 ) -> Result<Option<NativeStreamHandle<C>>, RuntimeFailure> {
555 match self
556 .bindings
557 .iter()
558 .filter(|binding| binding.capability_id() == C::ID)
559 .filter_map(ModuleDependency::stream_handle)
560 .collect::<Vec<_>>()
561 .as_slice()
562 {
563 [] => Ok(None),
564 [handle] => handle.typed::<C>().map(Some),
565 handles => Err(RuntimeFailure::AmbiguousBinding {
566 capability: C::ID,
567 providers: handles.len(),
568 }),
569 }
570 }
571
572 pub fn many_stream<C: StreamCapability>(
574 &self,
575 ) -> Result<Vec<NativeStreamHandle<C>>, RuntimeFailure> {
576 self.bindings
577 .iter()
578 .filter(|binding| binding.capability_id() == C::ID)
579 .filter_map(ModuleDependency::stream_handle)
580 .map(|handle| handle.typed::<C>())
581 .collect()
582 }
583
584 pub fn many_event<C: EventCapability>(&self) -> Result<NativeEventHandle<C>, RuntimeFailure> {
586 let handles: Vec<_> = self
587 .bindings
588 .iter()
589 .filter(|binding| binding.capability_id() == C::ID)
590 .filter_map(ModuleDependency::event_handle)
591 .collect();
592 if handles.iter().any(|handle| {
593 handle.capability_id() != C::ID || handle.descriptor_version() != C::DESCRIPTOR_VERSION
594 }) {
595 return Err(RuntimeFailure::ProtocolViolation { capability: C::ID });
596 }
597 let runtime = self
598 .runtime
599 .borrow()
600 .upgrade()
601 .ok_or(RuntimeFailure::AdmissionClosed)?;
602 let endpoints = handles
603 .iter()
604 .map(|handle| handle.binding.clone())
605 .collect::<Vec<_>>();
606 Ok(NativeEventHandle::from_endpoints(
607 &endpoints,
608 runtime,
609 &self.caller_instance,
610 true,
611 ))
612 }
613
614 pub fn one_event<C: EventCapability>(&self) -> Result<NativeEventHandle<C>, RuntimeFailure> {
616 match self
617 .bindings
618 .iter()
619 .filter(|binding| binding.capability_id() == C::ID)
620 .filter_map(ModuleDependency::event_handle)
621 .collect::<Vec<_>>()
622 .as_slice()
623 {
624 [handle] => handle.typed::<C>(),
625 [] => Err(RuntimeFailure::Unavailable { capability: C::ID }),
626 handles => Err(RuntimeFailure::AmbiguousBinding {
627 capability: C::ID,
628 providers: handles.len(),
629 }),
630 }
631 }
632
633 pub fn optional_event<C: EventCapability>(
635 &self,
636 ) -> Result<Option<NativeEventHandle<C>>, RuntimeFailure> {
637 match self
638 .bindings
639 .iter()
640 .filter(|binding| binding.capability_id() == C::ID)
641 .filter_map(ModuleDependency::event_handle)
642 .collect::<Vec<_>>()
643 .as_slice()
644 {
645 [] => Ok(None),
646 [handle] => handle.typed::<C>().map(Some),
647 handles => Err(RuntimeFailure::AmbiguousBinding {
648 capability: C::ID,
649 providers: handles.len(),
650 }),
651 }
652 }
653}