Skip to main content

oxihuman_core/
result_stack.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Stack of operation results for accumulating and inspecting outcomes.
6
7/// A single result entry.
8#[derive(Debug, Clone, PartialEq)]
9pub enum ResultKind {
10    Ok,
11    Err,
12    Skipped,
13}
14
15#[derive(Debug, Clone)]
16pub struct ResultEntry {
17    pub kind: ResultKind,
18    pub message: String,
19    pub code: i32,
20}
21
22/// Stack accumulating operation results.
23pub struct ResultStack {
24    entries: Vec<ResultEntry>,
25    ok_count: usize,
26    err_count: usize,
27    skip_count: usize,
28}
29
30#[allow(dead_code)]
31impl ResultStack {
32    pub fn new() -> Self {
33        ResultStack {
34            entries: Vec::new(),
35            ok_count: 0,
36            err_count: 0,
37            skip_count: 0,
38        }
39    }
40
41    pub fn push_ok(&mut self, msg: &str) {
42        self.entries.push(ResultEntry {
43            kind: ResultKind::Ok,
44            message: msg.to_string(),
45            code: 0,
46        });
47        self.ok_count += 1;
48    }
49
50    pub fn push_err(&mut self, msg: &str, code: i32) {
51        self.entries.push(ResultEntry {
52            kind: ResultKind::Err,
53            message: msg.to_string(),
54            code,
55        });
56        self.err_count += 1;
57    }
58
59    pub fn push_skipped(&mut self, msg: &str) {
60        self.entries.push(ResultEntry {
61            kind: ResultKind::Skipped,
62            message: msg.to_string(),
63            code: 0,
64        });
65        self.skip_count += 1;
66    }
67
68    pub fn pop(&mut self) -> Option<ResultEntry> {
69        let entry = self.entries.pop()?;
70        match entry.kind {
71            ResultKind::Ok => self.ok_count -= 1,
72            ResultKind::Err => self.err_count -= 1,
73            ResultKind::Skipped => self.skip_count -= 1,
74        }
75        Some(entry)
76    }
77
78    pub fn peek(&self) -> Option<&ResultEntry> {
79        self.entries.last()
80    }
81
82    pub fn has_errors(&self) -> bool {
83        self.err_count > 0
84    }
85
86    pub fn all_ok(&self) -> bool {
87        self.err_count == 0 && self.skip_count == 0 && self.ok_count > 0
88    }
89
90    pub fn ok_count(&self) -> usize {
91        self.ok_count
92    }
93
94    pub fn err_count(&self) -> usize {
95        self.err_count
96    }
97
98    pub fn skip_count(&self) -> usize {
99        self.skip_count
100    }
101
102    pub fn len(&self) -> usize {
103        self.entries.len()
104    }
105
106    pub fn is_empty(&self) -> bool {
107        self.entries.is_empty()
108    }
109
110    pub fn clear(&mut self) {
111        self.entries.clear();
112        self.ok_count = 0;
113        self.err_count = 0;
114        self.skip_count = 0;
115    }
116
117    pub fn errors(&self) -> Vec<&ResultEntry> {
118        self.entries
119            .iter()
120            .filter(|e| e.kind == ResultKind::Err)
121            .collect()
122    }
123
124    pub fn to_summary(&self) -> String {
125        format!(
126            "ok={} err={} skip={}",
127            self.ok_count, self.err_count, self.skip_count
128        )
129    }
130}
131
132impl Default for ResultStack {
133    fn default() -> Self {
134        Self::new()
135    }
136}
137
138pub fn new_result_stack() -> ResultStack {
139    ResultStack::new()
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn push_ok_increments() {
148        let mut s = new_result_stack();
149        s.push_ok("done");
150        assert_eq!(s.ok_count(), 1);
151        assert!(!s.has_errors());
152    }
153
154    #[test]
155    fn push_err_tracked() {
156        let mut s = new_result_stack();
157        s.push_err("fail", -1);
158        assert!(s.has_errors());
159        assert_eq!(s.err_count(), 1);
160    }
161
162    #[test]
163    fn push_skipped() {
164        let mut s = new_result_stack();
165        s.push_skipped("skip me");
166        assert_eq!(s.skip_count(), 1);
167    }
168
169    #[test]
170    fn pop_updates_counts() {
171        let mut s = new_result_stack();
172        s.push_ok("a");
173        s.pop();
174        assert_eq!(s.ok_count(), 0);
175    }
176
177    #[test]
178    fn all_ok_flag() {
179        let mut s = new_result_stack();
180        s.push_ok("a");
181        s.push_ok("b");
182        assert!(s.all_ok());
183        s.push_err("bad", 1);
184        assert!(!s.all_ok());
185    }
186
187    #[test]
188    fn errors_filter() {
189        let mut s = new_result_stack();
190        s.push_ok("ok");
191        s.push_err("e1", 1);
192        s.push_err("e2", 2);
193        assert_eq!(s.errors().len(), 2);
194    }
195
196    #[test]
197    fn clear_resets() {
198        let mut s = new_result_stack();
199        s.push_ok("a");
200        s.push_err("b", 1);
201        s.clear();
202        assert!(s.is_empty());
203        assert_eq!(s.ok_count(), 0);
204    }
205
206    #[test]
207    fn summary_string() {
208        let mut s = new_result_stack();
209        s.push_ok("a");
210        s.push_err("b", 1);
211        s.push_skipped("c");
212        let summary = s.to_summary();
213        assert!(summary.contains("ok=1"));
214        assert!(summary.contains("err=1"));
215    }
216
217    #[test]
218    fn peek_last() {
219        let mut s = new_result_stack();
220        s.push_ok("first");
221        s.push_err("second", 2);
222        assert_eq!(s.peek().expect("should succeed").kind, ResultKind::Err);
223    }
224}