use std::path::Path;
use tracing::error;
use validator::{Validate, ValidationError, ValidationErrors, ValidationErrorsKind};
use crate::secrets::Sensitive;
use crate::ConfigError;
pub fn create_validation_error(
error_msg: String,
validate_code: &'static str,
validate_error_msg: &'static str,
) -> ValidationError {
error!(error_msg);
let mut error = ValidationError::new(validate_code);
error.message = Some(validate_error_msg.into());
error
}
pub fn validate_ascii(name: &impl ToString) -> Result<(), ValidationError> {
if !name.to_string().is_ascii() {
return Err(ValidationError::new("The value is not ASCII"));
}
Ok(())
}
pub fn validate_path_exists(file_path: &Path) -> Result<(), ValidationError> {
if !file_path.exists() {
let mut error = ValidationError::new("file or directory not found");
error.message = Some(
"Please create the file/directory or change the path in the configuration.".into(),
);
return Err(error);
}
Ok(())
}
pub fn validate_vec_u256(vec: &[u8]) -> Result<(), ValidationError> {
if vec.len() != 32 {
return Err(ValidationError::new("The value is not a 32 byte vector"));
}
Ok(())
}
pub fn validate_optional_sensitive_vec_u256(
secret_key: &Sensitive<Vec<u8>>,
) -> Result<(), ValidationError> {
validate_vec_u256(secret_key.peek_secret())
}
#[derive(Debug)]
pub struct ParsedValidationError {
pub param_path: String,
pub code: String,
pub message: Option<String>,
pub params: String,
}
#[derive(thiserror::Error, Debug)]
pub struct ParsedValidationErrors(pub Vec<ParsedValidationError>);
impl From<ValidationErrors> for ParsedValidationErrors {
fn from(errors: ValidationErrors) -> Self {
let mut parsed_errors: ParsedValidationErrors = ParsedValidationErrors(vec![]);
parse_validation_error(&errors, "".to_string(), &mut parsed_errors);
parsed_errors
}
}
impl std::fmt::Display for ParsedValidationErrors {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut error_string = String::new();
for error in &self.0 {
error_string.push_str(&format!(
"Configuration error: The field: {} has an error: {} with value: {}, {} \n",
&error.param_path,
&error.code,
&error.params,
&error.message.clone().unwrap_or("".to_string()),
));
}
error_string = error_string.replace('\"', "");
write!(f, "{error_string}")
}
}
pub fn config_validate<T: Validate>(config: &T) -> Result<(), ConfigError> {
config
.validate()
.map_err(|errors| ConfigError::ConfigValidationError(ParsedValidationErrors::from(errors)))
}
fn parse_validation_error(
errors: &ValidationErrors,
current_path: String,
parsed_errors: &mut ParsedValidationErrors,
) {
for (field, error) in errors.errors().iter() {
let new_path = if current_path.is_empty() {
field.to_string()
} else {
format!("{current_path}.{field}")
};
match error {
ValidationErrorsKind::Struct(errors) => {
parse_validation_error(errors, new_path, parsed_errors);
}
ValidationErrorsKind::List(errors) => {
for (index, error) in errors.iter().enumerate() {
parse_validation_error(error.1, format!("{new_path}[{index}]"), parsed_errors);
}
}
ValidationErrorsKind::Field(errors) => {
for error in errors {
let parsed_error = ParsedValidationError {
param_path: new_path.to_owned(),
code: error.code.to_string(),
message: error.message.as_ref().map(|cow_string| cow_string.to_string()),
params: {
let params = &error.params;
params
.values()
.map(|v| v.to_string().replace('\"', ""))
.collect::<Vec<String>>()
.join(", ")
}
.to_owned(),
};
parsed_errors.0.push(parsed_error);
}
}
}
}
}
pub fn validate_positive(value: usize) -> Result<(), ValidationError> {
if value > 0 {
Ok(())
} else {
Err(create_validation_error(
format!("Invalid value: {value}"),
"Invalid value",
"Ensure value is positive, i.e., strictly greater than 0.",
))
}
}