unc_config_utils/
lib.rs

1use std::io::Read;
2
3use json_comments::StripComments;
4
5// strip comments from a JSON string with comments.
6// the comment formats that are supported: //, /* */ and #.
7// json-comments-rs is used
8// check out more details: https://github.com/tmccombs/json-comments-rs/blob/main/src/lib.rs
9pub fn strip_comments_from_json_str(json_str: &String) -> std::io::Result<String> {
10    let mut content_without_comments = String::new();
11    StripComments::new(json_str.as_bytes()).read_to_string(&mut content_without_comments)?;
12    Ok(content_without_comments)
13}
14
15// strip comments from a JSON input with comments.
16pub fn strip_comments_from_json_reader(reader: impl Read) -> impl Read {
17    StripComments::new(reader)
18}
19
20/// errors that arise when loading config files or config semantic checks
21/// config files here include: genesis.json, config.json, node_key.json, validator_key.json
22#[derive(thiserror::Error, Debug)]
23pub enum ValidationError {
24    #[error("config.json semantic issue: {error_message}")]
25    ConfigSemanticsError { error_message: String },
26    #[error("genesis.json semantic issue: {error_message}")]
27    GenesisSemanticsError { error_message: String },
28    #[error("config.json file issue: {error_message}")]
29    ConfigFileError { error_message: String },
30    #[error("genesis.json file issue: {error_message}")]
31    GenesisFileError { error_message: String },
32    #[error("node_key.json file issue: {error_message}")]
33    NodeKeyFileError { error_message: String },
34    #[error("validator_key.json file issue: {error_message}")]
35    ValidatorKeyFileError { error_message: String },
36    #[error("cross config files semantic issue: {error_message}")]
37    CrossFileSematicError { error_message: String },
38}
39
40/// Used to collect errors on the go.
41pub struct ValidationErrors(Vec<ValidationError>);
42
43impl ValidationErrors {
44    pub fn new() -> Self {
45        ValidationErrors(Vec::new())
46    }
47
48    pub fn is_empty(&self) -> bool {
49        self.0.is_empty()
50    }
51
52    pub fn push_errors(&mut self, error: ValidationError) {
53        self.0.push(error)
54    }
55
56    pub fn push_config_semantics_error(&mut self, error_message: String) {
57        self.0.push(ValidationError::ConfigSemanticsError { error_message: error_message })
58    }
59
60    pub fn push_config_file_error(&mut self, error_message: String) {
61        self.0.push(ValidationError::ConfigFileError { error_message: error_message })
62    }
63
64    pub fn push_genesis_semantics_error(&mut self, error_message: String) {
65        self.0.push(ValidationError::GenesisSemanticsError { error_message: error_message })
66    }
67
68    pub fn push_genesis_file_error(&mut self, error_message: String) {
69        self.0.push(ValidationError::GenesisFileError { error_message: error_message })
70    }
71
72    pub fn push_node_key_file_error(&mut self, error_message: String) {
73        self.0.push(ValidationError::NodeKeyFileError { error_message: error_message })
74    }
75
76    pub fn push_validator_key_file_error(&mut self, error_message: String) {
77        self.0.push(ValidationError::ValidatorKeyFileError { error_message: error_message })
78    }
79
80    pub fn push_cross_file_semantics_error(&mut self, error_message: String) {
81        self.0.push(ValidationError::CrossFileSematicError { error_message: error_message })
82    }
83
84    /// only to be used in panic_if_errors()
85    fn generate_final_error_message(&self) -> Option<String> {
86        if self.0.is_empty() {
87            None
88        } else {
89            let mut final_error_message = String::new();
90            for error in &self.0 {
91                final_error_message += "\n";
92
93                match error {
94                    ValidationError::ConfigSemanticsError { error_message }
95                    | ValidationError::GenesisSemanticsError { error_message } => {
96                        // the final error_message is concatenation of GenesisSemanticsError or ConfigSemanticsError's ever seen
97                        // not including the whole error.to_string() makes the final error message less confusing to read
98                        final_error_message += error_message
99                    }
100                    _ => final_error_message += &error.to_string(),
101                };
102            }
103            Some(final_error_message)
104        }
105    }
106
107    /// concatenate all errors of a certain type in one error message
108    /// to be used for error types that tend to appear in multiples, e.g. ConfigSemanticsError and GenesisSemanticsError
109    pub fn generate_error_message_per_type(&self) -> Option<String> {
110        if self.0.is_empty() {
111            None
112        } else {
113            let mut final_error_message = String::new();
114            for error in &self.0 {
115                final_error_message += "\n";
116                final_error_message += &error.to_string();
117            }
118            final_error_message += "\n";
119            Some(final_error_message)
120        }
121    }
122
123    /// only call this function when you want the program to return () or all errors so far
124    /// should only be used when you finished inserting all errors
125    pub fn return_ok_or_error(&self) -> anyhow::Result<()> {
126        if self.0.is_empty() {
127            tracing::info!(target: "config", "All validations have passed!");
128            Ok(())
129        } else {
130            Err(anyhow::Error::msg(format!(
131                "\nThe following config checks failed:{}\nPlease fix the config json files and validate again!",
132                self.generate_final_error_message().unwrap()
133            )))
134        }
135    }
136}