use super::*;
#[derive(Clone, Debug)]
pub struct RegistryError {
node: NodeId,
path: String,
source: DiagnosticSource,
message: String,
source_chain: Vec<ProvenanceStep>,
call_path: Vec<CallSite>,
registration_chain: Vec<RegistrationState>,
children: Vec<RegistryError>,
}
pub type RegistryResult<T> = Result<T, Box<RegistryError>>;
#[derive(Clone, Debug)]
pub struct RegistrationState {
pub node: NodeId,
pub path: String,
pub source: DiagnosticSource,
pub state: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DiagnosticSource {
Static(SourceLocation),
Owned(OwnedSourceLocation),
}
impl DiagnosticSource {
pub fn function(&self) -> &str {
match self {
Self::Static(source) => source.function,
Self::Owned(source) => &source.function,
}
}
}
impl From<SourceLocation> for DiagnosticSource {
fn from(source: SourceLocation) -> Self {
Self::Static(source)
}
}
impl From<OwnedSourceLocation> for DiagnosticSource {
fn from(source: OwnedSourceLocation) -> Self {
Self::Owned(source)
}
}
impl fmt::Display for DiagnosticSource {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Static(source) => source.fmt(formatter),
Self::Owned(source) => source.fmt(formatter),
}
}
}
impl RegistryError {
pub fn new(
node: NodeId,
path: impl Into<String>,
source: impl Into<DiagnosticSource>,
message: impl Into<String>,
) -> Self {
Self {
node,
path: path.into(),
source: source.into(),
message: message.into(),
source_chain: Vec::new(),
call_path: Vec::new(),
registration_chain: Vec::new(),
children: Vec::new(),
}
}
pub fn node(&self) -> NodeId {
self.node
}
pub fn path(&self) -> &str {
&self.path
}
pub fn source(&self) -> &DiagnosticSource {
&self.source
}
pub fn message(&self) -> &str {
&self.message
}
pub fn children(&self) -> &[RegistryError] {
&self.children
}
pub fn with_children(mut self, children: Vec<RegistryError>) -> Self {
self.children = children;
self
}
pub fn children_mut(&mut self) -> &mut Vec<RegistryError> {
&mut self.children
}
pub fn message_mut(&mut self) -> &mut String {
&mut self.message
}
pub fn source_chain_mut(&mut self) -> &mut Vec<ProvenanceStep> {
&mut self.source_chain
}
pub fn call_path_mut(&mut self) -> &mut Vec<CallSite> {
&mut self.call_path
}
pub fn registration_chain_mut(&mut self) -> &mut Vec<RegistrationState> {
&mut self.registration_chain
}
fn render_at(&self, depth: usize, output: &mut String) {
let indent = " ".repeat(depth);
writeln!(
output,
"{indent}{} {} [{}] branch={} function={}",
self.node,
self.path,
self.source,
self.path,
self.source.function()
)
.unwrap();
writeln!(output, "{indent}+-- error: {}", self.message).unwrap();
if !self.registration_chain.is_empty() {
writeln!(output, "{indent}+-- registration chain:").unwrap();
for (index, state) in self.registration_chain.iter().enumerate() {
let branch = if index + 1 == self.registration_chain.len() {
"`--"
} else {
"|--"
};
writeln!(
output,
"{indent}| {branch} {} {} [{}] function={} {}",
state.node,
state.path,
state.source,
state.source.function(),
state.state
)
.unwrap();
}
}
if !self.source_chain.is_empty() {
writeln!(output, "{indent}+-- source chain:").unwrap();
for (index, step) in self.source_chain.iter().enumerate() {
let branch = if index + 1 == self.source_chain.len() {
"`--"
} else {
"|--"
};
writeln!(
output,
"{indent}| {branch} {} {}::{} => {}",
step.node, step.object, step.operation, step.value
)
.unwrap();
}
}
if !self.call_path.is_empty() {
write!(output, "{indent}+-- call path: ").unwrap();
for (index, call) in self.call_path.iter().enumerate() {
if index > 0 {
output.push_str(" -> ");
}
write!(output, "{} {}", call.node, call.function).unwrap();
}
output.push('\n');
let located_calls = self
.call_path
.iter()
.filter_map(|call| call.source.map(|source| (call, source)))
.collect::<Vec<_>>();
if !located_calls.is_empty() {
writeln!(output, "{indent}+-- call sites:").unwrap();
for (index, (call, source)) in located_calls.iter().enumerate() {
let branch = if index + 1 == located_calls.len() {
"`--"
} else {
"|--"
};
writeln!(
output,
"{indent}| {branch} {} {} at {}",
call.node, call.function, source
)
.unwrap();
}
}
}
for child in &self.children {
child.render_at(depth + 1, output);
}
}
}
impl fmt::Display for RegistryError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut output = String::new();
self.render_at(0, &mut output);
formatter.write_str(&output)
}
}
impl std::error::Error for RegistryError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_error_still_exposes_the_facts_a_host_must_report() {
let node = NodeId::from_raw([7; 16]);
let source = SourceLocation {
file: "button.rs",
line: 3,
column: 1,
function: "Button",
};
let child = RegistryError::new(
node,
"root/control/button",
source,
"check `non_empty_text` failed",
);
let error = RegistryError::new(
node,
"root/control/button",
source,
"runtime health check failed",
)
.with_children(vec![child]);
assert_eq!(error.node(), node);
assert_eq!(error.path(), "root/control/button");
assert_eq!(error.source().function(), "Button");
assert_eq!(error.message(), "runtime health check failed");
assert_eq!(error.children().len(), 1);
assert_eq!(
error.children()[0].message(),
"check `non_empty_text` failed"
);
}
}