rustapi_view/
context.rs

1//! Context builder for templates
2
3use serde::Serialize;
4use tera::Context;
5
6/// Builder for constructing template context
7///
8/// This provides a fluent API for building template context without
9/// needing to create a struct for simple cases.
10///
11/// # Example
12///
13/// ```rust,ignore
14/// use rustapi_view::ContextBuilder;
15///
16/// let context = ContextBuilder::new()
17///     .insert("name", "Alice")
18///     .insert("age", 30)
19///     .insert_if("admin", true, |_| user.is_admin())
20///     .build();
21/// ```
22pub struct ContextBuilder {
23    context: Context,
24}
25
26impl ContextBuilder {
27    /// Create a new context builder
28    pub fn new() -> Self {
29        Self {
30            context: Context::new(),
31        }
32    }
33
34    /// Insert a value into the context
35    pub fn insert<T: Serialize + ?Sized>(mut self, key: impl Into<String>, value: &T) -> Self {
36        self.context.insert(key.into(), value);
37        self
38    }
39
40    /// Insert a value if a condition is met
41    pub fn insert_if<T: Serialize + ?Sized, F>(
42        self,
43        key: impl Into<String>,
44        value: &T,
45        condition: F,
46    ) -> Self
47    where
48        F: FnOnce(&T) -> bool,
49    {
50        if condition(value) {
51            self.insert(key, value)
52        } else {
53            self
54        }
55    }
56
57    /// Insert a value if it's Some
58    pub fn insert_some<T: Serialize + ?Sized>(
59        self,
60        key: impl Into<String>,
61        value: Option<&T>,
62    ) -> Self {
63        if let Some(v) = value {
64            self.insert(key, v)
65        } else {
66            self
67        }
68    }
69
70    /// Extend with values from a serializable struct
71    pub fn extend<T: Serialize>(mut self, value: &T) -> Result<Self, tera::Error> {
72        let additional = Context::from_serialize(value)?;
73        self.context.extend(additional);
74        Ok(self)
75    }
76
77    /// Build the context
78    pub fn build(self) -> Context {
79        self.context
80    }
81}
82
83impl Default for ContextBuilder {
84    fn default() -> Self {
85        Self::new()
86    }
87}
88
89impl From<ContextBuilder> for Context {
90    fn from(builder: ContextBuilder) -> Self {
91        builder.build()
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn test_context_builder() {
101        let context = ContextBuilder::new()
102            .insert("name", &"Alice")
103            .insert("age", &30)
104            .build();
105
106        assert!(context.contains_key("name"));
107        assert!(context.contains_key("age"));
108    }
109
110    #[test]
111    fn test_insert_if() {
112        let show = true;
113        let context = ContextBuilder::new()
114            .insert_if("visible", &"yes", |_| show)
115            .insert_if("hidden", &"no", |_| !show)
116            .build();
117
118        assert!(context.contains_key("visible"));
119        assert!(!context.contains_key("hidden"));
120    }
121
122    #[test]
123    fn test_insert_some() {
124        let name: Option<&str> = Some("Alice");
125        let missing: Option<&str> = None;
126
127        let context = ContextBuilder::new()
128            .insert_some("name", name)
129            .insert_some("missing", missing)
130            .build();
131
132        assert!(context.contains_key("name"));
133        assert!(!context.contains_key("missing"));
134    }
135}