use crate::{Templates, ViewError};
use http::{header, Response, StatusCode};
use rustapi_core::{IntoResponse, ResponseBody};
use rustapi_openapi::{MediaType, Operation, ResponseModifier, ResponseSpec, SchemaRef};
use serde::Serialize;
use std::collections::BTreeMap;
use std::marker::PhantomData;
pub struct View<T> {
content: Result<String, ViewError>,
status: StatusCode,
_phantom: PhantomData<T>,
}
impl<T: Serialize> View<T> {
pub async fn render(templates: &Templates, template: &str, context: T) -> Self {
let content = templates.render_with(template, &context).await;
Self {
content,
status: StatusCode::OK,
_phantom: PhantomData,
}
}
pub async fn render_with_status(
templates: &Templates,
template: &str,
context: T,
status: StatusCode,
) -> Self {
let content = templates.render_with(template, &context).await;
Self {
content,
status,
_phantom: PhantomData,
}
}
pub fn from_html(html: impl Into<String>) -> Self {
Self {
content: Ok(html.into()),
status: StatusCode::OK,
_phantom: PhantomData,
}
}
pub fn error(err: ViewError) -> Self {
Self {
content: Err(err),
status: StatusCode::INTERNAL_SERVER_ERROR,
_phantom: PhantomData,
}
}
pub fn status(mut self, status: StatusCode) -> Self {
self.status = status;
self
}
}
impl View<()> {
pub async fn render_context(
templates: &Templates,
template: &str,
context: &tera::Context,
) -> Self {
let content = templates.render(template, context).await;
Self {
content,
status: StatusCode::OK,
_phantom: PhantomData,
}
}
}
impl<T> IntoResponse for View<T> {
fn into_response(self) -> rustapi_core::Response {
match self.content {
Ok(html) => Response::builder()
.status(self.status)
.header(header::CONTENT_TYPE, "text/html; charset=utf-8")
.body(ResponseBody::from(html))
.unwrap(),
Err(err) => {
tracing::error!("Template rendering failed: {}", err);
Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.header(header::CONTENT_TYPE, "text/html; charset=utf-8")
.body(ResponseBody::from(
"<!DOCTYPE html><html><head><title>Error</title></head>\
<body><h1>500 Internal Server Error</h1>\
<p>Template rendering failed</p></body></html>",
))
.unwrap()
}
}
}
}
impl<T> ResponseModifier for View<T> {
fn update_response(op: &mut Operation) {
op.responses.insert(
"200".to_string(),
ResponseSpec {
description: "HTML Content".to_string(),
content: {
let mut map = BTreeMap::new();
map.insert(
"text/html".to_string(),
MediaType {
schema: Some(SchemaRef::Inline(
serde_json::json!({ "type": "string" }),
)),
example: None,
},
);
map
},
headers: BTreeMap::new(),
},
);
}
}
impl<T: Serialize> View<T> {
pub async fn not_found(templates: &Templates, template: &str, context: T) -> Self {
Self::render_with_status(templates, template, context, StatusCode::NOT_FOUND).await
}
pub async fn forbidden(templates: &Templates, template: &str, context: T) -> Self {
Self::render_with_status(templates, template, context, StatusCode::FORBIDDEN).await
}
pub async fn unauthorized(templates: &Templates, template: &str, context: T) -> Self {
Self::render_with_status(templates, template, context, StatusCode::UNAUTHORIZED).await
}
}