Skip to main content

platform_module/
linked.rs

1//! The compile-time loading source: behavior is linked Rust code.
2
3use crate::binding::{EventHandlerRegistrationContext, ModuleBinding};
4use platform_core::{EventHandler, EventHandlerRegistry};
5use platform_http::ApiOpenApiRouter;
6use platform_runtime::{FunctionRegistry, RuntimeDescriptor};
7use std::sync::Arc;
8
9pub type LinkedHttpRouteMerger = fn(ApiOpenApiRouter) -> ApiOpenApiRouter;
10
11#[derive(Debug, Clone, Copy)]
12pub struct LinkedHttpContribution {
13    pub public_prefixes: &'static [&'static str],
14    pub merge: LinkedHttpRouteMerger,
15}
16
17/// The only [`ModuleBinding`] impl in Step 1. Remote/Wasm impls arrive in their
18/// own specs without touching this one (open extension point). Only forwards to
19/// the existing registration logic — no logic moves here.
20#[derive(Debug)]
21pub struct LinkedBinding {
22    pub runtime: RuntimeDescriptor,
23    pub event_handlers: Vec<Arc<dyn EventHandler>>,
24    pub http: Option<LinkedHttpContribution>,
25}
26
27impl LinkedBinding {
28    /// Start building a linked binding.
29    #[must_use]
30    pub fn builder() -> LinkedBindingBuilder {
31        LinkedBindingBuilder {
32            runtime: RuntimeDescriptor::default(),
33            event_handlers: Vec::new(),
34            http: None,
35        }
36    }
37}
38
39impl ModuleBinding for LinkedBinding {
40    fn register_functions(&self, registry: &mut FunctionRegistry) {
41        self.runtime.register_into(registry);
42    }
43
44    fn register_event_handlers(
45        &self,
46        registry: &mut EventHandlerRegistry,
47        _context: &EventHandlerRegistrationContext,
48    ) {
49        registry.register_all(self.event_handlers.clone());
50    }
51}
52
53/// Fluent builder for [`LinkedBinding`]. Source-specific (Linked only).
54#[derive(Debug)]
55pub struct LinkedBindingBuilder {
56    runtime: RuntimeDescriptor,
57    event_handlers: Vec<Arc<dyn EventHandler>>,
58    http: Option<LinkedHttpContribution>,
59}
60
61impl LinkedBindingBuilder {
62    /// Set the runtime descriptor (functions, queues, triggers, flows).
63    #[must_use]
64    pub fn runtime(mut self, runtime: RuntimeDescriptor) -> Self {
65        self.runtime = runtime;
66        self
67    }
68
69    /// Set the in-process event handlers.
70    #[must_use]
71    pub fn event_handlers(mut self, handlers: Vec<Arc<dyn EventHandler>>) -> Self {
72        self.event_handlers = handlers;
73        self
74    }
75
76    /// Set this linked module's in-process HTTP router contribution.
77    #[must_use]
78    pub fn http(mut self, contribution: LinkedHttpContribution) -> Self {
79        self.http = Some(contribution);
80        self
81    }
82
83    /// Finish building.
84    #[must_use]
85    pub fn build(self) -> LinkedBinding {
86        LinkedBinding {
87            runtime: self.runtime,
88            event_handlers: self.event_handlers,
89            http: self.http,
90        }
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97    use async_trait::async_trait;
98    use platform_core::{ClaimedOutboxEvent, ExecutionContext};
99    use platform_runtime::{FunctionDefinition, FunctionHandler, RetryPolicy};
100    use serde_json::Value;
101
102    // Minimal no-op handler to register one function.
103    #[derive(Debug)]
104    struct NoopHandler;
105
106    #[async_trait]
107    impl FunctionHandler for NoopHandler {
108        async fn call(
109            &self,
110            _ctx: ExecutionContext,
111            _input: Value,
112        ) -> platform_core::AppResult<Value> {
113            Ok(Value::Null)
114        }
115    }
116
117    // Minimal no-op event handler bound to a fixed event name.
118    #[derive(Debug)]
119    struct NoopEventHandler;
120
121    #[async_trait]
122    impl EventHandler for NoopEventHandler {
123        fn event_name(&self) -> &str {
124            "test.event"
125        }
126
127        async fn handle(&self, _event: &ClaimedOutboxEvent) -> platform_core::AppResult<()> {
128            Ok(())
129        }
130    }
131
132    #[test]
133    fn linked_binding_registers_its_functions() {
134        let runtime = RuntimeDescriptor {
135            module: "test",
136            functions: vec![FunctionDefinition {
137                name: "test.noop".to_owned(),
138                version: 1,
139                queue: "test".to_owned(),
140                retry_policy: RetryPolicy::default(),
141                handler: Arc::new(NoopHandler),
142            }],
143            ..RuntimeDescriptor::default()
144        };
145        let binding = LinkedBinding::builder().runtime(runtime).build();
146
147        let mut registry = FunctionRegistry::default();
148        binding.register_functions(&mut registry);
149
150        assert!(registry.get("test.noop").is_some());
151    }
152
153    #[test]
154    fn linked_binding_registers_its_event_handlers() {
155        let binding = LinkedBinding::builder()
156            .event_handlers(vec![Arc::new(NoopEventHandler)])
157            .build();
158
159        let mut registry = EventHandlerRegistry::default();
160        binding.register_event_handlers(&mut registry, &EventHandlerRegistrationContext::empty());
161
162        assert_eq!(registry.handler_count("test.event"), 1);
163    }
164}