#[cfg(test)]
mod tests;
use crate::core::New;
use crate::extract::FromRequest;
use crate::mime_type::MimeType;
use crate::range::Range;
use crate::request::Request;
use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
#[derive(Debug, Clone)]
pub struct FieldError {
pub field: String,
pub message: String,
}
#[derive(Debug)]
pub struct ValidationErrors {
errors: Vec<FieldError>,
}
impl ValidationErrors {
pub fn new() -> Self {
Self { errors: Vec::new() }
}
pub fn add(&mut self, field: &str, message: &str) {
self.errors.push(FieldError {
field: field.to_string(),
message: message.to_string(),
});
}
pub fn is_empty(&self) -> bool {
self.errors.is_empty()
}
pub fn errors(&self) -> &[FieldError] {
&self.errors
}
pub fn into_json(&self) -> String {
let entries: Vec<String> = self
.errors
.iter()
.map(|e| {
format!(
"{{\"field\":\"{}\",\"message\":\"{}\"}}",
escape(&e.field),
escape(&e.message),
)
})
.collect();
format!("{{\"errors\":[{}]}}", entries.join(","))
}
}
fn escape(s: &str) -> String {
s.replace('\\', "\\\\").replace('"', "\\\"")
}
pub trait Validate {
fn validate(&self) -> Result<(), ValidationErrors>;
}
pub struct Validated<T>(pub T);
impl<T: FromRequest + Validate> FromRequest for Validated<T> {
fn from_request(request: &Request) -> Result<Self, Response> {
let value = T::from_request(request)?;
value.validate().map_err(validation_error_response)?;
Ok(Validated(value))
}
}
fn validation_error_response(errors: ValidationErrors) -> Response {
let json = errors.into_json();
let cr = Range::get_content_range(json.into_bytes(), MimeType::APPLICATION_JSON.to_string());
let mut response = Response::new();
response.status_code = *STATUS_CODE_REASON_PHRASE.n422_unprocessable_entity.status_code;
response.reason_phrase = STATUS_CODE_REASON_PHRASE
.n422_unprocessable_entity
.reason_phrase
.to_string();
response.content_range_list = vec![cr];
response
}
pub fn is_email(s: &str) -> bool {
let mut parts = s.splitn(2, '@');
let local = parts.next().unwrap_or("");
let domain = match parts.next() {
Some(d) => d,
None => return false,
};
!local.is_empty()
&& domain.len() >= 3
&& domain.contains('.')
&& !domain.starts_with('.')
&& !domain.ends_with('.')
}
pub fn is_url(s: &str) -> bool {
s.starts_with("http://") || s.starts_with("https://")
}