pub mod check;
pub mod errors;
pub mod input;
pub mod messages;
pub mod rule;
pub mod validator;
pub use errors::Errors;
pub use input::Input;
pub use messages::{Messages, SizeKind};
pub use rule::{IntoRules, Rule, Rules};
pub use validator::{Validated, Validator};
use rustlavel_http::{IntoResponse, Request, Response};
use std::future::Future;
pub async fn validate(
request: &mut Request,
rules: &[(&str, &str)],
) -> Result<Validated, Errors> {
Validator::from_request(request).rules(rules).validate()
}
pub async fn attempt<T: IntoResponse>(
body: impl Future<Output = Result<T, Errors>>,
) -> Response {
match body.await {
Ok(value) => value.into_response(),
Err(errors) => errors.into_response(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use rustlavel_core::Json;
use rustlavel_http::{Method, Status};
use std::task::{Context, Poll};
fn block_on<F: Future>(future: F) -> F::Output {
let mut context = Context::from_waker(std::task::Waker::noop());
let mut future = std::pin::pin!(future);
match future.as_mut().poll(&mut context) {
Poll::Ready(output) => output,
Poll::Pending => panic!("a validation future must never need a runtime"),
}
}
#[test]
fn the_entry_point_returns_the_validated_subset_of_a_request() {
let mut request = Request::new(Method::Post, "/users")
.with_json(Json::object([("email", "ada@example.com".into()), ("age", 36.into())]));
let data = block_on(validate(
&mut request,
&[("email", "required|email"), ("age", "integer|min:18")],
))
.unwrap();
assert_eq!(data.string("email").as_deref(), Some("ada@example.com"));
assert_eq!(data.integer("age"), Some(36));
}
#[test]
fn the_entry_point_returns_errors_shaped_like_laravels_422() {
let mut request = Request::new(Method::Post, "/users")
.with_json(Json::object([("email", "nope".into()), ("age", 12.into())]));
let errors = block_on(validate(
&mut request,
&[("email", "required|email"), ("age", "integer|min:18")],
))
.unwrap_err();
assert_eq!(
errors.to_json().to_string(),
r#"{"errors":{"age":["The age field must be at least 18."],"email":["The email field must be a valid email address."]},"message":"The age field must be at least 18. (and 1 more error)"}"#
);
}
#[test]
fn a_handler_body_using_the_question_mark_answers_422() {
let mut request = Request::new(Method::Post, "/api/users")
.with_json(Json::object([("email", "nope".into())]));
let response = block_on(attempt(async move {
let data = validate(&mut request, &[("email", "required|email")]).await?;
Ok(Response::json(data.into_json()))
}));
assert_eq!(response.status, Status::UNPROCESSABLE);
assert_eq!(response.headers.content_type(), Some("application/json"));
assert!(response.body_string().contains(r#""errors":{"email":["#));
}
#[test]
fn a_handler_body_that_validates_answers_with_its_own_response() {
let mut request = Request::new(Method::Post, "/api/users")
.with_json(Json::object([("email", "ada@example.com".into())]));
let response = block_on(attempt(async move {
let data = validate(&mut request, &[("email", "required|email")]).await?;
Ok(Response::json(data.into_json()))
}));
assert_eq!(response.status, Status::OK);
assert_eq!(response.body_string(), r#"{"email":"ada@example.com"}"#);
}
#[test]
fn a_browser_submission_fails_with_a_plain_422() {
let mut request = Request::new(Method::Post, "/register").with_form(&[("email", "nope")]);
let response = block_on(attempt(async move {
let data = validate(&mut request, &[("email", "required|email")]).await?;
Ok(Response::json(data.into_json()))
}));
assert_eq!(response.status, Status::UNPROCESSABLE);
assert_eq!(response.headers.content_type(), Some("text/plain"));
assert_eq!(response.body_string(), "The email field must be a valid email address.");
}
}