Skip to main content

homecore_plugins/
plugin.rs

1//! `HomeCorePlugin` trait + `PluginId` newtype.
2//!
3//! Every first-party and third-party HOMECORE integration must implement
4//! `HomeCorePlugin`. P1 provides an in-process native Rust implementation;
5//! the WASM ABI wrapper (which maps the WASM exports `setup_entry`,
6//! `call_service_handler`, `receive_event` to this trait) lands in P2.
7
8use std::fmt;
9
10use async_trait::async_trait;
11use homecore::HomeCore;
12
13use crate::error::PluginError;
14use crate::StateChangedEventJson;
15
16/// Unique identifier for a loaded plugin — mirrors the `domain` field of
17/// the plugin's `PluginManifest` (e.g. `"mqtt"`, `"homecore_lights"`).
18#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub struct PluginId(pub String);
20
21impl PluginId {
22    /// Create a new `PluginId` from any string-like value.
23    pub fn new(s: impl Into<String>) -> Self {
24        Self(s.into())
25    }
26
27    /// Return the inner domain string.
28    pub fn as_str(&self) -> &str {
29        &self.0
30    }
31}
32
33impl fmt::Display for PluginId {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        f.write_str(&self.0)
36    }
37}
38
39/// Lifecycle trait that every HOMECORE integration must implement.
40///
41/// Implementing types are passed to [`PluginRuntime::load`]; the runtime
42/// calls these methods at the appropriate lifecycle points.
43///
44/// # Async
45/// Both methods are `async` to allow network / IO initialisation without
46/// blocking the Tokio runtime. The `async_trait` macro erases the `impl`
47/// return type so it works in trait objects.
48#[async_trait]
49pub trait HomeCorePlugin: Send + Sync + 'static {
50    /// Called once when the plugin's config entry is being set up.
51    ///
52    /// The plugin receives a reference to the `HomeCore` runtime and should
53    /// register its entities, services, and event subscriptions here.
54    async fn setup(&self, hc: HomeCore) -> Result<(), PluginError>;
55
56    /// Receive a committed state change. The default is a no-op so built-in
57    /// plugins only opt into event work they need.
58    async fn state_changed(&self, _event: &StateChangedEventJson) -> Result<(), PluginError> {
59        Ok(())
60    }
61
62    /// Called when the plugin is being removed from the registry.
63    ///
64    /// The plugin should clean up subscriptions and deregister its entities.
65    async fn unload(&self) -> Result<(), PluginError>;
66}