scepter 0.1.5

Composable primitives for planet-scale time-series routing, indexing, and aggregation.
Documentation
/// Type of degradation observed while executing a distributed query.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IssueKind {
    /// A zone missed its soft deadline and was skipped.
    PrunedZone,
    /// A child timed out.
    TimedOut,
    /// Configuration was served from a stale cache.
    StaleConfig,
    /// No usable replica was found for a requested range.
    MissingReplica,
    /// A child returned incomplete data.
    IncompleteData,
}

/// One child-level query health issue.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChildIssue<ChildId> {
    /// Affected child, such as a zone or leaf ID.
    pub child: ChildId,
    /// Issue kind.
    pub kind: IssueKind,
    /// Human-readable detail suitable for logs or user-facing diagnostics.
    pub detail: String,
}

impl<ChildId> ChildIssue<ChildId> {
    /// Creates a child issue.
    pub fn new(child: ChildId, kind: IssueKind, detail: impl Into<String>) -> Self {
        Self {
            child,
            kind,
            detail: detail.into(),
        }
    }
}

/// Health metadata for a distributed query result.
#[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> {
    /// Creates empty health metadata.
    pub fn new() -> Self {
        Self::default()
    }

    /// Creates health metadata with an expected child count.
    pub fn with_expected_children(expected_children: usize) -> Self {
        Self {
            expected_children,
            completed_children: 0,
            issues: Vec::new(),
        }
    }

    /// Returns the expected child count.
    pub fn expected_children(&self) -> usize {
        self.expected_children
    }

    /// Returns the completed child count.
    pub fn completed_children(&self) -> usize {
        self.completed_children
    }

    /// Returns all recorded issues.
    pub fn issues(&self) -> &[ChildIssue<ChildId>] {
        &self.issues
    }

    /// Records a completed child response.
    pub fn record_completed(&mut self) {
        self.completed_children += 1;
    }

    /// Records an issue.
    pub fn record_issue(&mut self, issue: ChildIssue<ChildId>) {
        self.issues.push(issue);
    }

    /// Records an issue by parts.
    pub fn push_issue(&mut self, child: ChildId, kind: IssueKind, detail: impl Into<String>) {
        self.record_issue(ChildIssue::new(child, kind, detail));
    }

    /// Returns true if any issue has been recorded or some expected children
    /// did not complete.
    pub fn is_partial(&self) -> bool {
        !self.issues.is_empty() || self.completed_children < self.expected_children
    }

    /// Returns the completed-child ratio in `0.0..=1.0`.
    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);
    }
}