api_response/error_code/
tally.rs1use std::{
2 collections::{BTreeMap, HashSet},
3 thread::LocalKey,
4};
5
6pub use inventory;
8use serde::{Deserialize, Serialize};
9
10use super::{ErrDecl, ErrPath, ErrPathParent, ErrPathRoot, ErrType};
11
12#[macro_export]
15macro_rules! api_err {
16 ($err_decl:expr) => {{
17 $crate::error_code::tally::inventory::submit! {
18 $err_decl
19 }
20 $err_decl.api_error()
21 }};
22 ($err_type:expr, & $local_key_err_path:expr) => {{
23 $crate::error_code::tally::inventory::submit! {
24 $crate::error_code::tally::LocalKeyErrDecl::new($err_type, &$local_key_err_path)
25 }
26 $err_type | &$local_key_err_path
27 }};
28 ($err_type:expr, $new_text:expr, & $local_key_err_path:expr) => {{
29 $crate::error_code::tally::inventory::submit! {
30 $crate::error_code::tally::LocalKeyErrDecl::new($err_type.with_text($new_text), &$local_key_err_path)
31 }
32 ($err_type | $new_text) | &$local_key_err_path
33 }};
34
35 ($err_type:expr, $err_path:expr) => {{
36 $crate::error_code::tally::inventory::submit! {
37 $err_type.declare($err_path)
38 }
39 $err_type | &$err_path
40 }};
41 ($err_type:expr, $new_text:expr, $err_path:expr) => {{
42 $crate::error_code::tally::inventory::submit! {
43 $err_type.with_text($new_text).declare($err_path)
44 }
45 ($err_type | $new_text) | &$err_path
46 }};
47}
48
49#[non_exhaustive]
50pub struct LocalKeyErrDecl {
51 err_type: ErrType,
52 err_path: &'static LocalKey<ErrPath>,
53}
54impl LocalKeyErrDecl {
55 pub const fn new(err_type: ErrType, err_path: &'static LocalKey<ErrPath>) -> Self {
56 Self { err_type, err_path }
57 }
58}
59
60inventory::collect!(ErrDecl);
61inventory::collect!(LocalKeyErrDecl);
62
63pub fn tally_err_decl() -> ErrDeclTally {
65 let total = inventory::iter::<ErrDecl>
66 .into_iter()
67 .map(ToOwned::to_owned)
68 .chain(
69 inventory::iter::<LocalKeyErrDecl>
70 .into_iter()
71 .map(|v| v.err_type + v.err_path),
72 )
73 .collect();
74 ErrDeclTally { total }
75}
76
77#[derive(Debug)]
78#[non_exhaustive]
79pub struct ErrDeclTally {
80 total: Vec<ErrDecl>,
81}
82
83pub type ErrDeclTree =
84 BTreeMap<ErrPathRoot, BTreeMap<ErrPathParent, BTreeMap<ErrPath, BTreeMap<ErrType, HashSet<ErrDecl>>>>>;
85
86pub type ErrDeclTreeText = BTreeMap<String, BTreeMap<String, BTreeMap<String, BTreeMap<String, HashSet<String>>>>>;
87
88#[derive(Serialize, Deserialize)]
89#[allow(clippy::exhaustive_structs)]
90pub struct KV<T> {
91 pub key: String,
92 pub value: T,
93}
94
95pub type ErrDeclVecText = Vec<KV<Vec<KV<Vec<KV<Vec<KV<HashSet<String>>>>>>>>>;
96
97impl ErrDeclTally {
98 pub const fn total(&self) -> &Vec<ErrDecl> {
99 &self.total
100 }
101 pub fn unique(&self) -> Vec<ErrDecl> {
102 let mut seen = HashSet::new();
103 let mut unique = self.total.clone();
104 unique.retain(|v| seen.insert(v.to_string()));
105 unique
106 }
107 pub fn tree(&self) -> ErrDeclTree {
108 let mut b_tree_map: ErrDeclTree = BTreeMap::new();
109 let unique = self.unique();
110 for ele in unique {
111 let curr = ele.err_path();
112 let parent = curr.parent();
113 let root = parent.root();
114 let typ = ele.err_type();
115 b_tree_map
116 .entry(root)
117 .or_default()
118 .entry(parent)
119 .or_default()
120 .entry(*curr)
121 .or_default()
122 .entry(*typ)
123 .or_default()
124 .insert(ele);
125 }
126 b_tree_map
127 }
128 pub fn text_tree(&self) -> ErrDeclTreeText {
129 let mut b_tree_map: ErrDeclTreeText = BTreeMap::new();
130 let unique = self.unique();
131 for ele in unique {
132 let curr = ele.err_path();
133 let parent = curr.parent();
134 let root = parent.root();
135 let brief = ele.extract();
136 b_tree_map
137 .entry(format!("X{:02}({})", root.flag(), root.name()))
138 .or_default()
139 .entry(format!("Y{:02}({})", parent.flag(), parent.name()))
140 .or_default()
141 .entry(format!("Z{:02}({})", curr.flag(), curr.name()))
142 .or_default()
143 .entry(format!("ErrCode({})", brief.code()))
144 .or_default()
145 .insert(brief.message().to_owned());
146 }
147 b_tree_map
148 }
149 pub fn json(&self) -> String {
150 unsafe { serde_json::to_string_pretty(&self.text_tree()).unwrap_unchecked() }
151 }
152 #[allow(clippy::shadow_reuse, clippy::shadow_unrelated)]
153 pub fn text_vec(&self) -> ErrDeclVecText {
154 self.text_tree()
155 .iter()
156 .map(|(k, v)| KV {
157 key: k.clone(),
158 value: v
159 .iter()
160 .map(|(k, v)| KV {
161 key: k.clone(),
162 value: v
163 .iter()
164 .map(|(k, v)| KV {
165 key: k.clone(),
166 value: v
167 .iter()
168 .map(|(k, v)| KV {
169 key: k.clone(),
170 value: v.clone(),
171 })
172 .collect(),
173 })
174 .collect(),
175 })
176 .collect(),
177 })
178 .collect()
179 }
180 #[allow(clippy::missing_panics_doc, clippy::unwrap_used)]
181 pub fn xml(&self) -> String {
182 let mut writer = String::new();
183 let mut ser = quick_xml::se::Serializer::with_root(&mut writer, Some("ErrorDeclarations")).unwrap();
184 ser.indent(' ', 2);
185 self.text_vec().serialize(ser).unwrap();
186 writer
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use crate::{
193 ApiError,
194 error_code::{
195 ErrDecl, ErrPath, ErrPathParent, ErrPathRoot, ErrType,
196 tally::{ErrDeclTally, tally_err_decl},
197 },
198 };
199
200 #[test]
201 fn macro_api_err() {
202 const ET: ErrType = ErrType::T1100("The operation was cancelled.");
203 const EP_LV1: ErrPathRoot = ErrPathRoot::X00("product");
204 const EP_LV2: ErrPathParent = EP_LV1.Y01("system");
205 const EP_LV3: ErrPath = EP_LV2.Z20("module");
206 const EC: ErrDecl = ErrDecl::new(ET, EP_LV3);
207
208 let ae0: ApiError = api_err!(EC);
209 assert_eq!("The operation was cancelled. ErrCode(1100000120)", ae0.to_string());
210 let ae1: ApiError = api_err!(ET, EP_LV3);
211 let _ = api_err!(ET, EP_LV3);
212 assert_eq!("The operation was cancelled. ErrCode(1100000120)", ae1.to_string());
213 let ae2: ApiError = api_err!(ET, "This is new message.", EP_LV3);
214 assert_eq!("This is new message. ErrCode(1100000120)", ae2.to_string());
215
216 thread_local! {static EP_LV3_1:ErrPath = ErrPathRoot::X01("product-2").Y01("system-2").Z02("module-2")}
217 let ae3: ApiError = api_err!(ET, &EP_LV3_1);
218 assert_eq!("The operation was cancelled. ErrCode(1100010102)", ae3.to_string());
219 let ae4: ApiError = api_err!(ET, "This is new message-2.", &EP_LV3_1);
220 assert_eq!("This is new message-2. ErrCode(1100010102)", ae4.to_string());
221
222 let s = format!("{:?}", tally_err_decl());
223 println!("{s}");
224
225 let tally: ErrDeclTally = tally_err_decl();
226 for err_decl in tally.unique() {
227 println!("{err_decl}");
228 }
229 assert_eq!(tally.unique().len(), 4);
230 assert_eq!(tally.total().len(), 6);
231 println!("{}", tally.json());
232 println!("{}", tally.xml());
233 }
234}