api_response/error_code/
tally.rs

1use std::{
2    collections::{BTreeMap, HashSet},
3    thread::LocalKey,
4};
5
6// re-export
7pub use inventory;
8use serde::{Deserialize, Serialize};
9
10use super::{ErrDecl, ErrPath, ErrPathParent, ErrPathRoot, ErrType};
11
12/// Quickly create an `ApiError` builder `ApiErr` and collect error code mode
13/// information.
14#[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
63/// Obtain the list of error code declaration.
64pub 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}