1use core::{fmt, time::Duration};
2use std::io::{BufRead as _, Cursor, Error as IoError};
3
4use chrono::NaiveDate;
5use csv::{ByteRecord, Error as CsvError};
6use duration_str::deserialize_duration;
7use semver::Version;
8use serde::Deserialize;
9use serde_json::{Error as SerdeJsonError, Map, Value};
10
11use crate::formats::json;
12
13#[derive(Deserialize, Debug, Clone)]
15pub struct Info {
16 #[serde(rename = "Name")]
17 pub name: Box<str>,
18 #[serde(rename = "Version")]
19 pub version: Version,
20 #[serde(rename = "Release_date")]
21 pub release_date: NaiveDate,
22 #[serde(rename = "Nbproc")]
23 pub nbproc: usize,
24 #[serde(rename = "Process_num")]
25 pub process_num: usize,
26 #[serde(rename = "Pid")]
27 pub pid: usize,
28 #[serde(rename = "Uptime")]
29 #[serde(deserialize_with = "deserialize_duration")]
30 pub uptime: Duration,
31 #[serde(rename = "Uptime_sec")]
32 pub uptime_sec: usize,
33 }
35
36impl Info {
37 pub fn from_json_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, InfoFromJsonBytesError> {
38 let bytes = bytes.as_ref();
39
40 let output = serde_json::from_slice::<JsonOutput>(bytes)
41 .map_err(InfoFromJsonBytesError::DeOutputFailed)?;
42
43 let map: Map<String, Value> = output
44 .0
45 .into_iter()
46 .map(|x| {
47 let v = match x.field.name.as_ref() {
48 "Release_date" => {
49 Value::from(x.value.as_str().unwrap_or_default().replacen('/', "-", 3))
50 }
51 _ => Value::from(&x.value),
52 };
53
54 (x.field.name.to_string(), v)
55 })
56 .collect();
57
58 serde_json::from_value::<Self>(Value::Object(map)).map_err(InfoFromJsonBytesError::DeFailed)
59 }
60
61 pub fn from_kv_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, InfoFromKvBytesError> {
62 let bytes = bytes.as_ref();
63
64 let cursor = Cursor::new(bytes);
65
66 let list: Vec<(Box<str>, Box<str>)> = cursor
67 .lines()
68 .map(|x| match x {
69 Ok(s) => {
70 let mut split = s.split(": ");
71 let k: Box<str> = split.next().unwrap_or_default().into();
72 let v: Box<str> = split.next().unwrap_or_default().into();
73
74 let v = match k.as_ref() {
75 "Release_date" => v.replacen('/', "-", 3).into(),
76 _ => v,
77 };
78
79 Ok((k, v))
80 }
81 Err(err) => Err(InfoFromKvBytesError::LinesReadFailed(err)),
82 })
83 .collect::<Result<_, _>>()?;
84
85 let header = list.iter().map(|(x, _)| x.as_ref()).collect::<Vec<_>>();
86 let header_record = ByteRecord::from(header);
87
88 let row = list.iter().map(|(_, x)| x.as_ref()).collect::<Vec<_>>();
89 let row_record = ByteRecord::from(row);
90
91 row_record
92 .deserialize::<Self>(Some(&header_record))
93 .map_err(InfoFromKvBytesError::ValueDeFailed)
94 }
95}
96
97#[derive(Debug)]
99pub enum InfoFromJsonBytesError {
100 DeOutputFailed(SerdeJsonError),
101 DeFailed(SerdeJsonError),
102}
103
104impl fmt::Display for InfoFromJsonBytesError {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 write!(f, "{:?}", self)
107 }
108}
109
110impl std::error::Error for InfoFromJsonBytesError {}
111
112#[derive(Debug)]
114pub enum InfoFromKvBytesError {
115 LinesReadFailed(IoError),
116 ValueDeFailed(CsvError),
117}
118
119impl fmt::Display for InfoFromKvBytesError {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 write!(f, "{:?}", self)
122 }
123}
124
125impl std::error::Error for InfoFromKvBytesError {}
126
127#[derive(Deserialize, Debug, Clone)]
131pub struct JsonOutput(pub Vec<JsonOutputItem>);
132
133#[derive(Deserialize, Debug, Clone)]
134pub struct JsonOutputItem {
135 pub field: json::Field,
136 #[serde(rename = "processNum")]
137 pub process_num: usize,
138 pub tags: json::Tags,
139 pub value: json::Value,
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn test_info_from_json_bytes() {
148 let bytes = include_bytes!("../tests/files/2_5_5_show_info.json");
149
150 let info = Info::from_json_bytes(bytes).unwrap();
151
152 println!("{:?}", info);
153
154 assert_eq!(info.name, "HAProxy".into());
155 }
156
157 #[test]
158 fn test_info_from_kv_bytes() {
159 let bytes = include_bytes!("../tests/files/2_5_5_show_info.txt");
160
161 let info = Info::from_kv_bytes(bytes).unwrap();
162
163 assert_eq!(info.name, "HAProxy".into());
164 }
165}