use std::collections::HashMap;
use crate::{http::HttpResponse, template::engine::TemplateEngine};
use http::{StatusCode, header};
use minijinja::Value;
#[must_use]
pub fn render(
engine: &TemplateEngine,
template_name: &str,
context: HashMap<String, String>,
status: Option<StatusCode>,
content_type: Option<&str>,
) -> HttpResponse {
match engine.render(template_name, Value::from_serialize(&context)) {
Ok(rendered) => {
let mut response =
HttpResponse::with_status(status.unwrap_or(StatusCode::OK), rendered);
response.set_header(
header::CONTENT_TYPE,
content_type.unwrap_or("text/html; charset=utf-8"),
);
response
}
Err(_) => {
HttpResponse::with_status(StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error")
}
}
}
#[must_use]
pub fn redirect(url: &str, permanent: bool) -> HttpResponse {
let status = if permanent {
StatusCode::MOVED_PERMANENTLY
} else {
StatusCode::FOUND
};
let mut response = HttpResponse::with_status(status, "");
response.set_header(header::LOCATION, url);
response
}
#[must_use]
pub fn get_object_or_404<T>(
objects: &[T],
predicate: impl Fn(&T) -> bool,
) -> Result<&T, HttpResponse> {
objects
.iter()
.find(|object| predicate(object))
.ok_or_else(|| HttpResponse::with_status(StatusCode::NOT_FOUND, "Not Found"))
}
#[must_use]
pub fn get_list_or_404<T>(
objects: &[T],
predicate: impl Fn(&T) -> bool,
) -> Result<Vec<&T>, HttpResponse> {
let matches: Vec<&T> = objects.iter().filter(|object| predicate(object)).collect();
if matches.is_empty() {
Err(HttpResponse::with_status(
StatusCode::NOT_FOUND,
"Not Found",
))
} else {
Ok(matches)
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::header;
fn response_body(response: &HttpResponse) -> &str {
std::str::from_utf8(&response.content).expect("utf-8 response body")
}
fn response_header(response: &HttpResponse, name: header::HeaderName) -> Option<&str> {
response.headers.get(name)?.to_str().ok()
}
#[test]
fn test_render_basic_template() {
let mut engine = TemplateEngine::new();
engine.add_template("hello.html", "Hello {{ name }}!");
let response = render(
&engine,
"hello.html",
HashMap::from([(String::from("name"), String::from("World"))]),
None,
None,
);
assert_eq!(response.status_code, StatusCode::OK);
assert_eq!(response_body(&response), "Hello World!");
assert_eq!(
response_header(&response, header::CONTENT_TYPE),
Some("text/html; charset=utf-8")
);
}
#[test]
fn test_render_with_custom_status() {
let mut engine = TemplateEngine::new();
engine.add_template("created.html", "Created");
let response = render(
&engine,
"created.html",
HashMap::new(),
Some(StatusCode::CREATED),
None,
);
assert_eq!(response.status_code, StatusCode::CREATED);
assert_eq!(response_body(&response), "Created");
}
#[test]
fn test_render_missing_template_returns_500() {
let engine = TemplateEngine::new();
let response = render(&engine, "missing.html", HashMap::new(), None, None);
assert_eq!(response.status_code, StatusCode::INTERNAL_SERVER_ERROR);
assert_eq!(response_body(&response), "Internal Server Error");
}
#[test]
fn test_render_empty_context() {
let mut engine = TemplateEngine::new();
engine.add_template("empty.html", "No context needed");
let response = render(&engine, "empty.html", HashMap::new(), None, None);
assert_eq!(response.status_code, StatusCode::OK);
assert_eq!(response_body(&response), "No context needed");
}
#[test]
fn test_render_with_custom_content_type() {
let mut engine = TemplateEngine::new();
engine.add_template("page.html", "Page");
let response = render(
&engine,
"page.html",
HashMap::new(),
None,
Some("application/xhtml+xml"),
);
assert_eq!(
response_header(&response, header::CONTENT_TYPE),
Some("application/xhtml+xml")
);
assert_eq!(response.content_type, "application/xhtml+xml");
}
#[test]
fn test_render_custom_status_and_content_type() {
let mut engine = TemplateEngine::new();
engine.add_template("page.html", "Page");
let response = render(
&engine,
"page.html",
HashMap::new(),
Some(StatusCode::ACCEPTED),
Some("text/plain; charset=utf-8"),
);
assert_eq!(response.status_code, StatusCode::ACCEPTED);
assert_eq!(
response_header(&response, header::CONTENT_TYPE),
Some("text/plain; charset=utf-8")
);
}
#[test]
fn test_redirect_temporary() {
let response = redirect("/login", false);
assert_eq!(response.status_code, StatusCode::FOUND);
assert_eq!(response_header(&response, header::LOCATION), Some("/login"));
assert!(response.content.is_empty());
}
#[test]
fn test_redirect_permanent() {
let response = redirect("/moved", true);
assert_eq!(response.status_code, StatusCode::MOVED_PERMANENTLY);
assert_eq!(response_header(&response, header::LOCATION), Some("/moved"));
}
#[test]
fn test_redirect_preserves_exact_location() {
let response = redirect("https://example.com/path?q=rust#frag", false);
assert_eq!(
response_header(&response, header::LOCATION),
Some("https://example.com/path?q=rust#frag")
);
}
#[test]
fn test_get_object_or_404_found() {
let values = [1, 2, 3];
let object = get_object_or_404(&values, |value| *value == 2).expect("object found");
assert_eq!(*object, 2);
}
#[test]
fn test_get_object_or_404_returns_first_match() {
let values = [2, 4, 6, 8];
let object = get_object_or_404(&values, |value| *value % 2 == 0).expect("object found");
assert_eq!(*object, 2);
}
#[test]
fn test_get_object_or_404_not_found() {
let values = [1, 2, 3];
let response = get_object_or_404(&values, |value| *value == 4).expect_err("404 response");
assert_eq!(response.status_code, StatusCode::NOT_FOUND);
assert_eq!(response_body(&response), "Not Found");
}
#[test]
fn test_get_object_or_404_empty_slice() {
let values: [i32; 0] = [];
let response = get_object_or_404(&values, |_| true).expect_err("404 response");
assert_eq!(response.status_code, StatusCode::NOT_FOUND);
}
#[test]
fn test_get_list_or_404_found() {
let values = [1, 2, 3, 4];
let objects = get_list_or_404(&values, |value| *value % 2 == 0).expect("objects found");
assert_eq!(objects, vec![&2, &4]);
}
#[test]
fn test_get_list_or_404_partial_matches() {
let values = ["alpha", "beta", "gamma"];
let objects = get_list_or_404(&values, |value| value.contains('a')).expect("objects found");
assert_eq!(objects, vec![&"alpha", &"beta", &"gamma"]);
}
#[test]
fn test_get_list_or_404_empty() {
let values = [1, 2, 3];
let response = get_list_or_404(&values, |value| *value > 10).expect_err("404 response");
assert_eq!(response.status_code, StatusCode::NOT_FOUND);
assert_eq!(response_body(&response), "Not Found");
}
#[test]
fn test_get_list_or_404_preserves_order() {
let values = [5, 1, 3, 2, 4];
let objects = get_list_or_404(&values, |value| *value > 2).expect("objects found");
assert_eq!(objects, vec![&5, &3, &4]);
}
}