use std::borrow::Cow;
use std::path::PathBuf;
use rocket::{Rocket, Orbit, Ignite, Sentinel};
use rocket::request::Request;
use rocket::fairing::Fairing;
use rocket::response::{self, Responder};
use rocket::http::{ContentType, Status};
use rocket::figment::{value::Value, error::Error};
use rocket::serde::Serialize;
use rocket::yansi::Paint;
use crate::Engines;
use crate::fairing::TemplateFairing;
use crate::context::{Context, ContextManager};
pub(crate) const DEFAULT_TEMPLATE_DIR: &str = "templates";
#[derive(Debug)]
pub struct Template {
name: Cow<'static, str>,
value: Result<Value, Error>,
}
#[derive(Debug)]
pub(crate) struct TemplateInfo {
pub(crate) path: Option<PathBuf>,
pub(crate) engine_ext: &'static str,
pub(crate) data_type: ContentType
}
impl Template {
pub fn fairing() -> impl Fairing {
Template::custom(|_| {})
}
pub fn custom<F: Send + Sync + 'static>(f: F) -> impl Fairing
where F: Fn(&mut Engines)
{
Self::try_custom(move |engines| { f(engines); Ok(()) })
}
pub fn try_custom<F: Send + Sync + 'static>(f: F) -> impl Fairing
where F: Fn(&mut Engines) -> Result<(), Box<dyn std::error::Error>>
{
TemplateFairing { callback: Box::new(f) }
}
#[inline]
pub fn render<S, C>(name: S, context: C) -> Template
where S: Into<Cow<'static, str>>, C: Serialize
{
Template {
name: name.into(),
value: Value::serialize(context),
}
}
#[inline]
pub fn show<S, C>(rocket: &Rocket<Orbit>, name: S, context: C) -> Option<String>
where S: Into<Cow<'static, str>>, C: Serialize
{
let ctxt = rocket.state::<ContextManager>().map(ContextManager::context).or_else(|| {
warn!("Uninitialized template context: missing fairing.");
info!("To use templates, you must attach `Template::fairing()`.");
info!("See the `Template` documentation for more information.");
None
})?;
Template::render(name, context).finalize(&ctxt).ok().map(|v| v.1)
}
#[inline(always)]
pub(crate) fn finalize(self, ctxt: &Context) -> Result<(ContentType, String), Status> {
let name = &*self.name;
let info = ctxt.templates.get(name).ok_or_else(|| {
let ts: Vec<_> = ctxt.templates.keys().map(|s| s.as_str()).collect();
error_!("Template '{}' does not exist.", name);
info_!("Known templates: {}.", ts.join(", "));
info_!("Searched in {:?}.", ctxt.root);
Status::InternalServerError
})?;
let value = self.value.map_err(|e| {
error_!("Template context failed to serialize: {}.", e);
Status::InternalServerError
})?;
let string = ctxt.engines.render(name, info, value).ok_or_else(|| {
error_!("Template '{}' failed to render.", name);
Status::InternalServerError
})?;
Ok((info.data_type.clone(), string))
}
}
impl<'r> Responder<'r, 'static> for Template {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
let ctxt = req.rocket()
.state::<ContextManager>()
.ok_or_else(|| {
error_!("Uninitialized template context: missing fairing.");
info_!("To use templates, you must attach `Template::fairing()`.");
info_!("See the `Template` documentation for more information.");
Status::InternalServerError
})?;
self.finalize(&ctxt.context())?.respond_to(req)
}
}
impl Sentinel for Template {
fn abort(rocket: &Rocket<Ignite>) -> bool {
if rocket.state::<ContextManager>().is_none() {
let template = "Template".primary().bold();
let fairing = "Template::fairing()".primary().bold();
error!("returning `{}` responder without attaching `{}`.", template, fairing);
info_!("To use or query templates, you must attach `{}`.", fairing);
info_!("See the `Template` documentation for more information.");
return true;
}
false
}
}
#[macro_export]
macro_rules! context {
($($key:ident $(: $value:expr)?),*$(,)?) => {{
use $crate::serde::ser::{Serialize, Serializer, SerializeMap};
use ::std::fmt::{Debug, Formatter};
use ::std::result::Result;
#[allow(non_camel_case_types)]
struct ContextMacroCtxObject<$($key: Serialize),*> {
$($key: $key),*
}
#[allow(non_camel_case_types)]
impl<$($key: Serialize),*> Serialize for ContextMacroCtxObject<$($key),*> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer,
{
let mut map = serializer.serialize_map(None)?;
$(map.serialize_entry(stringify!($key), &self.$key)?;)*
map.end()
}
}
#[allow(non_camel_case_types)]
impl<$($key: Debug + Serialize),*> Debug for ContextMacroCtxObject<$($key),*> {
fn fmt(&self, f: &mut Formatter<'_>) -> ::std::fmt::Result {
f.debug_struct("context!")
$(.field(stringify!($key), &self.$key))*
.finish()
}
}
ContextMacroCtxObject {
$($key $(: $value)?),*
}
}};
}