Skip to main content

cordis/
service.rs

1//! Typed service conventions and helpers.
2
3use crate::context::Context;
4use crate::effect::EffectHandle;
5use crate::registry::{Inject, PluginHandle, PluginOutput, plugin_async, plugin_sync};
6use crate::{Result, Value};
7use std::future::Future;
8use std::sync::Arc;
9
10/// Marker and availability contract for named Cordis services.
11///
12/// The TypeScript base class registers itself from its constructor. Rust does
13/// not have inheritable constructors, so service values implement this trait
14/// and are installed through [`Context::provide_service`]. Their lifetime is
15/// still owned by the current plugin fiber.
16pub trait Service: Send + Sync + 'static {
17    /// Context property/service key.
18    const NAME: &'static str;
19
20    /// Whether dependent plugins may currently use this service.
21    fn is_available(&self) -> bool {
22        true
23    }
24}
25
26impl Context {
27    /// Provide a typed service under [`Service::NAME`].
28    pub fn provide_service<S>(&self, service: S) -> Result<EffectHandle>
29    where
30        S: Service,
31    {
32        self.provide_service_arc(Arc::new(service))
33    }
34
35    /// Provide an existing shared typed service.
36    pub fn provide_service_arc<S>(&self, service: Arc<S>) -> Result<EffectHandle>
37    where
38        S: Service,
39    {
40        let value = Value::from_arc(service);
41        let check = Arc::new(|value: &Value| {
42            value
43                .as_any()
44                .downcast_ref::<S>()
45                .map(|service| service.is_available())
46                .unwrap_or(false)
47        });
48        self.reflect()
49            .provide_value(S::NAME.to_owned(), value, Some(check))
50    }
51
52    /// Resolve service intercepts from root to leaf with a caller-supplied
53    /// merge operation.
54    pub fn resolve_service_config<T, F>(&self, name: &str, base: T, mut merge: F) -> Result<T>
55    where
56        T: Send + Sync + 'static,
57        F: FnMut(T, &T) -> T,
58    {
59        let mut output = base;
60        for config in self.intercepts::<T>(name)? {
61            output = merge(output, config.as_ref());
62        }
63        Ok(output)
64    }
65}
66
67/// Build a synchronous class-service-style plugin.
68///
69/// The constructor's returned service is automatically provided and removed
70/// with the plugin fiber.
71pub fn service_sync<S, C, F>(
72    name: impl Into<String>,
73    inject: Inject,
74    constructor: F,
75) -> PluginHandle
76where
77    S: Service,
78    C: Send + Sync + 'static,
79    F: Fn(Context, Arc<C>) -> Result<S> + Send + Sync + 'static,
80{
81    plugin_sync(name, inject, move |ctx, config| {
82        let service = constructor(ctx.clone(), config)?;
83        ctx.provide_service(service)?;
84        Ok(PluginOutput::none())
85    })
86}
87
88/// Build an asynchronous class-service-style plugin.
89pub fn service_async<S, C, F, Fut>(
90    name: impl Into<String>,
91    inject: Inject,
92    constructor: F,
93) -> PluginHandle
94where
95    S: Service,
96    C: Send + Sync + 'static,
97    F: Fn(Context, Arc<C>) -> Fut + Send + Sync + 'static,
98    Fut: Future<Output = Result<S>> + Send + 'static,
99{
100    let constructor = Arc::new(constructor);
101    plugin_async(name, inject, move |ctx, config| {
102        let constructor = constructor.clone();
103        async move {
104            let service = constructor(ctx.clone(), config).await?;
105            ctx.provide_service(service)?;
106            Ok(PluginOutput::none())
107        }
108    })
109}