use crate::envelope::{Envelope, ReplyErr, e_req_type};
use crate::main_engine::MainEngine;
use crate::plugin::Plugin;
use crate::section::valid_config_ns;
use ek_event::{Event, EventEngine};
use std::any::Any;
use std::sync::Arc;
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Severity {
Fail,
}
#[derive(Debug, Clone)]
pub struct Finding {
pub id: &'static str,
pub severity: Severity,
pub text: String,
}
#[derive(Debug, Default)]
pub struct ShapeReport {
pub findings: Vec<Finding>,
}
impl ShapeReport {
fn fail(&mut self, id: &'static str, text: impl Into<String>) {
self.findings.push(Finding {
id,
severity: Severity::Fail,
text: text.into(),
});
}
#[must_use]
pub fn is_ok(&self) -> bool {
self.findings.is_empty()
}
}
#[must_use]
pub fn shape_audit(plugin: &dyn Plugin, expected_version: &str) -> ShapeReport {
let mut rep = ShapeReport::default();
let name = plugin.name().to_string();
if plugin.version().as_ref() != expected_version {
rep.fail(
"Q1",
format!(
"version() 必须显式覆写为本 crate 版本(七律 6);期望 {expected_version},实得 {}",
plugin.version()
),
);
}
let svcs = plugin.services();
let routes: Vec<String> = plugin.routes().iter().map(|c| c.to_string()).collect();
if routes.is_empty() {
rep.fail("Q2", "routes() 为空 = 无订阅面(至少自报请求线)");
} else {
let mut seen = std::collections::BTreeSet::new();
for r in &routes {
if !seen.insert(r.clone()) {
rep.fail("Q2", format!("routes() 重复订阅 {r}"));
}
}
if !svcs.is_empty() {
let req_line = e_req_type(&name);
if !routes.iter().any(|r| r == &req_line) {
rep.fail(
"Q2",
format!("routes() 必须自报请求线 {req_line};漏报 = 服务请求永远进不来"),
);
}
}
}
let mut ids = std::collections::BTreeSet::new();
for s in &svcs {
let id: &String = &s.id;
if !ids.insert(id.clone()) {
rep.fail("Q3", format!("服务目录重复 {id}"));
}
let expected = format!("{name}.");
if !id.starts_with(&expected) {
rep.fail("Q3", format!("服务全名未挂实例名前缀: {id}"));
}
}
for s in &svcs {
if s.doc.trim().is_empty() {
rep.fail("Q4", format!("服务 {} 描述为空", s.id));
}
}
if let Some(cfg) = plugin.as_configurable() {
if !valid_config_ns(cfg.config_ns()) {
rep.fail("Q5", format!("config_ns 非法: {}", cfg.config_ns()));
}
}
rep
}
struct AuditProbeReq;
#[derive(Debug, Default)]
pub struct DeployReport {
pub findings: Vec<Finding>,
}
impl DeployReport {
fn fail(&mut self, id: &'static str, text: impl Into<String>) {
self.findings.push(Finding {
id,
severity: Severity::Fail,
text: text.into(),
});
}
#[must_use]
pub fn is_ok(&self) -> bool {
self.findings.is_empty()
}
}
pub fn deploy_audit<T: Plugin + 'static>(plugin: Arc<T>) -> DeployReport {
let mut rep = DeployReport::default();
let name = plugin.name().to_string();
let has_service_face = !plugin.services().is_empty();
let ev = Arc::new(EventEngine::new());
let host = MainEngine::new(Some(ev.clone()));
if let Err(e) = host.add_plugin(plugin) {
rep.fail("D1", format!("add_plugin 失败: {e}"));
return rep;
}
if let Err(e) = host.start() {
rep.fail("D2", format!("host.start 失败: {e}"));
return rep;
}
if host.get_state(&name).is_none() {
rep.fail("D3", "实例未在册(装配失败)");
}
if has_service_face {
let (env, rx) = Envelope::new(Arc::new(AuditProbeReq) as Arc<dyn Any + Send + Sync>);
let put = ev.put(Event::with_data(e_req_type(&name), Arc::new(env)));
if let Err(e) = put {
rep.fail("D4", format!("总线投递失败: {e:?}"));
} else {
match rx.recv_timeout(Duration::from_secs(10)) {
Ok(boxed) => {
let typed = boxed.downcast::<Result<(), ReplyErr>>();
match typed {
Ok(inner) => {
if matches!(*inner, Err(ReplyErr::InvalidPayload)) {
} else {
rep.fail(
"D4",
"探针未注册载荷竟然回了非 InvalidPayload 形态(契约应答面失真)",
);
}
}
Err(_) => {
}
}
}
Err(_) => rep.fail("D4", "请求探针超时:请求线未自报(routes 漏报)或分派断"),
}
}
}
host.close(Duration::from_secs(2));
rep
}