From bd24aeca403e4d0611f3afa56e65b12dd0d8da4f Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Thu, 19 Sep 2024 21:04:59 +0100 Subject: [PATCH] =?UTF-8?q?build(ssg):=20=F0=9F=94=A7=20Add=20caching=20me?= =?UTF-8?q?chanism?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ssg-core/src/compiler/service.rs | 10 ++- ssg-template/Cargo.toml | 2 +- ssg-template/examples/template_example.rs | 101 +++++++++++++++++----- ssg-template/src/cache.rs | 98 +++++++++++++++++++++ ssg-template/src/context.rs | 18 ++++ ssg-template/src/engine.rs | 40 ++++++--- ssg-template/src/lib.rs | 3 + ssg-template/tests/engine_tests.rs | 69 ++++++++++----- 8 files changed, 283 insertions(+), 58 deletions(-) create mode 100644 ssg-template/src/cache.rs diff --git a/ssg-core/src/compiler/service.rs b/ssg-core/src/compiler/service.rs index 884996cb..b77afe68 100644 --- a/ssg-core/src/compiler/service.rs +++ b/ssg-core/src/compiler/service.rs @@ -8,6 +8,7 @@ use ssg_rss::{ generate_rss, macro_set_rss_data_fields, models::data::RssData, }; use ssg_sitemap::create_site_map_data; +use std::time::Duration; use crate::{ macro_cleanup_directories, macro_create_directories, @@ -70,7 +71,10 @@ pub fn compile( HashMap::new(); // Initialize the templating engine - let engine = Engine::new(template_path.to_str().unwrap()); + let mut engine = Engine::new( + template_path.to_str().unwrap(), + Duration::from_secs(60), + ); // Process source files and store results in 'compiled_files' vector let compiled_files: Result> = source_files @@ -78,7 +82,7 @@ pub fn compile( .map(|file| { process_file( &file, - &engine, + &mut engine, template_path, &navigation, &mut global_tags_data, @@ -127,7 +131,7 @@ pub fn compile( /// Processes a single file, generating all necessary data and content. fn process_file( file: &FileData, - engine: &Engine, + engine: &mut Engine, _template_path: &Path, navigation: &str, global_tags_data: &mut HashMap>, diff --git a/ssg-template/Cargo.toml b/ssg-template/Cargo.toml index d880c751..a15a0987 100644 --- a/ssg-template/Cargo.toml +++ b/ssg-template/Cargo.toml @@ -33,7 +33,7 @@ regex = "1.5" # reqwest is a popular HTTP client for Rust, used for handling remote template fetching. # The `blocking` feature allows synchronous HTTP requests. -reqwest = { version = "0.12", features = ["blocking"] } +reqwest = { version = "0.12", features = ["json", "blocking"] } # serde is used for serializing and deserializing data structures, including JSON. # The `derive` feature simplifies the process of creating serializable and deserializable structs. diff --git a/ssg-template/examples/template_example.rs b/ssg-template/examples/template_example.rs index 1d3f0dfb..b90f387e 100644 --- a/ssg-template/examples/template_example.rs +++ b/ssg-template/examples/template_example.rs @@ -12,6 +12,7 @@ #![allow(missing_docs)] use ssg_template::{Context, Engine, TemplateError}; +use std::time::Duration; use std::{ collections::HashMap, fs::{self, File}, @@ -106,7 +107,10 @@ fn demonstrates_template_rendering_example() -> Result<(), TemplateError> ); // Step 4: Initialize the Engine with the base directory where the template is located - let engine = Engine::new(base_template_path.to_str().unwrap()); // Provide the dynamic base directory + let mut engine = Engine::new( + base_template_path.to_str().unwrap(), + Duration::from_secs(60), + ); // Provide the dynamic base directory // Step 5: Render the template by specifying the filename without the extension (just "index") let rendered_template = engine.render_page(&context, "index")?; @@ -136,7 +140,10 @@ fn demonstrates_error_handling_example() -> Result<(), TemplateError> { "#; template_file.write_all(template_content.as_bytes())?; - let engine = Engine::new(template_dir_path.to_str().unwrap()); + let mut engine = Engine::new( + template_dir_path.to_str().unwrap(), + Duration::from_secs(60), + ); match engine.render_page(&context, "invalid") { // Pass only "invalid", not "invalid.html" @@ -181,7 +188,10 @@ fn demonstrates_template_with_loop_example() -> Result<(), TemplateError> "#; template_file.write_all(template_content.as_bytes())?; - let engine = Engine::new(template_dir_path.to_str().unwrap()); + let mut engine = Engine::new( + template_dir_path.to_str().unwrap(), + Duration::from_secs(60), + ); let rendered_template = engine.render_page(&context, "items")?; println!("Rendered Template with Loop: ✅ \n{}", rendered_template); @@ -222,7 +232,10 @@ fn demonstrates_template_with_conditionals_example( "#; template_file.write_all(template_content.as_bytes())?; - let engine = Engine::new(template_dir_path.to_str().unwrap()); + let mut engine = Engine::new( + template_dir_path.to_str().unwrap(), + Duration::from_secs(60), + ); let rendered_template = engine.render_page(&context, "conditional")?; @@ -262,7 +275,10 @@ fn demonstrates_nested_context_example() -> Result<(), TemplateError> { "#; template_file.write_all(template_content.as_bytes())?; - let engine = Engine::new(template_dir_path.to_str().unwrap()); + let mut engine = Engine::new( + template_dir_path.to_str().unwrap(), + Duration::from_secs(60), + ); let rendered_template = engine.render_page(&context, "user_profile")?; // Pass only "user_profile", not "user_profile.html" @@ -313,7 +329,10 @@ fn demonstrates_template_with_partials_example( "#; main_template_file.write_all(main_template_content.as_bytes())?; - let engine = Engine::new(template_dir_path.to_str().unwrap()); + let mut engine = Engine::new( + template_dir_path.to_str().unwrap(), + Duration::from_secs(60), + ); // Render the header and footer templates separately and inject the results into the main template let rendered_header = engine.render_page(&context, "header")?; @@ -375,7 +394,10 @@ fn demonstrates_template_with_inheritance_example( context.set("footer".to_string(), "Footer Content".to_string()); // Initialize the Engine with the template directory - let engine = Engine::new(template_dir_path.to_str().unwrap()); + let mut engine = Engine::new( + template_dir_path.to_str().unwrap(), + Duration::from_secs(60), + ); // Render the 'about' content separately let about_content = fs::read_to_string(about_path)?; @@ -439,7 +461,10 @@ fn demonstrates_multiple_templates_example() -> Result<(), TemplateError> .set("email".to_string(), "contact@example.com".to_string()); // Initialize the Engine with the template directory - let engine = Engine::new(template_dir_path.to_str().unwrap()); + let mut engine = Engine::new( + template_dir_path.to_str().unwrap(), + Duration::from_secs(60), + ); // Render the home and contact pages independently let rendered_home = engine.render_page(&context_home, "home")?; @@ -480,7 +505,10 @@ fn demonstrates_large_context_example() -> Result<(), TemplateError> { template_file.write_all(template_content.as_bytes())?; // Initialize the Engine with the template directory - let engine = Engine::new(template_dir_path.to_str().unwrap()); + let mut engine = Engine::new( + template_dir_path.to_str().unwrap(), + Duration::from_secs(60), + ); // Render the template with the large context let rendered_template = @@ -524,7 +552,10 @@ fn demonstrates_dynamic_content_example() -> Result<(), TemplateError> { "#; template_file.write_all(template_content.as_bytes())?; - let engine = Engine::new(template_dir_path.to_str().unwrap()); + let mut engine = Engine::new( + template_dir_path.to_str().unwrap(), + Duration::from_secs(60), + ); // Render the template with the dynamic content let rendered_template = @@ -558,7 +589,10 @@ fn demonstrates_template_caching_example() -> Result<(), TemplateError> template_file.write_all(template_content.as_bytes())?; // Initialize the Engine with the template directory - let engine = Engine::new(template_dir_path.to_str().unwrap()); + let mut engine = Engine::new( + template_dir_path.to_str().unwrap(), + Duration::from_secs(60), + ); // Create a context for rendering let mut context = Context::new(); @@ -611,7 +645,10 @@ fn demonstrates_template_debugging_example() -> Result<(), TemplateError> "#; template_file.write_all(template_content.as_bytes())?; - let engine = Engine::new(template_dir_path.to_str().unwrap()); + let mut engine = Engine::new( + template_dir_path.to_str().unwrap(), + Duration::from_secs(60), + ); // Try to render the template and catch any missing context variables match engine.render_page(&context, "debug_template") { @@ -660,7 +697,10 @@ fn demonstrates_form_template_example() -> Result<(), TemplateError> { context.set("email".to_string(), "john@example.com".to_string()); // Initialize the Engine with the template directory - let engine = Engine::new(template_dir_path.to_str().unwrap()); + let mut engine = Engine::new( + template_dir_path.to_str().unwrap(), + Duration::from_secs(60), + ); // Render the form template let rendered_template = @@ -680,7 +720,10 @@ fn demonstrates_custom_error_handling_example( .set("title".to_string(), "Custom Error Example".to_string()); // Try to render a non-existent template to trigger a custom error - let engine = Engine::new("path/to/nonexistent/templates"); + let mut engine = Engine::new( + "path/to/nonexistent/templates", + Duration::from_secs(60), + ); match engine.render_page(&context, "nonexistent_template") { Ok(rendered) => println!("Unexpected success: \n{}", rendered), @@ -736,7 +779,10 @@ fn demonstrates_pagination_example() -> Result<(), TemplateError> { context.set("total_pages".to_string(), "3".to_string()); // Initialize the Engine with the template directory - let engine = Engine::new(template_dir_path.to_str().unwrap()); + let mut engine = Engine::new( + template_dir_path.to_str().unwrap(), + Duration::from_secs(60), + ); // Render the paginated template with the pre-processed posts let rendered_template = @@ -781,7 +827,10 @@ fn demonstrates_internationalization_example( "#; template_file.write_all(template_content.as_bytes())?; - let engine = Engine::new(template_dir_path.to_str().unwrap()); + let mut engine = Engine::new( + template_dir_path.to_str().unwrap(), + Duration::from_secs(60), + ); // Render the template with the language-based greeting let rendered_template = @@ -821,7 +870,10 @@ fn demonstrates_template_fragments_example() -> Result<(), TemplateError> "#; main_template_file.write_all(main_template_content.as_bytes())?; - let engine = Engine::new(template_dir_path.to_str().unwrap()); + let mut engine = Engine::new( + template_dir_path.to_str().unwrap(), + Duration::from_secs(60), + ); // Load and render fragments let rendered_header = @@ -883,7 +935,10 @@ fn demonstrates_markdown_to_html_example() -> Result<(), TemplateError> template_file.write_all(template_content.as_bytes())?; // Step 6: Initialize the Engine with the template directory - let engine = Engine::new(template_dir_path.to_str().unwrap()); + let mut engine = Engine::new( + template_dir_path.to_str().unwrap(), + Duration::from_secs(60), + ); // Step 7: Render the template with the manually converted markdown content let rendered_template = @@ -923,7 +978,10 @@ fn demonstrates_template_versioning_example( if client_version == 1 { "v1" } else { "v2" }; // Initialize the Engine with the template directory - let engine = Engine::new(template_dir_path.to_str().unwrap()); + let mut engine = Engine::new( + template_dir_path.to_str().unwrap(), + Duration::from_secs(60), + ); // Render the appropriate version of the template let rendered_template = @@ -964,7 +1022,10 @@ fn demonstrates_ajax_template_example() -> Result<(), TemplateError> { "#; template_file.write_all(template_content.as_bytes())?; - let engine = Engine::new(template_dir_path.to_str().unwrap()); + let mut engine = Engine::new( + template_dir_path.to_str().unwrap(), + Duration::from_secs(60), + ); // Render the template meant for AJAX let rendered_template = diff --git a/ssg-template/src/cache.rs b/ssg-template/src/cache.rs new file mode 100644 index 00000000..345a95fd --- /dev/null +++ b/ssg-template/src/cache.rs @@ -0,0 +1,98 @@ +use std::collections::HashMap; +use std::time::{Duration, Instant}; + +/// Represents a cached item with its value and expiration time. +struct CachedItem { + value: T, + expiration: Instant, +} + +/// A simple cache implementation with expiration. +pub struct Cache { + items: HashMap>, + ttl: Duration, +} + +impl Cache { + /// Creates a new Cache with the specified time-to-live (TTL) for items. + /// + /// # Arguments + /// + /// * `ttl` - The time-to-live for cached items. + /// + /// # Example + /// + /// ``` + /// use ssg_template::cache::Cache; + /// use std::time::Duration; + /// + /// let cache: Cache = Cache::new(Duration::from_secs(60)); + /// ``` + pub fn new(ttl: Duration) -> Self { + Cache { + items: HashMap::new(), + ttl, + } + } + + /// Inserts a key-value pair into the cache. + /// + /// # Arguments + /// + /// * `key` - The key to insert. + /// * `value` - The value to insert. + /// + /// # Example + /// + /// ``` + /// use ssg_template::cache::Cache; + /// use std::time::Duration; + /// + /// let mut cache = Cache::new(Duration::from_secs(60)); + /// cache.insert("key".to_string(), "value".to_string()); + /// ``` + pub fn insert(&mut self, key: K, value: V) { + let expiration = Instant::now() + self.ttl; + self.items.insert(key, CachedItem { value, expiration }); + } + + /// Retrieves a value from the cache if it exists and hasn't expired. + /// + /// # Arguments + /// + /// * `key` - The key to look up. + /// + /// # Returns + /// + /// An `Option` containing the value if it exists and hasn't expired, or `None` otherwise. + /// + /// # Example + /// + /// ``` + /// use ssg_template::cache::Cache; + /// use std::time::Duration; + /// + /// let mut cache = Cache::new(Duration::from_secs(60)); + /// cache.insert("key".to_string(), "value".to_string()); + /// + /// assert_eq!(cache.get(&"key".to_string()), Some(&"value".to_string())); + /// ``` + pub fn get(&self, key: &K) -> Option<&V> { + self.items.get(key).and_then(|item| { + if item.expiration > Instant::now() { + Some(&item.value) + } else { + None + } + }) + } + + /// Removes expired items from the cache. + /// + /// This method should be called periodically to clean up the cache. + /// + pub fn remove_expired(&mut self) { + self.items + .retain(|_, item| item.expiration > Instant::now()); + } +} diff --git a/ssg-template/src/context.rs b/ssg-template/src/context.rs index f12c719b..6c73d1bd 100644 --- a/ssg-template/src/context.rs +++ b/ssg-template/src/context.rs @@ -1,4 +1,6 @@ +use std::collections::hash_map::DefaultHasher; use std::collections::HashMap; +use std::hash::{Hash, Hasher}; /// Represents the context for template rendering. /// @@ -11,6 +13,22 @@ pub struct Context { } impl Context { + /// Computes a hash of the context. + /// + /// This method is used for caching purposes. + /// + /// # Returns + /// + /// A `u64` representing the hash of the context. + pub(crate) fn hash(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + for (key, value) in &self.elements { + key.hash(&mut hasher); + value.hash(&mut hasher); + } + hasher.finish() + } + /// Creates a new, empty `Context`. /// /// # Examples diff --git a/ssg-template/src/engine.rs b/ssg-template/src/engine.rs index 54cc35c0..806e2f99 100644 --- a/ssg-template/src/engine.rs +++ b/ssg-template/src/engine.rs @@ -1,9 +1,11 @@ // engine.rs +use crate::cache::Cache; use crate::{Context, TemplateError}; use std::collections::HashMap; use std::fs::{self, File}; use std::path::{Path, PathBuf}; +use std::time::Duration; #[derive(Debug, Default, PartialEq, Eq, Clone)] /// ## Struct: `PageOptions` - Options for rendering a page template @@ -42,6 +44,7 @@ impl<'a> PageOptions<'a> { /// The main template rendering engine. pub struct Engine { template_path: String, + render_cache: Cache, } impl Engine { @@ -50,21 +53,24 @@ impl Engine { /// # Arguments /// /// * `template_path` - A string slice that holds the path to the template directory. + /// * `cache_ttl` - The time-to-live for cached rendered templates. /// - /// # Examples + /// # Example /// /// ``` /// use ssg_template::Engine; + /// use std::time::Duration; /// - /// let engine = Engine::new("path/to/templates"); + /// let engine = Engine::new("path/to/templates", Duration::from_secs(300)); /// ``` - pub fn new(template_path: &str) -> Self { + pub fn new(template_path: &str, cache_ttl: Duration) -> Self { Self { template_path: template_path.to_string(), + render_cache: Cache::new(cache_ttl), } } - /// Renders a page using the specified layout and context. + /// Renders a page using the specified layout and context, with caching. /// /// # Arguments /// @@ -75,12 +81,13 @@ impl Engine { /// /// A `Result` containing the rendered page as a `String`, or a `TemplateError` if rendering fails. /// - /// # Examples + /// # Example /// /// ``` /// use ssg_template::{Engine, Context}; + /// use std::time::Duration; /// - /// let engine = Engine::new("path/to/templates"); + /// let mut engine = Engine::new("path/to/templates", Duration::from_secs(300)); /// let mut context = Context::new(); /// context.set("title".to_string(), "My Page".to_string()); /// @@ -90,16 +97,26 @@ impl Engine { /// } /// ``` pub fn render_page( - &self, + &mut self, context: &Context, layout: &str, ) -> Result { + let cache_key = format!("{}:{}", layout, context.hash()); + + if let Some(cached) = self.render_cache.get(&cache_key) { + return Ok(cached.to_string()); + } + let template_path = Path::new(&self.template_path) .join(format!("{}.html", layout)); let template_content = fs::read_to_string(&template_path) .map_err(TemplateError::Io)?; - self.render_template(&template_content, &context.elements) + let rendered = + self.render_template(&template_content, &context.elements)?; + self.render_cache.insert(cache_key, rendered.clone()); + + Ok(rendered) } /// Renders a template string with the given context. @@ -153,8 +170,9 @@ impl Engine { /// /// ``` /// use ssg_template::Engine; + /// use std::time::Duration; /// - /// let engine = Engine::new("path/to/templates"); + /// let engine = Engine::new("path/to/templates", Duration::from_secs(60)); /// match engine.create_template_folder(Some("custom/templates")) { /// Ok(path) => println!("Template folder created at: {}", path), /// Err(e) => eprintln!("Error creating template folder: {}", e), @@ -267,7 +285,9 @@ impl Engine { /// /// ``` /// use ssg_template::Engine; - /// let engine = Engine::new("dummy/path"); + /// use std::time::Duration; + /// + /// let engine = Engine::new("dummy/path",Duration::from_secs(60)); /// let result = engine.download_template_files("https://example.com/templates"); /// ``` pub fn download_template_files( diff --git a/ssg-template/src/lib.rs b/ssg-template/src/lib.rs index 6bb011af..4be6274c 100644 --- a/ssg-template/src/lib.rs +++ b/ssg-template/src/lib.rs @@ -21,6 +21,9 @@ pub mod engine; /// The `error` module contains the `TemplateError` enum, which represents errors that can occur during template processing. pub mod error; +/// The `cache` module contains the `Cache` struct, which is used to cache rendered templates for improved performance. +pub mod cache; + pub use context::Context; pub use engine::{Engine, PageOptions}; pub use error::TemplateError; diff --git a/ssg-template/tests/engine_tests.rs b/ssg-template/tests/engine_tests.rs index b8968b1a..ac16c793 100644 --- a/ssg-template/tests/engine_tests.rs +++ b/ssg-template/tests/engine_tests.rs @@ -8,11 +8,13 @@ mod tests { use ssg_template::{Engine, TemplateError}; use std::collections::HashMap; + use std::time::Duration; /// Test rendering a template with a valid context. #[test] fn test_engine_render_template() { - let engine = Engine::new("dummy/path"); + let engine = + Engine::new("dummy/path", Duration::from_secs(60)); let mut context = HashMap::new(); context.insert("name".to_string(), "World".to_string()); context.insert("greeting".to_string(), "Hello".to_string()); @@ -26,7 +28,8 @@ mod tests { /// Test rendering a template with unresolved tags. #[test] fn test_engine_render_template_unresolved_tags() { - let engine = Engine::new("dummy/path"); + let engine = + Engine::new("dummy/path", Duration::from_secs(60)); let context: HashMap = HashMap::new(); let template = "{{greeting}}, {{name}}!"; @@ -40,13 +43,12 @@ mod tests { /// Test rendering an empty template. #[test] fn test_engine_render_empty_template() { - let engine = Engine::new("dummy/path"); + let engine = + Engine::new("dummy/path", Duration::from_secs(60)); let context: HashMap = HashMap::new(); let template = ""; let result = engine.render_template(template, &context); - - // Check that the result is an error, specifically the "Template is empty" error assert!( matches!(result, Err(TemplateError::RenderError(msg)) if msg == "Template is empty") ); @@ -55,7 +57,8 @@ mod tests { /// Test rendering a template with an empty context. #[test] fn test_engine_render_empty_context() { - let engine = Engine::new("dummy/path"); + let engine = + Engine::new("dummy/path", Duration::from_secs(60)); let context: HashMap = HashMap::new(); let template = "{{greeting}}, {{name}}!"; @@ -69,7 +72,8 @@ mod tests { /// Test rendering a template with special characters in the context. #[test] fn test_engine_render_special_characters_in_context() { - let engine = Engine::new("dummy/path"); + let engine = + Engine::new("dummy/path", Duration::from_secs(60)); let mut context = HashMap::new(); context.insert( "name".to_string(), @@ -86,7 +90,8 @@ mod tests { /// Test rendering with a large context and template. #[test] fn test_engine_large_context() { - let engine = Engine::new("dummy/path"); + let engine = + Engine::new("dummy/path", Duration::from_secs(60)); let mut context = HashMap::new(); let keys: Vec = (0..1000).map(|i| format!("key{}", i)).collect(); @@ -117,16 +122,17 @@ mod tests { /// Tests related to file operations, such as downloading templates. mod file_tests { - use ssg_template::{Context, Engine, TemplateError}; - use super::*; + use ssg_template::{Context, Engine, TemplateError}; + use std::time::Duration; /// Test downloading template files from a URL. /// /// Note: This test may fail if there is no internet connection or the URL is unreachable. #[test] fn test_engine_download_template_files() { - let engine = Engine::new("dummy/path"); + let engine = + Engine::new("dummy/path", Duration::from_secs(60)); let url = "https://raw.githubusercontent.com/sebastienrousseau/shokunin/main/templates"; let result = engine.download_template_files(url); assert!(result.is_ok()); @@ -135,7 +141,8 @@ mod tests { /// Test rendering with an invalid template path. #[test] fn test_engine_invalid_template_path() { - let engine = Engine::new("invalid/path"); + let mut engine = + Engine::new("invalid/path", Duration::from_secs(60)); let context = Context { elements: HashMap::new(), }; @@ -172,6 +179,7 @@ mod file_operations_tests { use std::collections::HashMap; use std::fs::File; use std::io::Write; + use std::time::Duration; use tempfile::tempdir; /// Test `render_page` with a valid template path. @@ -190,7 +198,10 @@ mod file_operations_tests { .unwrap(); // Initialize engine with the temporary directory - let engine = Engine::new(temp_dir.path().to_str().unwrap()); + let mut engine = Engine::new( + temp_dir.path().to_str().unwrap(), + Duration::from_secs(60), + ); // Prepare the context let mut elements = HashMap::new(); @@ -213,7 +224,8 @@ mod file_operations_tests { /// Test `render_page` with an invalid template path. #[test] fn test_render_page_invalid_path() { - let engine = Engine::new("invalid/path"); + let mut engine = + Engine::new("invalid/path", Duration::from_secs(60)); let context = Context { elements: HashMap::new(), }; @@ -224,7 +236,8 @@ mod file_operations_tests { /// Test `render_page` when the file is missing. #[test] fn test_render_page_missing_file() { - let engine = Engine::new("missing/path"); + let mut engine = + Engine::new("missing/path", Duration::from_secs(60)); let context = Context { elements: HashMap::new(), }; @@ -237,11 +250,12 @@ mod context_edge_cases_tests { use ssg_template::{Engine, TemplateError}; use std::collections::HashMap; + use std::time::Duration; /// Test rendering a template with an empty context. #[test] fn test_render_template_empty_context() { - let engine = Engine::new("dummy/path"); + let engine = Engine::new("dummy/path", Duration::from_secs(60)); let context: HashMap = HashMap::new(); let template = "{{greeting}}, {{name}}!"; @@ -252,7 +266,7 @@ mod context_edge_cases_tests { /// Test rendering a template with special characters in the context. #[test] fn test_render_template_special_characters() { - let engine = Engine::new("dummy/path"); + let engine = Engine::new("dummy/path", Duration::from_secs(60)); let mut context = HashMap::new(); context.insert( "name".to_string(), @@ -270,13 +284,14 @@ mod context_edge_cases_tests { mod additional_tests { use ssg_template::{Context, Engine, PageOptions, TemplateError}; + use std::time::Duration; use std::{collections::HashMap, fs::File}; use tempfile::tempdir; /// Test rendering a template with an invalid format. #[test] fn test_engine_render_template_invalid_format() { - let engine = Engine::new("dummy/path"); + let engine = Engine::new("dummy/path", Duration::from_secs(60)); let mut context = HashMap::new(); context.insert("name".to_string(), "World".to_string()); context.insert("greeting".to_string(), "Hello".to_string()); @@ -298,7 +313,10 @@ mod additional_tests { File::create(&layout_path).unwrap(); // Initialize engine with the temporary directory - let engine = Engine::new(temp_dir.path().to_str().unwrap()); + let mut engine = Engine::new( + temp_dir.path().to_str().unwrap(), + Duration::from_secs(60), + ); // Prepare the context let mut elements = HashMap::new(); @@ -320,7 +338,10 @@ mod additional_tests { // Create the layout file, but simulate a permission error by not allowing writes File::create(&layout_path).unwrap(); - let engine = Engine::new("/restricted/directory"); + let mut engine = Engine::new( + "/restricted/directory", + Duration::from_secs(60), + ); let mut elements = HashMap::new(); elements.insert("greeting".to_string(), "Hello".to_string()); @@ -354,7 +375,7 @@ mod additional_tests { /// Test rendering a template with an invalid context data type (e.g., integer values). #[test] fn test_render_template_invalid_context_data_type() { - let engine = Engine::new("templates/"); + let engine = Engine::new("templates/", Duration::from_secs(60)); let template = "Hello, {{name}}!"; let mut invalid_context = HashMap::new(); invalid_context.insert("name".to_string(), "World".to_string()); // Valid @@ -367,7 +388,7 @@ mod additional_tests { /// Test render_template error handling with invalid template syntax. #[test] fn test_render_template_invalid_template_syntax() { - let engine = Engine::new("templates/"); + let engine = Engine::new("templates/", Duration::from_secs(60)); let invalid_template = "Hello, {{name"; // Missing closing braces let mut context = HashMap::new(); context.insert("name".to_string(), "World".to_string()); @@ -379,7 +400,7 @@ mod additional_tests { /// Test large template rendering. #[test] fn test_render_large_template() { - let engine = Engine::new("templates/"); + let engine = Engine::new("templates/", Duration::from_secs(60)); let large_template = "Hello, {{name}}".repeat(1000); // Large template with repetitive pattern let mut context = HashMap::new(); context.insert("name".to_string(), "World".to_string()); @@ -405,7 +426,7 @@ mod additional_tests { /// Test empty template rendering. #[test] fn test_render_template_empty_template() { - let engine = Engine::new("templates/"); + let engine = Engine::new("templates/", Duration::from_secs(60)); let empty_template = ""; // Empty template let mut context = HashMap::new(); context.insert("name".to_string(), "World".to_string());