use std::sync::LazyLock;
use regex::Regex;
use serde::Serialize;
pub trait AcubeValidate: Sized {
fn known_fields() -> &'static [&'static str];
fn validate(&mut self) -> Result<(), Vec<ValidationError>>;
}
pub trait AcubeSchemaInfo {
fn schema_info() -> SchemaInfo;
fn openapi_schema() -> serde_json::Value {
serde_json::json!({ "type": "object" })
}
}
#[derive(Debug, Clone, Serialize)]
pub struct ValidationError {
pub field: String,
pub message: String,
pub code: String,
}
#[derive(Debug, Clone)]
pub struct SchemaInfo {
pub name: String,
pub fields: Vec<FieldInfo>,
}
#[derive(Debug, Clone, Default)]
pub struct FieldConstraints {
pub min_length: Option<usize>,
pub max_length: Option<usize>,
pub pattern: Option<String>,
pub format: Option<String>,
pub min: Option<f64>,
pub max: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct FieldInfo {
pub name: String,
pub type_name: String,
pub required: bool,
pub pii: bool,
pub constraints: FieldConstraints,
}
pub fn sanitize_trim(s: &mut String) {
let trimmed = s.trim().to_string();
*s = trimmed;
}
pub fn sanitize_lowercase(s: &mut String) {
*s = s.to_lowercase();
}
pub fn sanitize_strip_html(s: &mut String) {
static HTML_TAG_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"<[^>]*>").unwrap());
*s = HTML_TAG_RE.replace_all(s, "").to_string();
}
pub fn validate_email(s: &str) -> bool {
static EMAIL_RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$").unwrap()
});
EMAIL_RE.is_match(s)
}
pub fn validate_url(s: &str) -> bool {
static URL_RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^https?://[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)*(:\d+)?(/[^\s]*)?$")
.unwrap()
});
URL_RE.is_match(s)
}
pub fn validate_uuid(s: &str) -> bool {
static UUID_RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")
.unwrap()
});
UUID_RE.is_match(s)
}
pub fn validate_pattern(s: &str, pattern: &str) -> bool {
match Regex::new(pattern) {
Ok(re) => re.is_match(s),
Err(_) => false,
}
}
pub fn check_unknown_fields(value: &serde_json::Value, known: &[&str]) -> Vec<ValidationError> {
let mut errors = Vec::new();
if let serde_json::Value::Object(map) = value {
for key in map.keys() {
if !known.contains(&key.as_str()) {
errors.push(ValidationError {
field: key.clone(),
message: format!("Unknown field '{}'", key),
code: "unknown_field".to_string(),
});
}
}
}
errors
}