#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IssueKind {
PrunedZone,
TimedOut,
StaleConfig,
MissingReplica,
IncompleteData,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChildIssue<ChildId> {
pub child: ChildId,
pub kind: IssueKind,
pub detail: String,
}
impl<ChildId> ChildIssue<ChildId> {
pub fn new(child: ChildId, kind: IssueKind, detail: impl Into<String>) -> Self {
Self {
child,
kind,
detail: detail.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QueryHealth<ChildId> {
expected_children: usize,
completed_children: usize,
issues: Vec<ChildIssue<ChildId>>,
}
impl<ChildId> Default for QueryHealth<ChildId> {
fn default() -> Self {
Self {
expected_children: 0,
completed_children: 0,
issues: Vec::new(),
}
}
}
impl<ChildId> QueryHealth<ChildId> {
pub fn new() -> Self {
Self::default()
}
pub fn with_expected_children(expected_children: usize) -> Self {
Self {
expected_children,
completed_children: 0,
issues: Vec::new(),
}
}
pub fn expected_children(&self) -> usize {
self.expected_children
}
pub fn completed_children(&self) -> usize {
self.completed_children
}
pub fn issues(&self) -> &[ChildIssue<ChildId>] {
&self.issues
}
pub fn record_completed(&mut self) {
self.completed_children += 1;
}
pub fn record_issue(&mut self, issue: ChildIssue<ChildId>) {
self.issues.push(issue);
}
pub fn push_issue(&mut self, child: ChildId, kind: IssueKind, detail: impl Into<String>) {
self.record_issue(ChildIssue::new(child, kind, detail));
}
pub fn is_partial(&self) -> bool {
!self.issues.is_empty() || self.completed_children < self.expected_children
}
pub fn completeness(&self) -> f64 {
if self.expected_children == 0 {
return 1.0;
}
(self.completed_children as f64 / self.expected_children as f64).min(1.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn query_health_tracks_partial_results() {
let mut health = QueryHealth::with_expected_children(3);
health.record_completed();
health.push_issue("zone-a", IssueKind::PrunedZone, "soft deadline elapsed");
assert!(health.is_partial());
assert_eq!(health.expected_children(), 3);
assert_eq!(health.completed_children(), 1);
assert_eq!(health.completeness(), 1.0 / 3.0);
assert_eq!(health.issues()[0].kind, IssueKind::PrunedZone);
}
#[test]
fn query_health_is_complete_when_all_children_finish_without_issues() {
let mut health = QueryHealth::<&str>::with_expected_children(2);
health.record_completed();
health.record_completed();
assert!(!health.is_partial());
assert_eq!(health.completeness(), 1.0);
}
}