Skip to main content

cordis/
context.rs

1//! Root and child contexts tying all Cordis services together.
2
3use crate::effect::{AsyncDisposer, EffectHandle};
4use crate::events::{Event, EventOptions, EventResult, EventValue, EventsRoot, EventsService};
5use crate::fiber::{Fiber, FiberInner};
6use crate::logger::{LogArg, Logger, LoggerRoot, LoggerService};
7use crate::reflect::{Accessor, ReflectRoot, ReflectService};
8use crate::registry::{Inject, IntoPlugin, PluginOutput, RegistryRoot, RegistryService};
9use crate::{Config, Result, Value};
10use std::collections::HashMap;
11use std::fmt::{self, Debug, Formatter};
12use std::future::Future;
13use std::sync::atomic::{AtomicU64, Ordering};
14use std::sync::{Arc, OnceLock, Weak};
15
16/// Opaque service-scope label used by [`Context::isolate_with`].
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
18pub struct Isolation(pub(crate) u64);
19
20impl Isolation {
21    /// Create a user-defined label.
22    ///
23    /// Labels returned by [`Context::new_isolation`] are preferred because
24    /// they cannot accidentally collide with framework-generated labels.
25    pub const fn from_raw(value: u64) -> Self {
26        Self(value)
27    }
28
29    /// Expose the numeric label for persistence or diagnostics.
30    pub const fn as_raw(self) -> u64 {
31        self.0
32    }
33}
34
35/// Event listener filter attached to a child context.
36pub type ContextFilter = Arc<dyn Fn(&Context) -> bool + Send + Sync + 'static>;
37
38/// Immutable data inherited by contexts and copied on extension.
39#[derive(Clone, Default)]
40pub struct ContextMeta {
41    pub(crate) isolates: Arc<HashMap<String, Isolation>>,
42    pub(crate) intercepts: Arc<Vec<(String, Value)>>,
43    pub(crate) values: Arc<HashMap<String, Value>>,
44    pub(crate) filter: Option<ContextFilter>,
45    pub(crate) base_url: Option<Arc<str>>,
46}
47
48impl Debug for ContextMeta {
49    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
50        f.debug_struct("ContextMeta")
51            .field("isolates", &self.isolates)
52            .field("intercept_count", &self.intercepts.len())
53            .field("value_keys", &self.values.keys().collect::<Vec<_>>())
54            .field("has_filter", &self.filter.is_some())
55            .field("base_url", &self.base_url)
56            .finish()
57    }
58}
59
60pub(crate) struct RootInner {
61    pub(crate) reflect: ReflectRoot,
62    pub(crate) registry: RegistryRoot,
63    pub(crate) events: EventsRoot,
64    pub(crate) logger: LoggerRoot,
65    pub(crate) next_scope: AtomicU64,
66    pub(crate) next_fiber: AtomicU64,
67    pub(crate) next_effect: AtomicU64,
68    pub(crate) root_fiber: OnceLock<Fiber>,
69}
70
71impl RootInner {
72    fn new() -> Self {
73        Self {
74            reflect: ReflectRoot::new(),
75            registry: RegistryRoot::new(),
76            events: EventsRoot::new(),
77            logger: LoggerRoot::new(),
78            // Keep 0 reserved for an absent scope/fiber/effect.
79            next_scope: AtomicU64::new(0),
80            next_fiber: AtomicU64::new(0),
81            next_effect: AtomicU64::new(0),
82            root_fiber: OnceLock::new(),
83        }
84    }
85
86    pub(crate) fn scope(&self) -> Isolation {
87        Isolation(self.next_scope.fetch_add(1, Ordering::Relaxed) + 1)
88    }
89
90    pub(crate) fn fiber_id(&self) -> u64 {
91        self.next_fiber.fetch_add(1, Ordering::Relaxed) + 1
92    }
93
94    pub(crate) fn effect_id(&self) -> u64 {
95        self.next_effect.fetch_add(1, Ordering::Relaxed) + 1
96    }
97}
98
99/// Root and child dependency containers for Cordis plugins.
100///
101/// Cloning a context is cheap. Child contexts share all runtime services while
102/// carrying immutable scope/intercept/metadata overlays and the current fiber.
103#[derive(Clone)]
104pub struct Context {
105    pub(crate) root: Arc<RootInner>,
106    pub(crate) fiber: Weak<FiberInner>,
107    pub(crate) meta: ContextMeta,
108}
109
110impl Context {
111    /// Create a root context and install the core reflection, registry, event,
112    /// and logger services.
113    pub fn new() -> Self {
114        let root = Arc::new(RootInner::new());
115        let fiber = Fiber::new_root(Arc::downgrade(&root), ContextMeta::default());
116        root.root_fiber
117            .set(fiber.clone())
118            .unwrap_or_else(|_| unreachable!("new root has no fiber"));
119        fiber.context().expect("fresh root is alive")
120    }
121
122    /// Return whether two contexts belong to the same root application.
123    pub fn same_root(&self, other: &Context) -> bool {
124        Arc::ptr_eq(&self.root, &other.root)
125    }
126
127    /// Return the root context.
128    pub fn root(&self) -> Context {
129        self.root
130            .root_fiber
131            .get()
132            .expect("root fiber initialized")
133            .context()
134            .expect("root context has a live root")
135    }
136
137    /// Return this context's owning plugin fiber.
138    pub fn fiber(&self) -> Result<Fiber> {
139        self.fiber
140            .upgrade()
141            .map(Fiber::from_inner)
142            .ok_or_else(|| crate::CordisError::new(crate::ErrorCode::InactiveEffect))
143    }
144
145    /// Return the base URL inherited by this context, when set.
146    pub fn base_url(&self) -> Option<&str> {
147        self.meta.base_url.as_deref()
148    }
149
150    /// Derive a child context with a new base URL.
151    pub fn with_base_url(&self, base_url: impl Into<Arc<str>>) -> Context {
152        let mut child = self.clone();
153        child.meta.base_url = Some(base_url.into());
154        child
155    }
156
157    /// Derive a child context carrying arbitrary metadata.
158    pub fn extend<T>(&self, name: impl Into<String>, value: T) -> Context
159    where
160        T: Send + Sync + 'static,
161    {
162        self.extend_value(name, Value::new(value))
163    }
164
165    /// Derive a child context carrying type-erased metadata.
166    pub fn extend_value(&self, name: impl Into<String>, value: Value) -> Context {
167        let mut values = (*self.meta.values).clone();
168        values.insert(name.into(), value);
169        let mut child = self.clone();
170        child.meta.values = Arc::new(values);
171        child
172    }
173
174    /// Read typed context metadata.
175    pub fn metadata<T>(&self, name: &str) -> Result<Option<Arc<T>>>
176    where
177        T: Send + Sync + 'static,
178    {
179        self.meta.values.get(name).map(Value::downcast).transpose()
180    }
181
182    /// Allocate a globally unique isolation label.
183    pub fn new_isolation(&self) -> Isolation {
184        self.root.scope()
185    }
186
187    /// Create a child context with a fresh independent service scope for
188    /// `name`.
189    pub fn isolate(&self, name: impl Into<String>) -> Context {
190        let label = self.new_isolation();
191        self.isolate_with(name, label)
192    }
193
194    /// Create a child context using a supplied scope label. Reusing the label
195    /// joins otherwise separate context branches to the same service scope.
196    ///
197    /// A scope holds one implementation per slot, and every name isolated
198    /// with the same label maps to that one slot: only one of those names
199    /// can be provided there, and providing a second fails with
200    /// [`DuplicateService`](crate::ErrorCode::DuplicateService).
201    pub fn isolate_with(&self, name: impl Into<String>, label: Isolation) -> Context {
202        let mut isolates = (*self.meta.isolates).clone();
203        isolates.insert(name.into(), label);
204        let mut child = self.clone();
205        child.meta.isolates = Arc::new(isolates);
206        child
207    }
208
209    /// Create a child context with several service names isolated together.
210    ///
211    /// All `names` share one scope label and therefore one implementation
212    /// slot, matching the upstream single-slot scope model: provide exactly
213    /// one of them in this branch — providing a second name fails with
214    /// [`DuplicateService`](crate::ErrorCode::DuplicateService) — and inject
215    /// only that name from sibling plugins. Use separate labels (or
216    /// [`isolate`](Self::isolate)) when several of the names need independent
217    /// implementations.
218    pub fn isolate_many(
219        &self,
220        names: impl IntoIterator<Item = impl Into<String>>,
221        label: Option<Isolation>,
222    ) -> Context {
223        let label = label.unwrap_or_else(|| self.new_isolation());
224        let mut child = self.clone();
225        let mut isolates = (*child.meta.isolates).clone();
226        for name in names {
227            isolates.insert(name.into(), label);
228        }
229        child.meta.isolates = Arc::new(isolates);
230        child
231    }
232
233    /// Add service-specific intercept configuration below this context.
234    pub fn intercept<T>(&self, name: impl Into<String>, config: T) -> Context
235    where
236        T: Send + Sync + 'static,
237    {
238        self.intercept_value(name, Value::new(config))
239    }
240
241    /// Add type-erased intercept configuration.
242    pub fn intercept_value(&self, name: impl Into<String>, config: Value) -> Context {
243        let mut intercepts = (*self.meta.intercepts).clone();
244        intercepts.push((name.into(), config));
245        let mut child = self.clone();
246        child.meta.intercepts = Arc::new(intercepts);
247        child
248    }
249
250    /// Return typed intercept configs in ancestor-to-descendant order.
251    pub fn intercepts<T>(&self, name: &str) -> Result<Vec<Arc<T>>>
252    where
253        T: Send + Sync + 'static,
254    {
255        self.meta
256            .intercepts
257            .iter()
258            .filter(|(entry, _)| entry == name)
259            .map(|(_, value)| value.downcast())
260            .collect()
261    }
262
263    /// Attach an event listener filter to a derived context.
264    pub fn with_filter<F>(&self, filter: F) -> Context
265    where
266        F: Fn(&Context) -> bool + Send + Sync + 'static,
267    {
268        let mut child = self.clone();
269        child.meta.filter = Some(Arc::new(filter));
270        child
271    }
272
273    /// Return the events service bound to this context.
274    pub fn events(&self) -> EventsService {
275        EventsService::new(self.clone())
276    }
277
278    /// Return the reflection/service store bound to this context.
279    pub fn reflect(&self) -> ReflectService {
280        ReflectService::new(self.clone())
281    }
282
283    /// Return the plugin registry bound to this context.
284    pub fn registry(&self) -> RegistryService {
285        RegistryService::new(self.clone())
286    }
287
288    /// Return a logger named from the current fiber and intercepts.
289    pub fn logger(&self) -> Logger {
290        LoggerService::new(self.clone()).logger(None)
291    }
292
293    /// Return an explicitly named logger.
294    pub fn named_logger(&self, name: impl Into<String>) -> Logger {
295        LoggerService::new(self.clone()).logger(Some(name.into()))
296    }
297
298    /// Return the logger service bound to this context.
299    pub fn logger_service(&self) -> LoggerService {
300        LoggerService::new(self.clone())
301    }
302
303    /// Register a synchronous cleanup operation on the current fiber.
304    pub fn effect<F>(&self, label: impl Into<String>, dispose: F) -> Result<EffectHandle>
305    where
306        F: FnOnce() -> Result<()> + Send + 'static,
307    {
308        self.fiber()?
309            .register_effect(label, AsyncDisposer::from_sync(dispose))
310    }
311
312    /// Register an infallible synchronous cleanup operation.
313    pub fn effect_infallible<F>(&self, label: impl Into<String>, dispose: F) -> Result<EffectHandle>
314    where
315        F: FnOnce() + Send + 'static,
316    {
317        self.fiber()?
318            .register_effect(label, AsyncDisposer::infallible(dispose))
319    }
320
321    /// Register an asynchronous cleanup operation.
322    pub fn effect_async<F, Fut>(&self, label: impl Into<String>, dispose: F) -> Result<EffectHandle>
323    where
324        F: FnOnce() -> Fut + Send + 'static,
325        Fut: Future<Output = Result<()>> + Send + 'static,
326    {
327        self.fiber()?
328            .register_effect(label, AsyncDisposer::from_async(dispose))
329    }
330
331    /// Provide a concrete service in this context's isolation scope.
332    pub fn provide<T>(&self, name: impl Into<String>, value: T) -> Result<EffectHandle>
333    where
334        T: Send + Sync + 'static,
335    {
336        self.reflect()
337            .provide_value(name.into(), Value::new(value), None)
338    }
339
340    /// Provide an existing `Arc` without wrapping it in a second `Arc`.
341    pub fn provide_arc<T>(&self, name: impl Into<String>, value: Arc<T>) -> Result<EffectHandle>
342    where
343        T: Send + Sync + 'static,
344    {
345        self.reflect()
346            .provide_value(name.into(), Value::from_arc(value), None)
347    }
348
349    /// Read a service without enforcing an inject declaration.
350    pub fn get<T>(&self, name: &str) -> Result<Option<Arc<T>>>
351    where
352        T: Send + Sync + 'static,
353    {
354        self.reflect().get(name, true)
355    }
356
357    /// Read a service even while its provider is loading or unloading.
358    pub fn get_unchecked<T>(&self, name: &str) -> Result<Option<Arc<T>>>
359    where
360        T: Send + Sync + 'static,
361    {
362        self.reflect().get(name, false)
363    }
364
365    /// Require a currently active service.
366    pub fn require<T>(&self, name: &str) -> Result<Arc<T>>
367    where
368        T: Send + Sync + 'static,
369    {
370        self.reflect().require(name)
371    }
372
373    /// Replace a service value. Only its providing fiber may do this.
374    pub fn set<T>(&self, name: &str, value: T) -> Result<()>
375    where
376        T: Send + Sync + 'static,
377    {
378        self.reflect().set_value(name, Value::new(value))
379    }
380
381    /// Re-evaluate dependency availability for the named services.
382    pub fn notify<I, S>(&self, names: I) -> Vec<Fiber>
383    where
384        I: IntoIterator<Item = S>,
385        S: AsRef<str>,
386    {
387        self.reflect().notify(names)
388    }
389
390    /// Register a dynamic computed property.
391    pub fn accessor(&self, name: impl Into<String>, accessor: Accessor) -> Result<EffectHandle> {
392        self.reflect().accessor(name.into(), accessor)
393    }
394
395    /// Register an event listener owned by this context's fiber.
396    pub fn on<F>(&self, name: impl Into<String>, listener: F) -> Result<EffectHandle>
397    where
398        F: Fn(Event) -> EventResult + Send + Sync + 'static,
399    {
400        self.events().on(name, listener, EventOptions::default())
401    }
402
403    /// Register a listener with placement and filtering options.
404    pub fn on_with<F>(
405        &self,
406        name: impl Into<String>,
407        listener: F,
408        options: EventOptions,
409    ) -> Result<EffectHandle>
410    where
411        F: Fn(Event) -> EventResult + Send + Sync + 'static,
412    {
413        self.events().on(name, listener, options)
414    }
415
416    /// Register an asynchronous event listener.
417    pub fn on_async<F, Fut>(&self, name: impl Into<String>, listener: F) -> Result<EffectHandle>
418    where
419        F: Fn(Event) -> Fut + Send + Sync + 'static,
420        Fut: Future<Output = EventResult> + Send + 'static,
421    {
422        self.events()
423            .on_async(name, listener, EventOptions::default())
424    }
425
426    /// Register a one-shot event listener.
427    pub fn once<F>(&self, name: impl Into<String>, listener: F) -> Result<EffectHandle>
428    where
429        F: Fn(Event) -> EventResult + Send + Sync + 'static,
430    {
431        self.events().once(name, listener, EventOptions::default())
432    }
433
434    /// Emit an event synchronously.
435    pub fn emit(
436        &self,
437        name: impl Into<String>,
438        args: impl IntoIterator<Item = EventValue>,
439    ) -> Result<()> {
440        self.events().emit(name, args)
441    }
442
443    /// Run all matching listeners concurrently.
444    pub async fn parallel(
445        &self,
446        name: impl Into<String>,
447        args: impl IntoIterator<Item = EventValue>,
448    ) -> Result<()> {
449        self.events().parallel(name, args).await
450    }
451
452    /// Await listeners in order until one returns a bail value.
453    pub async fn serial(
454        &self,
455        name: impl Into<String>,
456        args: impl IntoIterator<Item = EventValue>,
457    ) -> EventResult {
458        self.events().serial(name, args).await
459    }
460
461    /// Run listeners synchronously until one returns a bail value.
462    pub fn bail(
463        &self,
464        name: impl Into<String>,
465        args: impl IntoIterator<Item = EventValue>,
466    ) -> EventResult {
467        self.events().bail(name, args)
468    }
469
470    /// Compose listeners around an innermost synchronous callback.
471    pub fn waterfall<F>(
472        &self,
473        name: impl Into<String>,
474        args: impl IntoIterator<Item = EventValue>,
475        inner: F,
476    ) -> EventResult
477    where
478        F: Fn() -> EventResult + Send + Sync + 'static,
479    {
480        self.events().waterfall(name, args, inner)
481    }
482
483    /// Start a plugin and return its lifecycle fiber.
484    pub fn plugin<P, C>(&self, plugin: P, config: C) -> Fiber
485    where
486        P: IntoPlugin,
487        C: Send + Sync + 'static,
488    {
489        self.registry()
490            .plugin_value(plugin.into_plugin(), Config::new(config))
491    }
492
493    /// Start a plugin with unit configuration.
494    pub fn plugin_default<P>(&self, plugin: P) -> Fiber
495    where
496        P: IntoPlugin,
497    {
498        self.registry()
499            .plugin_value(plugin.into_plugin(), Config::default())
500    }
501
502    /// Wrap and start a concrete [`Plugin`](crate::Plugin) implementation.
503    pub fn plugin_object<P, C>(&self, plugin: P, config: C) -> Fiber
504    where
505        P: crate::Plugin,
506        C: Send + Sync + 'static,
507    {
508        self.registry()
509            .plugin_value(crate::PluginHandle::new(plugin), Config::new(config))
510    }
511
512    /// Start a concrete plugin implementation with unit configuration.
513    pub fn plugin_object_default<P>(&self, plugin: P) -> Fiber
514    where
515        P: crate::Plugin,
516    {
517        self.registry()
518            .plugin_value(crate::PluginHandle::new(plugin), Config::default())
519    }
520
521    /// Start an inline callback after all listed services become available.
522    pub fn inject<F>(&self, inject: Inject, callback: F) -> Fiber
523    where
524        F: Fn(Context) -> Result<PluginOutput> + Send + Sync + 'static,
525    {
526        let plugin = crate::plugin_sync::<(), _>("anonymous", inject, move |ctx, _| callback(ctx));
527        self.plugin_default(plugin)
528    }
529
530    /// Log an error through the current context logger.
531    pub fn log_error(&self, error: impl ToString) {
532        self.logger().error(error.to_string(), Vec::<LogArg>::new());
533    }
534
535    pub(crate) fn scope_override(&self, name: &str) -> Option<Isolation> {
536        self.meta.isolates.get(name).copied()
537    }
538
539    pub(crate) fn filter(&self) -> Option<&ContextFilter> {
540        self.meta.filter.as_ref()
541    }
542
543    pub(crate) fn root_arc(&self) -> &Arc<RootInner> {
544        &self.root
545    }
546}
547
548impl Default for Context {
549    fn default() -> Self {
550        Self::new()
551    }
552}
553
554impl Debug for Context {
555    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
556        let name = self
557            .fiber()
558            .map(|fiber| fiber.name())
559            .unwrap_or_else(|_| "disposed".to_owned());
560        f.debug_tuple("Context").field(&name).finish()
561    }
562}