use std::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct TemplateId(pub u32);
pub trait Render: Send + Sync + 'static {
type View<'a>: ?Sized;
fn template(&self, name: &str) -> Option<TemplateId>;
fn render(
&self,
template: TemplateId,
view: &Self::View<'_>,
out: &mut Output<'_>,
) -> Result<(), RenderError>;
fn error_page(&self, page: &ErrorPage<'_>, out: &mut Output<'_>) -> Result<bool, RenderError> {
let _ = (page, out);
Ok(false)
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct NoRender;
impl Render for NoRender {
type View<'a> = dyn View + 'a;
fn template(&self, _name: &str) -> Option<TemplateId> {
None
}
fn render(
&self,
_template: TemplateId,
_view: &(dyn View + '_),
_out: &mut Output<'_>,
) -> Result<(), RenderError> {
Err(RenderError::new("render.no_engine"))
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ErrorPage<'a> {
pub status: u16,
pub title: &'a str,
pub message: &'a str,
pub reference: &'a str,
}
impl<'a> ErrorPage<'a> {
#[must_use]
pub const fn new(status: u16, title: &'a str, message: &'a str, reference: &'a str) -> Self {
Self {
status,
title,
message,
reference,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Key(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ShapeId(pub u64);
impl ShapeId {
pub const ANONYMOUS: Self = Self(0);
}
pub trait Shape {
fn id(&self) -> ShapeId;
fn key(&self, name: &str) -> Option<Key>;
}
pub trait View {
fn shape(&self) -> &dyn Shape;
fn get(&self, key: Key) -> Value<'_>;
fn call(&self, key: Key, args: &[Value<'_>], with: &mut dyn FnMut(Value<'_>)) {
let _ = (key, args);
with(Value::Absent);
}
}
pub trait List {
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn get(&self, index: usize) -> Value<'_>;
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct SafeHtml<'a>(&'a str);
impl<'a> SafeHtml<'a> {
#[must_use]
pub const fn trusted(html: &'a str) -> Self {
Self(html)
}
#[must_use]
pub const fn as_str(&self) -> &'a str {
self.0
}
}
impl fmt::Debug for SafeHtml<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SafeHtml({} bytes)", self.0.len())
}
}
#[non_exhaustive]
#[derive(Clone, Copy)]
pub enum Value<'a> {
Absent,
Bool(bool),
Int(i64),
Float(f64),
Text(&'a str),
Html(SafeHtml<'a>),
Display(&'a dyn fmt::Display),
View(&'a dyn View),
List(&'a dyn List),
}
impl Value<'_> {
#[must_use]
pub const fn is_absent(&self) -> bool {
matches!(self, Self::Absent)
}
#[must_use]
pub const fn as_text(&self) -> Option<&str> {
match self {
Self::Text(t) => Some(t),
_ => None,
}
}
}
impl fmt::Debug for Value<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Absent => f.write_str("Absent"),
Self::Bool(b) => write!(f, "Bool({b})"),
Self::Int(i) => write!(f, "Int({i})"),
Self::Float(x) => write!(f, "Float({x})"),
Self::Text(t) => write!(f, "Text({t:?})"),
Self::Html(h) => write!(f, "{h:?}"),
Self::Display(_) => f.write_str("Display(..)"),
Self::View(_) => f.write_str("View(..)"),
Self::List(l) => write!(f, "List({} items)", l.len()),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Fields<'a>(pub &'a [(&'a str, Value<'a>)]);
impl Shape for Fields<'_> {
fn id(&self) -> ShapeId {
ShapeId::ANONYMOUS
}
fn key(&self, name: &str) -> Option<Key> {
self.0
.iter()
.position(|(n, _)| *n == name)
.map(|i| Key(i as u32))
}
}
impl View for Fields<'_> {
fn shape(&self) -> &dyn Shape {
self
}
fn get(&self, key: Key) -> Value<'_> {
self.0
.get(key.0 as usize)
.map_or(Value::Absent, |(_, v)| *v)
}
}
pub struct Output<'a> {
buf: &'a mut Vec<u8>,
limit: usize,
}
impl<'a> Output<'a> {
#[must_use]
pub fn new(buf: &'a mut Vec<u8>, limit: usize) -> Self {
Self { buf, limit }
}
#[inline]
pub fn write(&mut self, bytes: &[u8]) -> Result<(), RenderError> {
if self.buf.len() + bytes.len() > self.limit {
return Err(RenderError::new("render.too_large"));
}
self.buf.extend_from_slice(bytes);
Ok(())
}
#[inline]
pub fn write_str(&mut self, s: &str) -> Result<(), RenderError> {
self.write(s.as_bytes())
}
pub fn buffer(&mut self) -> &mut Vec<u8> {
self.buf
}
pub(crate) fn over(&self) -> bool {
self.buf.len() > self.limit
}
#[must_use]
pub fn len(&self) -> usize {
self.buf.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
#[must_use]
pub fn remaining(&self) -> usize {
self.limit.saturating_sub(self.buf.len())
}
}
impl fmt::Debug for Output<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Output")
.field("len", &self.buf.len())
.field("limit", &self.limit)
.finish()
}
}
pub struct RenderError {
code: &'static str,
detail: Option<Box<str>>,
}
impl RenderError {
#[must_use]
pub const fn new(code: &'static str) -> Self {
Self { code, detail: None }
}
#[must_use]
pub fn with_detail(mut self, detail: impl Into<Box<str>>) -> Self {
self.detail = Some(detail.into());
self
}
#[must_use]
pub const fn code(&self) -> &'static str {
self.code
}
#[must_use]
pub fn detail(&self) -> Option<&str> {
self.detail.as_deref()
}
}
impl fmt::Debug for RenderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RenderError")
.field("code", &self.code)
.field("detail", &self.detail)
.finish()
}
}
impl fmt::Display for RenderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.detail {
Some(d) => write!(f, "render failed ({}): {d}", self.code),
None => write!(f, "render failed ({})", self.code),
}
}
}
impl std::error::Error for RenderError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_output_budget() {
let mut buf = Vec::new();
let mut out = Output::new(&mut buf, 4);
out.write(b"abc").unwrap();
assert_eq!(out.remaining(), 1);
assert_eq!(out.write(b"de").unwrap_err().code(), "render.too_large");
out.write(b"d").unwrap();
assert_eq!(buf, b"abcd");
}
#[test]
fn test_fields_view() {
let v = Fields(&[("a", Value::Bool(true)), ("b", Value::Text("x"))]);
assert_eq!(v.shape().key("b"), Some(Key(1)));
assert_eq!(v.shape().key("c"), None);
assert!(v.get(Key(9)).is_absent());
assert_eq!(v.get(Key(1)).as_text(), Some("x"));
let mut got = false;
v.call(Key(0), &[], &mut |x| got = x.is_absent());
assert!(got);
}
}