allora_runtime/dsl/
runtime.rs

1//! AlloraRuntime: aggregate of built runtime components (extensible).
2//!
3//! Current contents:
4//! * channels: vector of in-memory channels (single kind)
5//!
6//! Future extensions (not yet wired):
7//! * endpoints
8//! * filters / routers
9//! * adapters
10//! * correlation groups
11//!
12//! Design goals:
13//! * Single return object from top-level build to avoid signature churn as components grow.
14//! * Provide accessor methods with owned + borrowed variants.
15//! * Keep internal storage concrete now; migrate to trait objects (`ChannelRef`) when multiple channel kinds arrive.
16//!
17//! Backward compatibility note removed: prefer `build()` which returns `AlloraRuntime`.
18//!
19//! # Overview
20//! `AlloraRuntime` is the single return object from the top-level `build()` DSL facade.
21//! It bundles all instantiated runtime components derived from a configuration spec.
22//! Currently it only contains channels (in-memory kind); future releases will extend it
23//! with endpoints, filters/routers, adapters, and correlation utilities without changing
24//! the public `build()` signature.
25//!
26//! # Guarantees
27//! * The collection of channels preserves the order they were defined in the source spec.
28//! * Channel IDs are unique (enforced at build time); missing IDs receive deterministic
29//!   `channel:auto.N` identifiers within the same build invocation.
30//! * Lookup (`channel_by_id`) performs a linear scan; acceptable for small collections.
31//!   This can be optimized later by introducing an internal index without API changes.
32//!
33//! # Usage Example
34//! ```no_run
35//! use allora_core::Channel;
36//! use allora_runtime::build;
37//! // Requires a valid allora.yml at the given path.
38//! let rt = build("tests/fixtures/allora.yml").unwrap();
39//! assert!(rt.channel_by_id("inbound.orders").is_some());
40//! for ch in rt.channels() { println!("id={}", ch.id()); }
41//! ```
42//!
43//! # Future Extensions (Illustrative)
44//! * `endpoints()` -> &[Endpoint]
45//! * `filters()` / `routers()` -> pattern components
46//! * `adapters()` -> inbound / outbound integration points
47//! * `correlations()` -> tracking groups for aggregation patterns
48//! These will be added as additional fields with accessor methods while keeping
49//! `AlloraRuntime` construction centralized in the DSL facade.
50use crate::channel::Channel;
51use crate::dsl::component_builders::ServiceProcessor;
52use crate::service_activator_processor::ServiceActivatorProcessor;
53use crate::Filter;
54use crate::HttpInboundAdapter;
55use crate::HttpOutboundAdapter;
56use std::sync::Arc;
57
58#[derive(Debug)]
59/// Aggregated runtime container for all built components (channels today, more later).
60///
61/// Prefer borrowing via the accessor methods (`channels()`, `channel_by_id`) for read-only
62/// operations. Use `into_channels()` only when you need ownership transfer (e.g. embedding
63/// channels into another structure or performing manual lifecycle management).
64pub struct AlloraRuntime {
65    channels: Vec<Arc<dyn Channel>>,
66    filters: Vec<Filter>,
67    services: Vec<ServiceProcessor>,
68    service_activator_processors: Vec<ServiceActivatorProcessor>,
69    http_inbound_adapters: Vec<HttpInboundAdapter>,
70    http_outbound_adapters: Vec<HttpOutboundAdapter>,
71}
72
73impl AlloraRuntime {
74    /// Create a new runtime instance from a vector of channels.
75    ///
76    /// Typically, invoked internally by the DSL (`build_runtime_from_str`).
77    pub fn new(channels: Vec<Box<dyn Channel>>) -> Self {
78        let channels_arc = channels.into_iter().map(|c| Arc::from(c)).collect();
79        Self {
80            channels: channels_arc,
81            filters: Vec::new(),
82            services: Vec::new(),
83            service_activator_processors: Vec::new(),
84            http_inbound_adapters: Vec::new(),
85            http_outbound_adapters: Vec::new(),
86        }
87    }
88    /// Sets the filters for this runtime.
89    ///
90    /// Consumes the provided filters vector and assigns it to the runtime.
91    pub fn with_filters(mut self, filters: Vec<Filter>) -> Self {
92        self.filters = filters;
93        self
94    }
95    /// Sets the services for this runtime.
96    ///
97    /// Consumes the provided services vector and assigns it to the runtime.
98    pub fn with_services(mut self, services: Vec<ServiceProcessor>) -> Self {
99        self.services = services;
100        self
101    }
102    pub fn with_service_processors(mut self, proc: Vec<ServiceActivatorProcessor>) -> Self {
103        self.service_activator_processors = proc;
104        self
105    }
106    /// Sets the HTTP inbound adapters for this runtime.
107    ///
108    /// Consumes the provided adapters vector and assigns it to the runtime.
109    pub fn with_http_inbound_adapters(mut self, adapters: Vec<HttpInboundAdapter>) -> Self {
110        self.http_inbound_adapters = adapters;
111        self
112    }
113    pub fn with_http_outbound_adapters(mut self, adapters: Vec<HttpOutboundAdapter>) -> Self {
114        self.http_outbound_adapters = adapters;
115        self
116    }
117    /// Borrow all channels as an iterator of &dyn Channel (zero allocation).
118    pub fn channels(&self) -> impl Iterator<Item = &dyn Channel> {
119        self.channels.iter().map(|c| c.as_ref())
120    }
121    /// Borrow underlying boxed channel slice (rarely needed).
122    pub fn channels_slice(&self) -> &[Arc<dyn Channel>] {
123        &self.channels
124    }
125    /// Borrow all filters (read-only slice).
126    pub fn filters(&self) -> &[Filter] {
127        &self.filters
128    }
129    /// Borrow all services (read-only slice).
130    pub fn services(&self) -> &[ServiceProcessor] {
131        &self.services
132    }
133    /// Consume the runtime, yielding owned channels.
134    pub fn into_channels(self) -> Vec<Arc<dyn Channel>> {
135        self.channels
136    }
137    /// Consume the runtime, yielding owned filters.
138    pub fn into_filters(self) -> Vec<Filter> {
139        self.filters
140    }
141    /// Consume the runtime, yielding owned services.
142    pub fn into_services(self) -> Vec<ServiceProcessor> {
143        self.services
144    }
145    /// Find a channel by its id; returns `None` if not present.
146    ///
147    /// Complexity: O(n). Optimizations (hash index) can be added later without
148    /// changing this method's signature or semantics.
149    pub fn channel_by_id(&self, id: &str) -> Option<&dyn Channel> {
150        self.channels
151            .iter()
152            .find(|c| c.id() == id)
153            .map(|c| c.as_ref())
154    }
155    /// Return a cloned Arc<dyn Channel> by id if it exists (helper for builders needing ownership).
156    pub fn channel_ref_by_id(&self, id: &str) -> Option<Arc<dyn Channel>> {
157        self.channels
158            .iter()
159            .find(|c| c.id() == id)
160            .map(|c| Arc::clone(c))
161    }
162    /// Generic typed channel lookup: returns &T if a channel with `id` exists and downcasts to T.
163    pub fn channel_typed<T: Channel + 'static>(&self, id: &str) -> Option<&T> {
164        self.channels
165            .iter()
166            .find(|c| c.id() == id)
167            .and_then(|c| c.as_any().downcast_ref::<T>())
168    }
169    /// Required typed channel lookup: panics with a clear message if missing or wrong type.
170    pub fn channel<T: Channel + 'static>(&self, id: &str) -> &T {
171        for c in &self.channels {
172            if c.id() == id {
173                if let Some(t) = c.as_any().downcast_ref::<T>() {
174                    return t;
175                } else {
176                    panic!(
177                        "channel '{}' exists with kind '{}' but does not match expected type '{}'",
178                        id,
179                        c.kind(),
180                        std::any::type_name::<T>()
181                    );
182                }
183            }
184        }
185        panic!(
186            "channel '{}' not found (expected type '{}')",
187            id,
188            std::any::type_name::<T>()
189        );
190    }
191    /// Predicate: does channel id exist and is of type T?
192    pub fn channel_is<T: Channel + 'static>(&self, id: &str) -> bool {
193        self.channel_typed::<T>(id).is_some()
194    }
195    /// Total number of channels in this runtime.
196    pub fn channel_count(&self) -> usize {
197        self.channels.len()
198    }
199    /// Total number of filters in this runtime.
200    pub fn filter_count(&self) -> usize {
201        self.filters.len()
202    }
203    /// Total number of services in this runtime.
204    pub fn service_count(&self) -> usize {
205        self.services.len()
206    }
207    /// Total number of service processors in this runtime.
208    pub fn service_processor_count(&self) -> usize {
209        self.service_activator_processors.len()
210    }
211    pub fn service_activator_processors(&self) -> &[ServiceActivatorProcessor] {
212        &self.service_activator_processors
213    }
214    pub fn service_activator_processor_by_id(
215        &self,
216        id: &str,
217    ) -> Option<&ServiceActivatorProcessor> {
218        self.service_activator_processors
219            .iter()
220            .find(|p| p.id() == id)
221    }
222    pub fn service_activator_processor_mut_by_id(
223        &mut self,
224        id: &str,
225    ) -> Option<&mut ServiceActivatorProcessor> {
226        self.service_activator_processors
227            .iter_mut()
228            .find(|p| p.id() == id)
229    }
230    pub fn http_inbound_adapters(&self) -> &[HttpInboundAdapter] {
231        &self.http_inbound_adapters
232    }
233    pub fn http_inbound_adapter_count(&self) -> usize {
234        self.http_inbound_adapters.len()
235    }
236    pub fn http_outbound_adapters(&self) -> &[HttpOutboundAdapter] {
237        &self.http_outbound_adapters
238    }
239    pub fn http_outbound_adapter_count(&self) -> usize {
240        self.http_outbound_adapters.len()
241    }
242}