From ef314495ba49c180f33359ac202474cc1a4eb8d0 Mon Sep 17 00:00:00 2001 From: yukkop Date: Sun, 18 May 2025 20:18:54 +0000 Subject: [PATCH] fix: `hemar`: include object does not exists --- package/c/hemar/hemar.c | 8 +- package/c/hemar/test/test_render_all.sql | 448 ++++++++++--------- package/c/hemar/test/test_render_include.sql | 38 ++ 3 files changed, 274 insertions(+), 220 deletions(-) diff --git a/package/c/hemar/hemar.c b/package/c/hemar/hemar.c index 7183394..104b8ea 100755 --- a/package/c/hemar/hemar.c +++ b/package/c/hemar/hemar.c @@ -1547,9 +1547,13 @@ render_template(TemplateNode *node, Jsonb *define, StringInfo result, MemoryCont /* Get the include data from the context */ JsonbValue *include_data = jsonb_get_by_path_internal(define, include_path, context); - elog(DEBUG1, "Include data: %s", JsonbToCString(NULL, &JsonbValueToJsonb(include_data)->root, VARSIZE_ANY_EXHDR(JsonbValueToJsonb(include_data)))); + if (!include_data) + { + elog(WARNING, "Include data not found"); + break; + } - if (include_data != NULL && include_data->type == jbvBinary) + if (include_data->type == jbvBinary) { JsonbIterator *it = JsonbIteratorInit((JsonbContainer *)include_data->val.binary.data); JsonbIteratorToken token; diff --git a/package/c/hemar/test/test_render_all.sql b/package/c/hemar/test/test_render_all.sql index 8282ea6..d590999 100755 --- a/package/c/hemar/test/test_render_all.sql +++ b/package/c/hemar/test/test_render_all.sql @@ -1,219 +1,231 @@ --- Test all template tags together -DO $$ -DECLARE - total_tests INT := 0; - passed_tests INT := 0; - test_result TEXT; - expected TEXT; - passed BOOLEAN; -BEGIN - -- Test 1: Complex template with all tag types - total_tests := total_tests + 1; - test_result := hemar.render( - '{ - "page": { - "title": "My Page", - "sections": [ - { - "id": "section1", - "title": "Section 1", - "items": [ - { - "id": "item1", - "status": "active", - "content": "Item 1 Content", - "template": "item_template" - }, - { - "id": "item2", - "status": "inactive", - "content": "Item 2 Content", - "template": "item_template" - } - ] - } - ] - }, - "include": { - "meta_tags": { - "content": "" - }, - "header": { - "template": "Welcome to {{ page.title }}!", - "context": { - "page": { - "title": "My Page" - } - } - }, - "item_template": { - "template": "Status: {{ status }}, Content: {{ content }}" - }, - "footer": { - "content": "" - } - } - }'::jsonb, - $template$ - - - {{ page.title }} - {{ include meta_tags }} - - -
{{ include header }}
-
- {{ for section in page.sections }} -
-

{{ section.title }}

- {{ for item in section.items }} -
- {{ include item.template }} - {{ exec - DECLARE - v_status TEXT; - BEGIN - v_status := context->'item'->>'status'; - RETURN CASE - WHEN v_status = 'active' THEN ' (Active Item)' - ELSE ' (Inactive Item)' - END; - END; - }} -
- {{ end }} -
- {{ end }} -
- - -$template$ - ); - - expected := ' - - - My Page - - - -
Welcome to My Page!
-
-
-

Section 1

-
- Status: active, Content: Item 1 Content (Active Item) -
-
- Status: inactive, Content: Item 2 Content (Inactive Item) -
-
-
- - -'; - - passed := test_result = expected; - passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); - IF passed THEN - RAISE NOTICE 'Test %: Complex template with all tag types: PASSED', total_tests; - ELSE - RAISE WARNING 'Test %: Complex template with all tag types: FAILED. Expected "%", got "%"', - total_tests, expected, test_result; - END IF; - - -- Test 2: Template with nested includes and shared context - total_tests := total_tests + 1; - test_result := hemar.render( - '{ - "user": { - "name": "John", - "role": "admin" - }, - "include": { - "user_info": { - "template": "User: {{ user.name }} ({{ user.role }})" - }, - "permissions": { - "template": "{{ include user_info }} - Permissions: {{ for perm in user.permissions }}{{ perm }} {{ end }}", - "context": { - "user": { - "name": "John", - "role": "admin", - "permissions": ["read", "write", "delete"] - } - } - } - } - }'::jsonb, - $template${{ include permissions }}$template$ - ); - - expected := 'User: John (admin) - Permissions: read write delete '; - - passed := test_result = expected; - passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); - IF passed THEN - RAISE NOTICE 'Test %: Template with nested includes and shared context: PASSED', total_tests; - ELSE - RAISE WARNING 'Test %: Template with nested includes and shared context: FAILED. Expected "%", got "%"', - total_tests, expected, test_result; - END IF; - - -- Test 3: Template with execute tag using context from section - total_tests := total_tests + 1; - test_result := hemar.render( - '{ - "items": [ - {"id": 1, "value": 100}, - {"id": 2, "value": 200}, - {"id": 3, "value": 300} - ] - }'::jsonb, - $template$Items: -{{ for item in items }} - Item {{ item.id }}: {{ exec - DECLARE - v_value INT; - BEGIN - v_value := (context->>'value')::int; - RETURN v_value * 2; - END; - }} -{{ end }}$template$ - ); - - expected := 'Items: - Item 1: 200 - Item 2: 400 - Item 3: 600 -'; - - passed := test_result = expected; - passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); - IF passed THEN - RAISE NOTICE 'Test %: Template with execute tag using context from section: PASSED', total_tests; - ELSE - RAISE WARNING 'Test %: Template with execute tag using context from section: FAILED. Expected "%", got "%"', - total_tests, expected, test_result; - END IF; - - -- Print summary - IF passed_tests = total_tests THEN - RAISE NOTICE '------------------------------------'; - RAISE NOTICE 'SUMMARY: % of % combined template tests passed (100%%)', - passed_tests, total_tests; - RAISE NOTICE '------------------------------------'; - ELSE - RAISE WARNING '------------------------------------'; - RAISE WARNING 'SUMMARY: % of % combined template tests passed (%)', - passed_tests, - total_tests, - round((passed_tests::numeric / total_tests::numeric) * 100, 2) || '%'; - RAISE WARNING '------------------------------------'; - END IF; - - IF passed_tests != total_tests THEN - RAISE EXCEPTION 'Tests failed: % of % combined template tests did not pass', (total_tests - passed_tests), total_tests; - END IF; +-- Test all template tags together +DO $$ +DECLARE + total_tests INT := 0; + passed_tests INT := 0; + test_result TEXT; + expected TEXT; + passed BOOLEAN; +BEGIN + -- Test 1: Complex template with all tag types + total_tests := total_tests + 1; + BEGIN + test_result := hemar.render( + '{ + "page": { + "title": "My Page", + "sections": [ + { + "id": "section1", + "title": "Section 1", + "items": [ + { + "id": "item1", + "status": "active", + "content": "Item 1 Content", + "template": "item_template" + }, + { + "id": "item2", + "status": "inactive", + "content": "Item 2 Content", + "template": "item_template" + } + ] + } + ] + }, + "include": { + "meta_tags": { + "content": "" + }, + "header": { + "template": "Welcome to {{ page.title }}!", + "context": { + "page": { + "title": "My Page" + } + } + }, + "item_template": { + "template": "Status: {{ status }}, Content: {{ content }}" + }, + "footer": { + "content": "" + } + } + }'::jsonb, + $template$ + + + {{ page.title }} + {{ include meta_tags }} + + +
{{ include header }}
+
+ {{ for section in page.sections }} +
+

{{ section.title }}

+ {{ for item in section.items }} +
+ {{ include item.template }} + {{ exec + DECLARE + v_status TEXT; + BEGIN + v_status := context->'item'->>'status'; + RETURN CASE + WHEN v_status = 'active' THEN ' (Active Item)' + ELSE ' (Inactive Item)' + END; + END; + }} +
+ {{ end }} +
+ {{ end }} +
+ + +$template$ + ); + + expected := ' + + + My Page + + + +
Welcome to My Page!
+
+
+

Section 1

+
+ Status: active, Content: Item 1 Content (Active Item) +
+
+ Status: inactive, Content: Item 2 Content (Inactive Item) +
+
+
+ + +'; + + passed := test_result = expected; + passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); + IF passed THEN + RAISE NOTICE 'Test %: Complex template with all tag types: PASSED', total_tests; + ELSE + RAISE WARNING 'Test %: Complex template with all tag types: FAILED. Expected "%", got "%"', + total_tests, expected, test_result; + END IF; + EXCEPTION WHEN OTHERS THEN + RAISE WARNING 'Test % failed: Error: %', total_tests, SQLERRM; + END; + + -- Test 2: Template with nested includes and shared context + total_tests := total_tests + 1; + BEGIN + test_result := hemar.render( + '{ + "user": { + "name": "John", + "role": "admin" + }, + "include": { + "user_info": { + "template": "User: {{ user.name }} ({{ user.role }})" + }, + "permissions": { + "template": "{{ include user_info }} - Permissions: {{ for perm in user.permissions }}{{ perm }} {{ end }}", + "context": { + "user": { + "name": "John", + "role": "admin", + "permissions": ["read", "write", "delete"] + } + } + } + } + }'::jsonb, + $template${{ include permissions }}$template$ + ); + + expected := 'User: John (admin) - Permissions: read write delete '; + + passed := test_result = expected; + passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); + IF passed THEN + RAISE NOTICE 'Test %: Template with nested includes and shared context: PASSED', total_tests; + ELSE + RAISE WARNING 'Test %: Template with nested includes and shared context: FAILED. Expected "%", got "%"', + total_tests, expected, test_result; + END IF; + EXCEPTION WHEN OTHERS THEN + RAISE WARNING 'Test % failed: Error: %', total_tests, SQLERRM; + END; + + -- Test 3: Template with execute tag using context from section + total_tests := total_tests + 1; + BEGIN + test_result := hemar.render( + '{ + "items": [ + {"id": 1, "value": 100}, + {"id": 2, "value": 200}, + {"id": 3, "value": 300} + ] + }'::jsonb, + $template$Items: +{{ for item in items }} + Item {{ item.id }}: {{ exec + DECLARE + v_value INT; + BEGIN + v_value := (context->>'value')::int; + RETURN v_value * 2; + END; + }} +{{ end }}$template$ + ); + + expected := 'Items: + Item 1: 200 + Item 2: 400 + Item 3: 600 +'; + + passed := test_result = expected; + passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); + IF passed THEN + RAISE NOTICE 'Test %: Template with execute tag using context from section: PASSED', total_tests; + ELSE + RAISE WARNING 'Test %: Template with execute tag using context from section: FAILED. Expected "%", got "%"', + total_tests, expected, test_result; + END IF; + EXCEPTION WHEN OTHERS THEN + RAISE WARNING 'Test % failed: Error: %', total_tests, SQLERRM; + END; + + -- Print summary + IF passed_tests = total_tests THEN + RAISE NOTICE '------------------------------------'; + RAISE NOTICE 'SUMMARY: % of % combined template tests passed (100%%)', + passed_tests, total_tests; + RAISE NOTICE '------------------------------------'; + ELSE + RAISE WARNING '------------------------------------'; + RAISE WARNING 'SUMMARY: % of % combined template tests passed (%)', + passed_tests, + total_tests, + round((passed_tests::numeric / total_tests::numeric) * 100, 2) || '%'; + RAISE WARNING '------------------------------------'; + END IF; + + IF passed_tests != total_tests THEN + RAISE EXCEPTION 'Tests failed: % of % combined template tests did not pass', (total_tests - passed_tests), total_tests; + END IF; END $$; \ No newline at end of file diff --git a/package/c/hemar/test/test_render_include.sql b/package/c/hemar/test/test_render_include.sql index 2a63bdd..c4a8d99 100755 --- a/package/c/hemar/test/test_render_include.sql +++ b/package/c/hemar/test/test_render_include.sql @@ -177,6 +177,44 @@ BEGIN RAISE WARNING 'Test % failed: Should have raised an error for invalid include data', total_tests; END; + -- Test 8: Error handling - unexisting include object + total_tests := total_tests + 1; + BEGIN + result := hemar.render( + '{}'::jsonb, + '{{ include invalid_template }}' + ); + + IF result = '' THEN + RAISE NOTICE 'Test % passed: Error handling for unexisting include object works correctly', total_tests; + passed_tests := passed_tests + 1; + ELSE + RAISE WARNING 'Test % failed: Expected "", got "%"', total_tests, result; + END IF; + EXCEPTION WHEN OTHERS THEN + RAISE WARNING 'Test % failed: Should have raised an error for unexisting include object', total_tests; + END; + + -- Test 9: Error handling - unexisting include data + total_tests := total_tests + 1; + BEGIN + result := hemar.render( + '{ + "include": { } + }'::jsonb, + '{{ include invalid_template }}' + ); + + IF result = '' THEN + RAISE NOTICE 'Test % passed: Error handling for unexisting include data works correctly', total_tests; + passed_tests := passed_tests + 1; + ELSE + RAISE WARNING 'Test % failed: Expected "", got "%"', total_tests, result; + END IF; + EXCEPTION WHEN OTHERS THEN + RAISE WARNING 'Test % failed: Should have raised an error for unexisting include data', total_tests; + END; + IF passed_tests = total_tests THEN RAISE NOTICE '------------------------------------'; RAISE NOTICE 'SUMMARY: % of % template include tests passed (100%%)',