1use std::fmt;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
15pub struct TemplateId(pub u32);
16
17pub trait Render: Send + Sync + 'static {
39 fn template(&self, name: &str) -> Option<TemplateId>;
42
43 fn render(
50 &self,
51 template: TemplateId,
52 view: &dyn View,
53 out: &mut Output<'_>,
54 ) -> Result<(), RenderError>;
55}
56
57#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
59pub struct Key(pub u32);
60
61#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
64pub struct ShapeId(pub u64);
65
66impl ShapeId {
67 pub const ANONYMOUS: Self = Self(0);
70}
71
72pub trait Shape {
74 fn id(&self) -> ShapeId;
77
78 fn key(&self, name: &str) -> Option<Key>;
80}
81
82pub trait View {
84 fn shape(&self) -> &dyn Shape;
86
87 fn get(&self, key: Key) -> Value<'_>;
89
90 fn call(&self, key: Key, args: &[Value<'_>], with: &mut dyn FnMut(Value<'_>)) {
94 let _ = (key, args);
95 with(Value::Absent);
96 }
97}
98
99pub trait List {
101 fn len(&self) -> usize;
103
104 fn is_empty(&self) -> bool {
106 self.len() == 0
107 }
108
109 fn get(&self, index: usize) -> Value<'_>;
111}
112
113#[derive(Clone, Copy, PartialEq, Eq)]
118pub struct SafeHtml<'a>(&'a str);
119
120impl<'a> SafeHtml<'a> {
121 #[must_use]
123 pub const fn trusted(html: &'a str) -> Self {
124 Self(html)
125 }
126
127 #[must_use]
129 pub const fn as_str(&self) -> &'a str {
130 self.0
131 }
132}
133
134impl fmt::Debug for SafeHtml<'_> {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 write!(f, "SafeHtml({} bytes)", self.0.len())
137 }
138}
139
140#[non_exhaustive]
142#[derive(Clone, Copy)]
143pub enum Value<'a> {
144 Absent,
146 Bool(bool),
148 Int(i64),
150 Float(f64),
152 Text(&'a str),
154 Html(SafeHtml<'a>),
156 Display(&'a dyn fmt::Display),
158 View(&'a dyn View),
160 List(&'a dyn List),
162}
163
164impl Value<'_> {
165 #[must_use]
167 pub const fn is_absent(&self) -> bool {
168 matches!(self, Self::Absent)
169 }
170
171 #[must_use]
173 pub const fn as_text(&self) -> Option<&str> {
174 match self {
175 Self::Text(t) => Some(t),
176 _ => None,
177 }
178 }
179}
180
181impl fmt::Debug for Value<'_> {
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 match self {
184 Self::Absent => f.write_str("Absent"),
185 Self::Bool(b) => write!(f, "Bool({b})"),
186 Self::Int(i) => write!(f, "Int({i})"),
187 Self::Float(x) => write!(f, "Float({x})"),
188 Self::Text(t) => write!(f, "Text({t:?})"),
189 Self::Html(h) => write!(f, "{h:?}"),
190 Self::Display(_) => f.write_str("Display(..)"),
191 Self::View(_) => f.write_str("View(..)"),
192 Self::List(l) => write!(f, "List({} items)", l.len()),
193 }
194 }
195}
196
197#[derive(Clone, Copy, Debug)]
208pub struct Fields<'a>(pub &'a [(&'a str, Value<'a>)]);
209
210impl Shape for Fields<'_> {
211 fn id(&self) -> ShapeId {
212 ShapeId::ANONYMOUS
213 }
214
215 fn key(&self, name: &str) -> Option<Key> {
216 self.0
217 .iter()
218 .position(|(n, _)| *n == name)
219 .map(|i| Key(i as u32))
220 }
221}
222
223impl View for Fields<'_> {
224 fn shape(&self) -> &dyn Shape {
225 self
226 }
227
228 fn get(&self, key: Key) -> Value<'_> {
229 self.0
230 .get(key.0 as usize)
231 .map_or(Value::Absent, |(_, v)| *v)
232 }
233}
234
235pub struct Output<'a> {
238 buf: &'a mut Vec<u8>,
239 limit: usize,
240}
241
242impl<'a> Output<'a> {
243 #[must_use]
245 pub fn new(buf: &'a mut Vec<u8>, limit: usize) -> Self {
246 Self { buf, limit }
247 }
248
249 #[inline]
255 pub fn write(&mut self, bytes: &[u8]) -> Result<(), RenderError> {
256 if self.buf.len() + bytes.len() > self.limit {
257 return Err(RenderError::new("render.too_large"));
258 }
259 self.buf.extend_from_slice(bytes);
260 Ok(())
261 }
262
263 #[inline]
269 pub fn write_str(&mut self, s: &str) -> Result<(), RenderError> {
270 self.write(s.as_bytes())
271 }
272
273 #[must_use]
275 pub fn len(&self) -> usize {
276 self.buf.len()
277 }
278
279 #[must_use]
281 pub fn is_empty(&self) -> bool {
282 self.buf.is_empty()
283 }
284
285 #[must_use]
287 pub fn remaining(&self) -> usize {
288 self.limit.saturating_sub(self.buf.len())
289 }
290}
291
292impl fmt::Debug for Output<'_> {
293 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294 f.debug_struct("Output")
295 .field("len", &self.buf.len())
296 .field("limit", &self.limit)
297 .finish()
298 }
299}
300
301pub struct RenderError {
303 code: &'static str,
304 detail: Option<Box<str>>,
305}
306
307impl RenderError {
308 #[must_use]
310 pub const fn new(code: &'static str) -> Self {
311 Self { code, detail: None }
312 }
313
314 #[must_use]
316 pub fn with_detail(mut self, detail: impl Into<Box<str>>) -> Self {
317 self.detail = Some(detail.into());
318 self
319 }
320
321 #[must_use]
323 pub const fn code(&self) -> &'static str {
324 self.code
325 }
326
327 #[must_use]
329 pub fn detail(&self) -> Option<&str> {
330 self.detail.as_deref()
331 }
332}
333
334impl fmt::Debug for RenderError {
335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336 f.debug_struct("RenderError")
337 .field("code", &self.code)
338 .field("detail", &self.detail)
339 .finish()
340 }
341}
342
343impl fmt::Display for RenderError {
344 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345 match &self.detail {
346 Some(d) => write!(f, "render failed ({}): {d}", self.code),
347 None => write!(f, "render failed ({})", self.code),
348 }
349 }
350}
351
352impl std::error::Error for RenderError {}
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357
358 #[test]
359 fn test_output_budget() {
360 let mut buf = Vec::new();
361 let mut out = Output::new(&mut buf, 4);
362 out.write(b"abc").unwrap();
363 assert_eq!(out.remaining(), 1);
364 assert_eq!(out.write(b"de").unwrap_err().code(), "render.too_large");
365 out.write(b"d").unwrap();
366 assert_eq!(buf, b"abcd");
367 }
368
369 #[test]
370 fn test_fields_view() {
371 let v = Fields(&[("a", Value::Bool(true)), ("b", Value::Text("x"))]);
372 assert_eq!(v.shape().key("b"), Some(Key(1)));
373 assert_eq!(v.shape().key("c"), None);
374 assert!(v.get(Key(9)).is_absent());
375 assert_eq!(v.get(Key(1)).as_text(), Some("x"));
376 let mut got = false;
377 v.call(Key(0), &[], &mut |x| got = x.is_absent());
378 assert!(got);
379 }
380}