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 }
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(), };
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 }
386}