use hunyi::{
ASYNC_EXPOSURE_RULE, AsyncExposureBoundary, DYN_TRAIT_RULE, DynTraitBoundary,
FORBIDDEN_MARKER_RULE, ForbiddenMarkerBoundary, IMPL_TRAIT_RULE, ImplTraitBoundary,
SIGNATURE_RULE, SemanticBoundary, TRAIT_IMPL_RULE, TraitImplBoundary, UNSAFE_CONFINEMENT_RULE,
UnsafeBoundary, VisibilityBoundary,
};
use louke::{RuntimeBoundary, runtime_seam_rule_line};
fn text_section(title: &str, count: usize) -> String {
if count == 0 {
return String::new();
}
let noun = if count == 1 { "boundary" } else { "boundaries" };
format!("{title} {noun} ({count}):\n")
}
fn anchor_line(anchor: Option<&str>) -> String {
match anchor {
Some(anchor) => format!(" anchor: {anchor}\n"),
None => String::new(),
}
}
fn module_block(
severity: &str,
module: &str,
krate: &str,
rule_line: &str,
reason: &str,
) -> String {
format!(
"\n[{severity}] module {module} in {krate}\n rule: {rule_line}\n reason: {reason}\n"
)
}
pub(in crate::runner) fn semantic_text(boundaries: &[SemanticBoundary]) -> String {
if boundaries.is_empty() {
return String::new();
}
let mut out = text_section("Semantic", boundaries.len());
for boundary in boundaries {
let opt_in = if boundary.including_trait_impls() {
" (including trait impls)"
} else {
""
};
let rule_line = format!(
"{}: {}{}",
SIGNATURE_RULE,
boundary.forbidden().join(", "),
opt_in
);
out.push_str(&module_block(
boundary.severity().as_str(),
boundary.module(),
boundary.crate_package(),
&rule_line,
boundary.reason(),
));
out.push_str(&anchor_line(boundary.anchor()));
}
out
}
pub(in crate::runner) fn trait_impl_text(boundaries: &[TraitImplBoundary]) -> String {
if boundaries.is_empty() {
return String::new();
}
let mut out = text_section("Trait-impl-locality", boundaries.len());
for boundary in boundaries {
out.push_str(&format!(
"\n[{}] trait {} in {}\n rule: {} (declared: {})\n reason: {}\n",
boundary.severity().as_str(),
boundary.trait_(),
boundary.crate_package(),
TRAIT_IMPL_RULE,
boundary.allowed_locations().join(", "),
boundary.reason(),
));
out.push_str(&anchor_line(boundary.anchor()));
}
out
}
pub(in crate::runner) fn visibility_text(boundaries: &[VisibilityBoundary]) -> String {
if boundaries.is_empty() {
return String::new();
}
let mut out = text_section("Visibility", boundaries.len());
for boundary in boundaries {
out.push_str(&module_block(
boundary.severity().as_str(),
boundary.module(),
boundary.crate_package(),
boundary.ceiling().rule(),
boundary.reason(),
));
out.push_str(&anchor_line(boundary.anchor()));
}
out
}
pub(in crate::runner) fn forbidden_marker_text(boundaries: &[ForbiddenMarkerBoundary]) -> String {
if boundaries.is_empty() {
return String::new();
}
let mut out = text_section("Forbidden-marker", boundaries.len());
for boundary in boundaries {
out.push_str(&format!(
"\n[{}] subtree {} in {}\n rule: {}: {}\n reason: {}\n",
boundary.severity().as_str(),
boundary.module(),
boundary.crate_package(),
FORBIDDEN_MARKER_RULE,
boundary.forbidden().join(", "),
boundary.reason(),
));
out.push_str(&anchor_line(boundary.anchor()));
}
out
}
pub(in crate::runner) fn dyn_trait_text(boundaries: &[DynTraitBoundary]) -> String {
if boundaries.is_empty() {
return String::new();
}
let mut out = text_section("Dyn-trait", boundaries.len());
for boundary in boundaries {
let rule_line = shape_rule_text(DYN_TRAIT_RULE, boundary.forbidden_operands());
out.push_str(&module_block(
boundary.severity().as_str(),
boundary.module(),
boundary.crate_package(),
&rule_line,
boundary.reason(),
));
out.push_str(&anchor_line(boundary.anchor()));
}
out
}
pub(in crate::runner) fn shape_rule_text(rule: &str, operands: &[String]) -> String {
if operands.is_empty() {
rule.to_string()
} else {
format!("{rule} of: {}", operands.join(", "))
}
}
pub(in crate::runner) fn impl_trait_text(boundaries: &[ImplTraitBoundary]) -> String {
if boundaries.is_empty() {
return String::new();
}
let mut out = text_section("Impl-trait", boundaries.len());
for boundary in boundaries {
let rule_line = shape_rule_text(IMPL_TRAIT_RULE, boundary.forbidden_operands());
out.push_str(&module_block(
boundary.severity().as_str(),
boundary.module(),
boundary.crate_package(),
&rule_line,
boundary.reason(),
));
out.push_str(&anchor_line(boundary.anchor()));
}
out
}
pub(in crate::runner) fn async_exposure_text(boundaries: &[AsyncExposureBoundary]) -> String {
if boundaries.is_empty() {
return String::new();
}
let mut out = text_section("Async-exposure", boundaries.len());
for boundary in boundaries {
let scope = if boundary.including_submodules() {
" (including submodules)"
} else {
""
};
let rule_line = format!("{}{}", ASYNC_EXPOSURE_RULE, scope);
out.push_str(&module_block(
boundary.severity().as_str(),
boundary.module(),
boundary.crate_package(),
&rule_line,
boundary.reason(),
));
out.push_str(&anchor_line(boundary.anchor()));
}
out
}
pub(in crate::runner) fn unsafe_text(boundaries: &[UnsafeBoundary]) -> String {
if boundaries.is_empty() {
return String::new();
}
let mut out = text_section("Unsafe-confinement", boundaries.len());
for boundary in boundaries {
out.push_str(&format!(
"\n[{}] crate {}\n rule: {} (allowed: {})\n reason: {}\n",
boundary.severity().as_str(),
boundary.crate_package(),
UNSAFE_CONFINEMENT_RULE,
boundary.allowed_locations().join(", "),
boundary.reason(),
));
out.push_str(&anchor_line(boundary.anchor()));
}
out
}
pub(in crate::runner) fn runtime_text(boundaries: &[RuntimeBoundary]) -> String {
if boundaries.is_empty() {
return String::new();
}
let mut out = text_section("Runtime", boundaries.len());
for boundary in boundaries {
out.push_str(&format!(
"\n[{}] seam {} (reacts at runtime, not at check)\n rule: {}\n posture: {}\n reason: {}\n",
boundary.severity().as_str(),
boundary.seam(),
runtime_seam_rule_line(boundary.allowed_origins()),
boundary.posture().as_str(),
boundary.reason(),
));
out.push_str(&anchor_line(boundary.anchor()));
}
out
}