Archived
102 lines
3.7 KiB
Rust
102 lines
3.7 KiB
Rust
//! VBD (Volatility-Based Decomposition) and EBD (Experience-Based Decomposition) layer rules.
|
|
|
|
use crate::error::ArchDiagnostic;
|
|
use crate::rule::{ArchRule, FnContext};
|
|
|
|
// ── VBD-001: Accessor must not call Manager ───────────────────────────────────
|
|
|
|
/// VBD-001: @accessor functions must not call @manager functions.
|
|
/// Accessors are read-only, stable-interface components; they must not depend
|
|
/// on manager-layer orchestration logic.
|
|
pub struct AccessorMustNotCallManager;
|
|
|
|
impl ArchRule for AccessorMustNotCallManager {
|
|
fn name(&self) -> &str { "VBD-001" }
|
|
|
|
fn description(&self) -> &str {
|
|
"@accessor must not call @manager functions (accessor must not depend on manager layer)"
|
|
}
|
|
|
|
fn check(&self, ctx: &FnContext<'_>) -> Vec<ArchDiagnostic> {
|
|
if !ctx.has_annotation("accessor") {
|
|
return vec![];
|
|
}
|
|
ctx.body_calls
|
|
.iter()
|
|
.filter(|call| ctx.callee_has_annotation(&call.callee, "manager"))
|
|
.map(|call| ArchDiagnostic::error(
|
|
self.name(),
|
|
format!(
|
|
"accessor '{}' calls manager '{}' — accessors must not depend on the manager layer",
|
|
ctx.fn_name, call.callee
|
|
),
|
|
Some(ctx.fn_name.to_string()),
|
|
))
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
// ── VBD-002: Experience must not directly call Experience ─────────────────────
|
|
|
|
/// VBD-002 / EBD-001: @experience functions must not call other @experience functions directly.
|
|
/// Experiences should communicate via events, not direct calls, to preserve
|
|
/// loose coupling between user-facing features.
|
|
pub struct ExperienceMustNotCallExperience;
|
|
|
|
impl ArchRule for ExperienceMustNotCallExperience {
|
|
fn name(&self) -> &str { "VBD-002" }
|
|
|
|
fn description(&self) -> &str {
|
|
"@experience must not call another @experience directly (use events instead)"
|
|
}
|
|
|
|
fn check(&self, ctx: &FnContext<'_>) -> Vec<ArchDiagnostic> {
|
|
if !ctx.has_annotation("experience") {
|
|
return vec![];
|
|
}
|
|
ctx.body_calls
|
|
.iter()
|
|
.filter(|call| ctx.callee_has_annotation(&call.callee, "experience"))
|
|
.map(|call| ArchDiagnostic::error(
|
|
self.name(),
|
|
format!(
|
|
"experience '{}' directly calls experience '{}' — use an event instead",
|
|
ctx.fn_name, call.callee
|
|
),
|
|
Some(ctx.fn_name.to_string()),
|
|
))
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
// ── VBD-003: Experience should return Result<T, E> ────────────────────────────
|
|
|
|
/// VBD-003: @experience functions should return Result<T, E> for proper error propagation.
|
|
pub struct ExperienceShouldReturnResult;
|
|
|
|
impl ArchRule for ExperienceShouldReturnResult {
|
|
fn name(&self) -> &str { "VBD-003" }
|
|
|
|
fn description(&self) -> &str {
|
|
"@experience functions should return Result<T, E> for proper error handling"
|
|
}
|
|
|
|
fn check(&self, ctx: &FnContext<'_>) -> Vec<ArchDiagnostic> {
|
|
if !ctx.has_annotation("experience") {
|
|
return vec![];
|
|
}
|
|
// Warn if return type doesn't include "Result"
|
|
if !ctx.return_type_name.contains("Result") {
|
|
return vec![ArchDiagnostic::warning(
|
|
self.name(),
|
|
format!(
|
|
"experience '{}' returns '{}' instead of Result<T, E> — experiences should propagate errors",
|
|
ctx.fn_name, ctx.return_type_name
|
|
),
|
|
Some(ctx.fn_name.to_string()),
|
|
)];
|
|
}
|
|
vec![]
|
|
}
|
|
}
|