ek-core 0.1.0

Business-agnostic plugin host and actor-over-bus runtime on top of ek-event: single-registry plugins, explicit deps with topological start, per-plugin actor workers, one-shot request/reply, panic quarantine, five-step close.
Documentation
//! `PluginContext` —— 插件的唯一出口(put / ask / notify / write_log)。
//!
//! 纪律(P-H2 审核修订):不再暴露 event_engine() 访问器——插件不许自装 handler
//! 绕过 worker/熔断/背压;收信口(on_mail)与发信口(ctx)各只有一个。

use crate::envelope::{e_req_type, e_to_type, Envelope, OneshotErr, ReplyErr};
use crate::error::AskError;
use crate::log_plugin::{LogData, EVENT_LOG};
use crate::main_engine::PluginState;
use crate::registry::Registry;
use ek_event::{Event, EventEngine, PutError};
use std::any::Any;
use std::sync::Arc;
use std::time::Duration;

/// 发放到插件的上下文(on_init 传入;`Clone` 供插件自存)。
#[derive(Clone)]
pub struct PluginContext {
    pub(crate) event: Arc<EventEngine>,
    pub(crate) registry: Arc<Registry>,
    pub(crate) name: String,
}

impl PluginContext {
    pub(crate) fn new(event: Arc<EventEngine>, registry: Arc<Registry>, name: String) -> Self {
        Self { event, registry, name }
    }

    /// 本插件注册名。
    pub fn name(&self) -> &str {
        &self.name
    }

    /// 广播出口:插件自发事件(业务 broadcast / eTo / eLog 均经此)。
    pub fn put(&self, event: Event) -> Result<(), PutError> {
        self.event.put(event)
    }

    /// 日志出口(eLog → 内置 LogPlugin)。
    pub fn write_log(&self, level: u8, msg: &str) {
        let data = LogData {
            level,
            source: self.name.clone(),
            msg: msg.to_string(),
        };
        let _ = self.event.put(Event::with_data(EVENT_LOG, Arc::new(data)));
    }

    /// 定向通知出口:eTo.<target>,无回包;目标不在线时事件被静默分发为空(尽力而为)。
    pub fn notify(&self, target: &str, payload: Arc<dyn Any + Send + Sync>) {
        let _ = self.event.put(Event::with_data(e_to_type(target), payload));
    }

    /// 请求出口:eReq.<target> + Envelope{payload, reply} + std mpsc 一次性信道。
    ///
    /// **禁止自呼**:ask 指向自身 target 会占满自己的 worker 直到超时
    /// (自己阻塞等自己的回包,worker 无法转去处理请求)——需要自身逻辑请直调。
    ///
    /// 时序:预检目标 Running → put → recv_timeout。
    /// - 预检失败(不存在/未 Running/已 Quarantined)→ 立即 `Err(TargetGone)`;
    /// - 回包 `Ok(Res)` 且契约类型匹配 → `Ok(Res)`;
    /// - 回包 `Err(InvalidPayload)` **或** 回包类型与本侧契约不一致 → `Err(InvalidPayload)`;
    /// - 信道断连(handler panic/信封丢失/目标邮箱溢出被丢)→ `Err(TargetGone)`(尽快,不虚耗超时);
    ///   注:目标邮箱溢出时 forwarder 弃整个请求事件,信封随之 drop → 回包信道立即关闭,
    ///   发起方走 TargetGone 快失败;同时宿主侧有溢出丢弃 eLog 节流告警(可追溯);
    /// - 等满时限 → `Err(Timeout)`;
    /// - put 失败(总线已停)→ `Err(BusStopped)`。
    ///
    /// TargetGone 的即时性为「尽力而为」:预检 + 断连快失败覆盖绝大多数场合;
    /// 极端 TOCTOU 窗口(预检后瞬间目标下线且断连未被感知)由 Timeout 兜底。
    pub fn ask<Req, Res>(&self, target: &str, req: Req, timeout: Duration) -> Result<Res, AskError>
    where
        Req: Any + Send + Sync,
        Res: Any + Send + Sync,
    {
        match self.registry.get(target) {
            Some(entry) if entry.state.get() == PluginState::Running => {}
            _ => return Err(AskError::TargetGone { target: target.to_string() }),
        }
        let (envelope, reply_rx) = Envelope::new(Arc::new(req));
        let put = self.event.put(Event::with_data(e_req_type(target), Arc::new(envelope)));
        if put.is_err() {
            return Err(AskError::BusStopped);
        }
        let boxed = reply_rx.recv_timeout(timeout).map_err(|e| match e {
            OneshotErr::Timeout => AskError::Timeout { target: target.to_string() },
            OneshotErr::Closed => AskError::TargetGone { target: target.to_string() },
        })?;
        // 回包为 Box<dyn Any>: downcast 到本侧契约 Result<Res, ReplyErr>
        match boxed.downcast::<Result<Res, ReplyErr>>() {
            Ok(inner) => match *inner {
                Ok(res) => Ok(res),
                Err(_) => Err(AskError::InvalidPayload { target: target.to_string() }),
            },
            // 回包类型与本侧契约不同(双方契约错配)→ 协议违约
            Err(_) => Err(AskError::InvalidPayload { target: target.to_string() }),
        }
    }
}