///|
/// ═══ 功能组:diagnostics(诊断策略与聚合,T10)═══
/// DiagnosticPolicy + DiagnosticCollector:让转换诊断从"返回数组"
/// 升级为可运维信号——中间件按策略熔断、按租户统计降级率。
///
/// 背景(架构审计 P1-10):诊断有 Exact/Degraded/Unsupported 分级,
/// 但没有策略控制与聚合;宿主全部自建。本组补上:
/// - 策略:ReportOnly(默认,行为不变)/ FailOnUnsupported /
/// FailOnDegraded——"Unsupported capabilities MUST fail explicitly"
/// 的可配置落地
/// - 聚合:按 status/field 计数、最重诊断、人类可读 summary
///|
/// status 标签(ConversionStatus 无 to_string;集中于此避免散落字面量)
fn status_label(status : @lux.ConversionStatus) -> String {
match status {
Exact => "Exact"
Degraded => "Degraded"
Unsupported => "Unsupported"
Invalid => "Invalid"
}
}
///|
/// DiagnosticPolicy — 诊断处置策略
pub(all) enum DiagnosticPolicy {
/// 只报告不失败(默认;与既有行为完全一致)
ReportOnly
/// 任一 Unsupported(或更严重)诊断即失败
FailOnUnsupported
/// 任一 Degraded(或更严重)诊断即失败
FailOnDegraded
} derive(Eq, Debug)
///|
/// explicit: Eq::equal kept as a regular method (implicit promotion is deprecated, E0079)
pub extend DiagnosticPolicy with Eq::{equal}
///|
#deprecated("Use `!=` instead", skip_current_package=true)
#doc(hidden)
pub extend DiagnosticPolicy with Eq::{not_equal}
///|
#deprecated("Use `Debug::to_repr` instead", skip_current_package=true)
#doc(hidden)
pub extend DiagnosticPolicy with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// 诊断严重度排序(数值越大越重):Exact < Degraded < Unsupported < Invalid
fn status_severity(status : @lux.ConversionStatus) -> Int {
match status {
Exact => 0
Degraded => 1
Unsupported => 2
Invalid => 3
}
}
///|
/// 策略阈值:达到该严重度即失败(ReportOnly 返回 -1 = 不失败)
fn policy_threshold(policy : DiagnosticPolicy) -> Int {
match policy {
ReportOnly => -1
FailOnUnsupported => 2
FailOnDegraded => 1
}
}
///|
/// 策略名(错误消息用)
fn policy_label(policy : DiagnosticPolicy) -> String {
match policy {
ReportOnly => "ReportOnly"
FailOnUnsupported => "FailOnUnsupported"
FailOnDegraded => "FailOnDegraded"
}
}
///|
/// 对「值 + 诊断」执行策略(T10 一次性入口)
///
/// ReportOnly → 原样 Ok;FailOnUnsupported/FailOnDegraded → 存在达到
/// 阈值的诊断时返回 Err(消息含计数与首条诊断详情)。
pub fn[T] enforce_policy(
value : T,
diagnostics : Array[@lux.ConversionDiagnostic],
policy : DiagnosticPolicy,
) -> Result[T, String] {
let threshold = policy_threshold(policy)
if threshold < 0 {
return Ok(value)
}
let mut violations = 0
let mut first_detail : String? = None
for d in diagnostics {
if status_severity(d.status) >= threshold {
violations = violations + 1
if first_detail is None {
first_detail = Some(d.field + ": " + status_label(d.status))
}
}
}
if violations == 0 {
Ok(value)
} else {
Err(
"diagnostic policy " +
policy_label(policy) +
" violated: " +
violations.to_string() +
" diagnostic(s) at or above threshold; first: " +
first_detail.unwrap_or(""),
)
}
}
///|
/// DiagnosticCounts — 按 status 的计数(ConversionStatus 无 Hash 实现,
/// 用专用结构体而非 Map;运维直接读字段)
pub struct DiagnosticCounts {
mut exact : Int
mut degraded : Int
mut unsupported : Int
mut invalid : Int
} derive(Eq, Debug)
///|
/// explicit: Eq::equal kept as a regular method (implicit promotion is deprecated, E0079)
pub extend DiagnosticCounts with Eq::{equal}
///|
#deprecated("Use `!=` instead", skip_current_package=true)
#doc(hidden)
pub extend DiagnosticCounts with Eq::{not_equal}
///|
#deprecated("Use `Debug::to_repr` instead", skip_current_package=true)
#doc(hidden)
pub extend DiagnosticCounts with @moonbitlang/core/debug.Debug::{to_repr}
///|
pub fn DiagnosticCounts::default() -> DiagnosticCounts {
{ exact: 0, degraded: 0, unsupported: 0, invalid: 0, }
}
///|
/// 计数总数
pub fn DiagnosticCounts::total(self : DiagnosticCounts) -> Int {
self.exact + self.degraded + self.unsupported + self.invalid
}
///|
/// DiagnosticCollector — 诊断聚合器(T10)
///
/// 中间件运维视图:跨请求累积诊断,按 status/field 计数,
/// 取最重诊断,输出人类可读 summary。典型用法:
/// ```moonbit nochecheck
/// let collector = DiagnosticCollector::new()
/// collector.add_all(result.diagnostics())
/// // 周期性:
/// log(collector.summary())
/// collector.reset()
/// ```
pub struct DiagnosticCollector {
all : Array[@lux.ConversionDiagnostic]
}
///|
pub fn DiagnosticCollector::new() -> DiagnosticCollector {
{ all: [], }
}
///|
/// 追加单条诊断
pub fn DiagnosticCollector::push(
self : DiagnosticCollector,
diagnostic : @lux.ConversionDiagnostic,
) -> Unit {
self.all.push(diagnostic)
}
///|
/// 追加一组诊断(如 ConversionResult.diagnostics())
pub fn DiagnosticCollector::add_all(
self : DiagnosticCollector,
diagnostics : Array[@lux.ConversionDiagnostic],
) -> Unit {
for d in diagnostics {
self.all.push(d)
}
}
///|
/// 按 status 计数
pub fn DiagnosticCollector::count_by_status(
self : DiagnosticCollector,
) -> DiagnosticCounts {
let counts = DiagnosticCounts::default()
for d in self.all {
match d.status {
Exact => counts.exact = counts.exact + 1
Degraded => counts.degraded = counts.degraded + 1
Unsupported => counts.unsupported = counts.unsupported + 1
Invalid => counts.invalid = counts.invalid + 1
}
}
counts
}
///|
/// 按 field 计数(运维定位热点字段,如 options.previous_response_id)
pub fn DiagnosticCollector::count_by_field(
self : DiagnosticCollector,
field : String,
) -> Int {
let mut n = 0
for d in self.all {
if d.field == field {
n = n + 1
}
}
n
}
///|
/// 诊断总数
pub fn DiagnosticCollector::total(self : DiagnosticCollector) -> Int {
self.all.length()
}
///|
/// 最重诊断的 status(空收集器返回 None)
pub fn DiagnosticCollector::worst_status(
self : DiagnosticCollector,
) -> @lux.ConversionStatus? {
let mut worst : @lux.ConversionStatus? = None
let mut worst_score = -1
for d in self.all {
let score = status_severity(d.status)
if score > worst_score {
worst_score = score
worst = Some(d.status)
}
}
worst
}
///|
/// 对收集的诊断执行策略(周期巡检/熔断判定用)
pub fn DiagnosticCollector::enforce(
self : DiagnosticCollector,
policy : DiagnosticPolicy,
) -> Result[Unit, String] {
match enforce_policy((), self.all, policy) {
Ok(_) => Ok(())
Err(e) => Err(e)
}
}
///|
/// 人类可读摘要(日志/监控用)
pub fn DiagnosticCollector::summary(self : DiagnosticCollector) -> String {
if self.all.length() == 0 {
return "diagnostics: none"
}
let counts = self.count_by_status()
let parts : Array[String] = []
if counts.exact > 0 {
parts.push("Exact=" + counts.exact.to_string())
}
if counts.degraded > 0 {
parts.push("Degraded=" + counts.degraded.to_string())
}
if counts.unsupported > 0 {
parts.push("Unsupported=" + counts.unsupported.to_string())
}
if counts.invalid > 0 {
parts.push("Invalid=" + counts.invalid.to_string())
}
"diagnostics: total=" +
self.all.length().to_string() +
" [" +
parts.join(" ") +
"]"
}
///|
/// 取全部诊断(上报/导出用)
pub fn DiagnosticCollector::diagnostics(
self : DiagnosticCollector,
) -> Array[@lux.ConversionDiagnostic] {
self.all.copy()
}
///|
/// 清空(周期快照后重置)
pub fn DiagnosticCollector::reset(self : DiagnosticCollector) -> Unit {
while self.all.length() > 0 {
ignore(self.all.pop())
}
}