walker_common/report/
stats.rs1use std::cmp::max;
2use std::fs::File;
3use std::io::ErrorKind;
4use std::path::Path;
5use time::OffsetDateTime;
6
7#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
8pub struct ReportStatistics {
9 #[serde(with = "time::serde::rfc3339")]
11 pub last_run: time::OffsetDateTime,
12
13 #[serde(default)]
14 pub entries: Vec<Record>,
15}
16
17#[derive(Debug, thiserror::Error)]
18pub enum Error {
19 #[error(transparent)]
20 Serde(#[from] serde_json::Error),
21 #[error(transparent)]
22 Io(#[from] std::io::Error),
23}
24
25impl ReportStatistics {
26 pub fn load(path: impl AsRef<Path>) -> Result<Self, Error> {
27 Ok(serde_json::from_reader(File::open(path)?)?)
28 }
29
30 pub fn store(&self, path: impl AsRef<Path>) -> Result<(), Error> {
31 Ok(serde_json::to_writer(File::create(path)?, self)?)
32 }
33}
34
35#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
36pub struct Record {
37 #[serde(with = "time::serde::rfc3339")]
39 pub timestamp: time::OffsetDateTime,
40
41 pub total: usize,
43
44 pub errors: usize,
46 pub total_errors: usize,
48 pub warnings: usize,
50 pub total_warnings: usize,
52}
53
54pub fn record(path: impl AsRef<Path>, record: Record) -> Result<(), Error> {
55 let mut stats = match ReportStatistics::load(&path) {
58 Ok(stats) => stats,
59 Err(Error::Io(err)) if err.kind() == ErrorKind::NotFound => ReportStatistics {
60 last_run: OffsetDateTime::now_utc(),
61 entries: vec![],
62 },
63 Err(err) => return Err(err),
64 };
65
66 stats.last_run = max(stats.last_run, record.timestamp);
69
70 let pos = stats
73 .entries
74 .binary_search_by_key(&record.timestamp, |entry| entry.timestamp)
75 .unwrap_or_else(|e| e);
76 stats.entries.insert(pos, record);
77
78 stats.store(path)?;
81
82 Ok(())
85}
86
87pub struct Statistics {
88 pub total: usize,
89 pub errors: usize,
90 pub total_errors: usize,
91 pub warnings: usize,
92 pub total_warnings: usize,
93}
94
95pub fn record_now(stats_file: Option<&Path>, stats: Statistics) -> Result<(), Error> {
97 if let Some(statistics) = &stats_file {
98 let Statistics {
99 total,
100 errors,
101 total_errors,
102 warnings,
103 total_warnings,
104 } = stats;
105
106 record(
107 statistics,
108 Record {
109 timestamp: OffsetDateTime::now_utc(),
110 total,
111 errors,
112 total_errors,
113 warnings,
114 total_warnings,
115 },
116 )?;
117 }
118
119 Ok(())
120}