rustlavel_validation/
lib.rs1pub mod check;
55pub mod errors;
56pub mod input;
57pub mod messages;
58pub mod rule;
59pub mod validator;
60
61pub use errors::Errors;
62pub use input::Input;
63pub use messages::{Messages, SizeKind};
64pub use rule::{IntoRules, Rule, Rules};
65pub use validator::{Validated, Validator};
66
67use rustlavel_http::{IntoResponse, Request, Response};
68use std::future::Future;
69
70pub async fn validate(
83 request: &mut Request,
84 rules: &[(&str, &str)],
85) -> Result<Validated, Errors> {
86 Validator::from_request(request).rules(rules).validate()
87}
88
89pub async fn attempt<T: IntoResponse>(
95 body: impl Future<Output = Result<T, Errors>>,
96) -> Response {
97 match body.await {
98 Ok(value) => value.into_response(),
99 Err(errors) => errors.into_response(),
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106 use rustlavel_core::Json;
107 use rustlavel_http::{Method, Status};
108 use std::task::{Context, Poll};
109
110 fn block_on<F: Future>(future: F) -> F::Output {
115 let mut context = Context::from_waker(std::task::Waker::noop());
116 let mut future = std::pin::pin!(future);
117 match future.as_mut().poll(&mut context) {
118 Poll::Ready(output) => output,
119 Poll::Pending => panic!("a validation future must never need a runtime"),
120 }
121 }
122
123 #[test]
124 fn the_entry_point_returns_the_validated_subset_of_a_request() {
125 let mut request = Request::new(Method::Post, "/users")
126 .with_json(Json::object([("email", "ada@example.com".into()), ("age", 36.into())]));
127
128 let data = block_on(validate(
129 &mut request,
130 &[("email", "required|email"), ("age", "integer|min:18")],
131 ))
132 .unwrap();
133
134 assert_eq!(data.string("email").as_deref(), Some("ada@example.com"));
135 assert_eq!(data.integer("age"), Some(36));
136 }
137
138 #[test]
139 fn the_entry_point_returns_errors_shaped_like_laravels_422() {
140 let mut request = Request::new(Method::Post, "/users")
141 .with_json(Json::object([("email", "nope".into()), ("age", 12.into())]));
142
143 let errors = block_on(validate(
144 &mut request,
145 &[("email", "required|email"), ("age", "integer|min:18")],
146 ))
147 .unwrap_err();
148
149 assert_eq!(
150 errors.to_json().to_string(),
151 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)"}"#
152 );
153 }
154
155 #[test]
156 fn a_handler_body_using_the_question_mark_answers_422() {
157 let mut request = Request::new(Method::Post, "/api/users")
158 .with_json(Json::object([("email", "nope".into())]));
159
160 let response = block_on(attempt(async move {
161 let data = validate(&mut request, &[("email", "required|email")]).await?;
162 Ok(Response::json(data.into_json()))
163 }));
164
165 assert_eq!(response.status, Status::UNPROCESSABLE);
166 assert_eq!(response.headers.content_type(), Some("application/json"));
167 assert!(response.body_string().contains(r#""errors":{"email":["#));
168 }
169
170 #[test]
171 fn a_handler_body_that_validates_answers_with_its_own_response() {
172 let mut request = Request::new(Method::Post, "/api/users")
173 .with_json(Json::object([("email", "ada@example.com".into())]));
174
175 let response = block_on(attempt(async move {
176 let data = validate(&mut request, &[("email", "required|email")]).await?;
177 Ok(Response::json(data.into_json()))
178 }));
179
180 assert_eq!(response.status, Status::OK);
181 assert_eq!(response.body_string(), r#"{"email":"ada@example.com"}"#);
182 }
183
184 #[test]
185 fn a_browser_submission_fails_with_a_plain_422() {
186 let mut request = Request::new(Method::Post, "/register").with_form(&[("email", "nope")]);
187
188 let response = block_on(attempt(async move {
189 let data = validate(&mut request, &[("email", "required|email")]).await?;
190 Ok(Response::json(data.into_json()))
191 }));
192
193 assert_eq!(response.status, Status::UNPROCESSABLE);
194 assert_eq!(response.headers.content_type(), Some("text/plain"));
195 assert_eq!(response.body_string(), "The email field must be a valid email address.");
196 }
197}