1818 T : FromStr + Clone ,
1919 T :: Err : Display ,
2020{
21- /// Resolve template using Zerv object context, return final value
22- pub fn resolve ( & self , zerv : & Zerv ) -> Result < T , ZervError > {
21+ /// Resolve template using optional Zerv object context, return final value
22+ pub fn resolve ( & self , zerv : Option < & Zerv > ) -> Result < T , ZervError > {
2323 match self {
2424 Template :: Value ( v) => Ok ( v. clone ( ) ) ,
2525 Template :: Template ( template) => {
@@ -32,25 +32,40 @@ where
3232 }
3333 }
3434
35- /// Render Handlebars template using Zerv object as context
36- fn render_template ( template : & str , zerv : & Zerv ) -> Result < String , ZervError > {
35+ /// Render Handlebars template using optional Zerv object as context
36+ fn render_template ( template : & str , zerv : Option < & Zerv > ) -> Result < String , ZervError > {
3737 let mut handlebars = handlebars:: Handlebars :: new ( ) ;
3838 handlebars. set_strict_mode ( false ) ; // Allow missing variables
3939
4040 // Register custom Zerv helpers
4141 register_helpers ( & mut handlebars) ?;
4242
43- // Create template context from Zerv object
44- let template_context = TemplateContext :: from_zerv ( zerv) ;
45- let context = serde_json:: to_value ( template_context)
46- . map_err ( |e| ZervError :: TemplateError ( format ! ( "Serialization error: {e}" ) ) ) ?;
43+ // Create template context from Zerv object or empty context
44+ let context = if let Some ( z) = zerv {
45+ let template_context = TemplateContext :: from_zerv ( z) ;
46+ serde_json:: to_value ( template_context)
47+ . map_err ( |e| ZervError :: TemplateError ( format ! ( "Serialization error: {e}" ) ) ) ?
48+ } else {
49+ serde_json:: Value :: Object ( serde_json:: Map :: new ( ) )
50+ } ;
4751
4852 handlebars
4953 . render_template ( template, & context)
5054 . map_err ( |e| ZervError :: TemplateError ( format ! ( "Template render error: {e}" ) ) )
5155 }
5256}
5357
58+ impl Template < String > {
59+ /// Parse a template string and render it to final string result.
60+ /// Uses empty context (no Zerv object) and handles errors internally.
61+ pub fn render ( template_str : & str ) -> String {
62+ let template = Self :: from ( template_str. to_string ( ) ) ;
63+ template
64+ . resolve ( None )
65+ . unwrap_or_else ( |e| format ! ( "template_error: {}" , e) )
66+ }
67+ }
68+
5469impl From < String > for Template < String > {
5570 fn from ( value : String ) -> Self {
5671 if value. contains ( "{{" ) && value. contains ( "}}" ) {
@@ -128,7 +143,64 @@ mod tests {
128143 fn test_template_resolve ( #[ case] input : & str , #[ case] expected : & str ) {
129144 let template: Template < String > = Template :: from_str ( input) . unwrap ( ) ;
130145 let zerv = ZervFixture :: new ( ) . with_version ( 1 , 2 , 0 ) . build ( ) ;
131- let result = template. resolve ( & zerv) . unwrap ( ) ;
146+ let result = template. resolve ( Some ( & zerv) ) . unwrap ( ) ;
132147 assert_eq ! ( result, expected) ;
133148 }
149+
150+ #[ rstest]
151+ // Test basic string without template syntax
152+ #[ case( "static_text" , "static_text" ) ]
153+ // Test template with helpers (without Zerv context)
154+ #[ case( "{{hash_int 'test' 5}}" , "16668" ) ]
155+ #[ case( "{{hash 'test'}}" , "c7dedb4" ) ]
156+ #[ case( "{{sanitize 'Feature-Branch'}}" , "feature.branch" ) ]
157+ #[ case( "{{prefix 'abcdefghij' 5}}" , "abcde" ) ]
158+ // Test math helpers
159+ #[ case( "{{add 5 3}}" , "8" ) ]
160+ #[ case( "{{multiply 7 6}}" , "42" ) ]
161+ // Test complex template with multiple helpers
162+ #[ case( "hash_{{hash_int 'branch' 3}}_{{prefix 'commit' 2}}" , "hash_380_co" ) ]
163+ fn test_template_render ( #[ case] input : & str , #[ case] expected : & str ) {
164+ let result = Template :: render ( input) ;
165+ assert_eq ! ( result, expected) ;
166+ }
167+
168+ #[ test]
169+ fn test_template_render_error_handling ( ) {
170+ // Test with invalid helper that should produce error
171+ let result = Template :: render ( "{{unknown_helper 'test'}}" ) ;
172+
173+ // Should not panic and should return error message
174+ assert ! ( result. starts_with( "template_error:" ) ) ;
175+ assert ! ( result. contains( "Template render error" ) ) ;
176+ }
177+
178+ #[ test]
179+ fn test_template_render_empty_string ( ) {
180+ let result = Template :: render ( "" ) ;
181+ assert_eq ! ( result, "" ) ;
182+ }
183+
184+ #[ test]
185+ fn test_template_render_with_context_variables ( ) {
186+ // When using Template::render(), context variables should not be available
187+ // and should be treated as empty/missing
188+ let result = Template :: render ( "{{missing_var}}" ) ;
189+ assert_eq ! ( result, "" ) ; // Missing variables become empty strings
190+ }
191+
192+ #[ rstest]
193+ #[ case( "{{hash_int 'feature-1' 5}}" ) ]
194+ #[ case( "{{sanitize 'Test-Branch-Name'}}" ) ]
195+ #[ case( "{{add 10 20}}" ) ]
196+ fn test_template_render_consistency ( #[ case] template_str : & str ) {
197+ // Template::render() should give same result as the verbose chain
198+ let render_result = Template :: render ( template_str) ;
199+
200+ let verbose_result = Template :: from ( template_str. to_string ( ) )
201+ . resolve ( None )
202+ . unwrap_or_else ( |e| format ! ( "template_error: {}" , e) ) ;
203+
204+ assert_eq ! ( render_result, verbose_result) ;
205+ }
134206}
0 commit comments