weaver_lib/renderers/
globals.rs

1use liquid::model::KString;
2use liquid::{self};
3use serde::{Deserialize, Serialize};
4use std::{collections::HashMap, sync::Arc};
5
6use crate::document::BaseMetaData;
7
8#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
9pub struct LiquidGlobalsPage {
10    pub route: KString,
11    pub title: String,
12    pub body: String,
13    pub meta: BaseMetaData,
14    pub excerpt: Option<String>,
15}
16
17impl LiquidGlobalsPage {
18    pub fn to_liquid_data(&self) -> liquid::model::Value {
19        liquid::model::to_value(self)
20            .expect("Failed to serialize LiquidGlobalsPage to liquid value")
21    }
22}
23
24impl From<&crate::Document> for LiquidGlobalsPage {
25    fn from(value: &crate::Document) -> Self {
26        let route_kstring = KString::from(value.at_path.clone());
27
28        Self {
29            route: route_kstring,
30            excerpt: value.excerpt.clone(),
31            meta: value.metadata.clone(),
32            body: value.html.clone().unwrap_or("".into()),
33            title: value.metadata.title.clone(),
34        }
35    }
36}
37
38#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
39pub struct LiquidGlobals {
40    pub page: LiquidGlobalsPage,
41    pub content: HashMap<KString, LiquidGlobalsPage>,
42}
43
44impl LiquidGlobals {
45    pub async fn new(
46        page_arc_mutex: Arc<tokio::sync::Mutex<crate::Document>>,
47        all_documents_by_route: &Arc<HashMap<KString, LiquidGlobalsPage>>,
48    ) -> Self {
49        let page_guard = page_arc_mutex.lock().await;
50        let page_globals = LiquidGlobalsPage::from(&*page_guard);
51
52        let mut content_map = HashMap::new();
53        for (route, doc_arc_mutex) in all_documents_by_route.iter() {
54            if route != &page_globals.route {
55                content_map.insert(route.clone(), doc_arc_mutex.clone());
56            }
57        }
58
59        drop(page_guard);
60
61        Self {
62            page: page_globals,
63            content: content_map,
64        }
65    }
66
67    pub fn to_liquid_data(&self) -> liquid::Object {
68        liquid::object!({
69            "page": self.page.to_liquid_data(),
70            "content": liquid::model::to_value(&self.content)
71                 .expect("Failed to serialize content HashMap to liquid value")
72        })
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use liquid::ValueView;
80    use liquid::model::KString;
81    use pretty_assertions::assert_eq;
82    use std::collections::HashMap;
83    use std::sync::Arc;
84    use tokio::sync::Mutex;
85
86    fn create_mock_document(
87        route: &str,
88        title: &str,
89        body: Option<&str>,
90        excerpt: Option<&str>,
91    ) -> crate::Document {
92        crate::Document {
93            at_path: route.to_string(),
94            excerpt: excerpt.map(|s| s.to_string()),
95            metadata: BaseMetaData {
96                title: title.to_string(),
97                ..Default::default()
98            },
99            html: body.map(|s| s.to_string()),
100            markdown: String::new(),
101            toc: vec![],
102        }
103    }
104
105    #[test]
106    fn test_liquid_globals_page_to_liquid_data() {
107        let liquid_page = LiquidGlobalsPage {
108            route: KString::from("/test"),
109            title: "Test Page".to_string(),
110            body: "<p>Test Body</p>".to_string(),
111            meta: BaseMetaData {
112                title: "Test Meta Title".to_string(),
113                ..Default::default()
114            },
115            excerpt: None,
116        };
117
118        let liquid_value = liquid_page.to_liquid_data();
119
120        assert!(liquid_value.is_object());
121        let liquid_object = liquid_value.as_object().unwrap();
122
123        assert_eq!(
124            liquid_object
125                .get(&KString::from("route"))
126                .unwrap()
127                .as_scalar()
128                .unwrap()
129                .to_kstr(),
130            "/test"
131        );
132        assert_eq!(
133            liquid_object
134                .get(&KString::from("title"))
135                .unwrap()
136                .as_scalar()
137                .unwrap()
138                .to_kstr(),
139            "Test Page"
140        );
141        assert_eq!(
142            liquid_object
143                .get(&KString::from("body"))
144                .unwrap()
145                .as_scalar()
146                .unwrap()
147                .to_kstr(),
148            "<p>Test Body</p>"
149        );
150
151        let meta_value = liquid_object.get(&KString::from("meta")).unwrap();
152        assert!(meta_value.is_object());
153        let meta_object = meta_value.as_object().unwrap();
154        assert_eq!(
155            meta_object
156                .get(&KString::from("title"))
157                .unwrap()
158                .as_scalar()
159                .unwrap()
160                .to_kstr(),
161            "Test Meta Title"
162        );
163        assert_eq!(
164            meta_object
165                .get(&KString::from("template"))
166                .unwrap()
167                .as_scalar()
168                .unwrap()
169                .to_kstr(),
170            "default"
171        );
172        assert_eq!(
173            liquid_object
174                .get(&KString::from("excerpt"))
175                .unwrap()
176                .is_nil(),
177            true
178        );
179
180        /*let expected_tags_liquid_array = liquid::model::Value::Array(vec![
181            liquid::model::Value::scalar("tag1"),
182            liquid::model::Value::scalar("tag2"),
183        ]);
184        assert_eq!(
185            meta_object
186                .get(&KString::from("tags"))
187                .unwrap()
188                .as_array()
189                .unwrap(),
190            &expected_tags_liquid_array
191        );
192
193        let expected_keywords_liquid_array = liquid::model::Value::Array(vec![]);
194        assert_eq!(
195            meta_object.get(&KString::from("keywords")).unwrap(), // Get &LiquidValue
196            &expected_keywords_liquid_array // Compare with expected &LiquidValue::Array
197        );*/
198    }
199
200    #[tokio::test]
201    async fn test_liquid_globals_new() {
202        let page_doc = create_mock_document("/page", "Page Title", Some("<p>page body</p>"), None);
203        let content_doc_1 = create_mock_document(
204            "/posts/post-1",
205            "Post One",
206            Some("<p>post 1 body</p>"),
207            Some("excerpt 1"),
208        );
209        let content_doc_2 = create_mock_document("/about", "About Us", None, None);
210
211        let page_arc_mutex = Arc::new(Mutex::new(page_doc.clone()));
212        let post1_arc_mutex = Arc::new(Mutex::new(content_doc_1.clone()));
213        let about_arc_mutex = Arc::new(Mutex::new(content_doc_2.clone()));
214
215        let mut all_documents_by_route = HashMap::new();
216        all_documents_by_route.insert(KString::from("/page"), LiquidGlobalsPage::from(&page_doc));
217        all_documents_by_route.insert(
218            KString::from("/posts/post-1"),
219            LiquidGlobalsPage::from(&content_doc_1),
220        );
221        all_documents_by_route.insert(
222            KString::from("/about"),
223            LiquidGlobalsPage::from(&content_doc_2),
224        );
225
226        let liquid_globals = LiquidGlobals::new(
227            Arc::clone(&page_arc_mutex),
228            &Arc::new(all_documents_by_route),
229        )
230        .await;
231
232        let page_doc_guard = page_arc_mutex.lock().await;
233        let expected_page_globals = LiquidGlobalsPage::from(&*page_doc_guard);
234        assert_eq!(liquid_globals.page, expected_page_globals);
235        drop(page_doc_guard);
236
237        assert_eq!(liquid_globals.content.len(), 2);
238
239        assert!(
240            liquid_globals
241                .content
242                .contains_key(&KString::from("/posts/post-1"))
243        );
244        assert!(
245            liquid_globals
246                .content
247                .contains_key(&KString::from("/about"))
248        );
249        assert!(!liquid_globals.content.contains_key(&KString::from("/page")));
250
251        let post1_doc_guard = post1_arc_mutex.lock().await;
252        let expected_post1_globals = LiquidGlobalsPage::from(&*post1_doc_guard);
253        assert_eq!(
254            liquid_globals
255                .content
256                .get(&KString::from("/posts/post-1"))
257                .unwrap(),
258            &expected_post1_globals
259        );
260        drop(post1_doc_guard);
261
262        let about_doc_guard = about_arc_mutex.lock().await;
263        let expected_about_globals = LiquidGlobalsPage::from(&*about_doc_guard);
264        assert_eq!(
265            liquid_globals
266                .content
267                .get(&KString::from("/about"))
268                .unwrap(),
269            &expected_about_globals
270        );
271        drop(about_doc_guard);
272    }
273
274    #[tokio::test]
275    async fn test_liquid_globals_new_only_page_doc() {
276        let page_doc = create_mock_document("/index", "Home Page", Some("<p>home</p>"), None);
277        let page_arc_mutex = Arc::new(Mutex::new(page_doc.clone()));
278        let page_global = LiquidGlobalsPage::from(&page_doc);
279
280        let mut all_documents_by_route = HashMap::new();
281        all_documents_by_route.insert(KString::from("/index"), page_global);
282
283        let liquid_globals = LiquidGlobals::new(
284            Arc::clone(&page_arc_mutex),
285            &Arc::new(all_documents_by_route),
286        )
287        .await;
288
289        let page_doc_guard = page_arc_mutex.lock().await;
290        let expected_page_globals = LiquidGlobalsPage::from(&*page_doc_guard);
291        assert_eq!(liquid_globals.page, expected_page_globals);
292        drop(page_doc_guard);
293
294        assert_eq!(liquid_globals.content.len(), 0);
295        assert!(liquid_globals.content.is_empty());
296    }
297
298    #[test]
299    fn test_liquid_globals_to_liquid_data() {
300        let page_page = LiquidGlobalsPage {
301            route: KString::from("/page"),
302            title: "Page".to_string(),
303            body: "<p>page</p>".to_string(),
304            meta: BaseMetaData {
305                title: "Page Meta".to_string(),
306                ..Default::default()
307            },
308            excerpt: Some("page excerpt".to_string()),
309        };
310        let content_page_1 = LiquidGlobalsPage {
311            route: KString::from("/post-1"),
312            title: "Post 1".to_string(),
313            body: "<p>post1</p>".to_string(),
314            meta: BaseMetaData {
315                title: "Post 1 Meta".to_string(),
316                ..Default::default()
317            },
318            excerpt: Some("post1 excerpt".to_string()),
319        };
320        let content_page_2 = LiquidGlobalsPage {
321            route: KString::from("/about"),
322            title: "About".to_string(),
323            body: "".into(),
324            meta: BaseMetaData {
325                title: "About Meta".to_string(),
326                ..Default::default()
327            },
328            excerpt: None,
329        };
330
331        let mut content_map: HashMap<KString, LiquidGlobalsPage> = HashMap::new();
332        content_map.insert(KString::from("/post-1"), content_page_1.clone());
333        content_map.insert(KString::from("/about"), content_page_2.clone());
334
335        let liquid_globals = LiquidGlobals {
336            page: page_page.clone(),
337            content: content_map.clone(), // Clone for the struct
338        };
339
340        let liquid_object = liquid_globals.to_liquid_data();
341
342        assert!(liquid_object.is_object());
343        let liquid_map = liquid_object.as_object().unwrap();
344
345        assert!(liquid_map.contains_key(&KString::from("page")));
346        assert!(liquid_map.contains_key(&KString::from("content")));
347        assert_eq!(liquid_map.size(), 2);
348
349        /*let page_value = liquid_map.get(&KString::from("page")).unwrap();
350        let expected_page_liquid_value = page_page.to_liquid_data();
351        assert_eq!(page_value, &expected_page_liquid_value);
352
353        let content_value = liquid_map.get(&KString::from("content")).unwrap();
354        assert!(content_value.is_object());
355        let expected_content_liquid_value = liquid::model::to_value(&content_map)
356            .expect("Failed to serialize expected content map");
357        assert_eq!(content_value, &expected_content_liquid_value);
358
359        let content_object = content_value.as_object().unwrap();
360        let post1_liquid_value = content_object.get(&KString::from("/post-1")).unwrap();
361        assert!(post1_liquid_value.is_object());
362        let post1_object = post1_liquid_value.as_object().unwrap();
363        assert_eq!(
364            post1_object
365                .get(&KString::from("title"))
366                .unwrap()
367                .as_scalar()
368                .unwrap()
369                .to_kstr(),
370            "Post 1"
371        );
372
373        let about_liquid_value = content_object.get(&KString::from("/about")).unwrap();
374        assert!(about_liquid_value.is_object());
375        let about_object = about_liquid_value.as_object().unwrap();
376        assert_eq!(
377            about_object
378                .get(&KString::from("route"))
379                .unwrap()
380                .as_scalar()
381                .unwrap()
382                .to_kstr(),
383            "/about"
384        );*/
385    }
386}