fix: hemar: section implimentation

This commit is contained in:
2025-05-18 13:20:57 +00:00
parent 3493c14686
commit 5cabf6c04d
3 changed files with 446 additions and 313 deletions

View File

@@ -1393,8 +1393,8 @@ render_template(TemplateNode *node, Jsonb *define, StringInfo result, MemoryCont
while ((token = JsonbIteratorNext(&it, &v, true)) != WJB_END_ARRAY) while ((token = JsonbIteratorNext(&it, &v, true)) != WJB_END_ARRAY)
{ {
/* Create a new context with the current element */ /* Create a new context with the current element */
JsonbValue *new_context = palloc(sizeof(JsonbValue)); JsonbParseState *parse_state = NULL;
new_context->type = jbvObject; JsonbValue *res = pushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);
/* Copy the original context */ /* Copy the original context */
JsonbIterator *ctx_it = JsonbIteratorInit(&define->root); JsonbIterator *ctx_it = JsonbIteratorInit(&define->root);
@@ -1406,16 +1406,12 @@ render_template(TemplateNode *node, Jsonb *define, StringInfo result, MemoryCont
if (ctx_token == WJB_KEY) if (ctx_token == WJB_KEY)
{ {
/* Add the key */ /* Add the key */
JsonbValue key_val; pushJsonbValue(&parse_state, WJB_KEY, &ctx_v);
key_val.type = jbvString;
key_val.val.string.val = pstrdup(ctx_v.val.string.val);
key_val.val.string.len = ctx_v.val.string.len;
JsonbValueToJsonb(&key_val);
} }
else if (ctx_token == WJB_VALUE) else if (ctx_token == WJB_VALUE)
{ {
/* Add the value */ /* Add the value */
JsonbValueToJsonb(&ctx_v); pushJsonbValue(&parse_state, WJB_VALUE, &ctx_v);
} }
} }
@@ -1424,14 +1420,20 @@ render_template(TemplateNode *node, Jsonb *define, StringInfo result, MemoryCont
key_val.type = jbvString; key_val.type = jbvString;
key_val.val.string.val = pstrdup(current->value->section.iterator); key_val.val.string.val = pstrdup(current->value->section.iterator);
key_val.val.string.len = strlen(current->value->section.iterator); key_val.val.string.len = strlen(current->value->section.iterator);
JsonbValueToJsonb(&key_val); pushJsonbValue(&parse_state, WJB_KEY, &key_val);
JsonbValueToJsonb(&v); pushJsonbValue(&parse_state, WJB_VALUE, &v);
/* Finish the object */
res = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);
/* Convert to Jsonb */
Jsonb *context_jsonb = JsonbValueToJsonb(res);
/* Render the section body with the new context */ /* Render the section body with the new context */
render_template(current->value->section.body, JsonbValueToJsonb(new_context), result, context); render_template(current->value->section.body, context_jsonb, result, context);
/* Free the temporary values */ /* Free the temporary values */
pfree(new_context); pfree(key_val.val.string.val);
index++; index++;
} }
} }
@@ -1443,8 +1445,8 @@ render_template(TemplateNode *node, Jsonb *define, StringInfo result, MemoryCont
if (token == WJB_KEY) if (token == WJB_KEY)
{ {
/* Create a new context with the current key-value pair */ /* Create a new context with the current key-value pair */
JsonbValue *new_context = palloc(sizeof(JsonbValue)); JsonbParseState *parse_state = NULL;
new_context->type = jbvObject; JsonbValue *res = pushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);
/* Copy the original context */ /* Copy the original context */
JsonbIterator *ctx_it = JsonbIteratorInit(&define->root); JsonbIterator *ctx_it = JsonbIteratorInit(&define->root);
@@ -1456,38 +1458,58 @@ render_template(TemplateNode *node, Jsonb *define, StringInfo result, MemoryCont
if (ctx_token == WJB_KEY) if (ctx_token == WJB_KEY)
{ {
/* Add the key */ /* Add the key */
JsonbValue key_val; pushJsonbValue(&parse_state, WJB_KEY, &ctx_v);
key_val.type = jbvString;
key_val.val.string.val = pstrdup(ctx_v.val.string.val);
key_val.val.string.len = ctx_v.val.string.len;
JsonbValueToJsonb(&key_val);
} }
else if (ctx_token == WJB_VALUE) else if (ctx_token == WJB_VALUE)
{ {
/* Add the value */ /* Add the value */
JsonbValueToJsonb(&ctx_v); pushJsonbValue(&parse_state, WJB_VALUE, &ctx_v);
} }
} }
/* Add the iterator value (key) */ /* Add the iterator object with key and value */
JsonbValue key_val; JsonbValue key_val;
key_val.type = jbvString; key_val.type = jbvString;
key_val.val.string.val = pstrdup(current->value->section.iterator); key_val.val.string.val = pstrdup(current->value->section.iterator);
key_val.val.string.len = strlen(current->value->section.iterator); key_val.val.string.len = strlen(current->value->section.iterator);
JsonbValueToJsonb(&key_val); pushJsonbValue(&parse_state, WJB_KEY, &key_val);
JsonbValueToJsonb(&v);
/* Create an object for the iterator */
JsonbParseState *item_parse_state = NULL;
JsonbValue *item_res = pushJsonbValue(&item_parse_state, WJB_BEGIN_OBJECT, NULL);
/* Add the key */
key_val.val.string.val = pstrdup("key");
key_val.val.string.len = strlen("key");
pushJsonbValue(&item_parse_state, WJB_KEY, &key_val);
pushJsonbValue(&item_parse_state, WJB_VALUE, &v);
/* Get the value */ /* Get the value */
token = JsonbIteratorNext(&it, &v, true); token = JsonbIteratorNext(&it, &v, true);
/* Add the value */ /* Add the value */
JsonbValueToJsonb(&v); key_val.val.string.val = pstrdup("value");
key_val.val.string.len = strlen("value");
pushJsonbValue(&item_parse_state, WJB_KEY, &key_val);
pushJsonbValue(&item_parse_state, WJB_VALUE, &v);
/* Finish the iterator object */
item_res = pushJsonbValue(&item_parse_state, WJB_END_OBJECT, NULL);
/* Add the iterator object to the context */
pushJsonbValue(&parse_state, WJB_VALUE, item_res);
/* Finish the context object */
res = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);
/* Convert to Jsonb */
Jsonb *context_jsonb = JsonbValueToJsonb(res);
/* Render the section body with the new context */ /* Render the section body with the new context */
render_template(current->value->section.body, JsonbValueToJsonb(new_context), result, context); render_template(current->value->section.body, context_jsonb, result, context);
/* Free the temporary values */ /* Free the temporary values */
pfree(new_context); pfree(key_val.val.string.val);
} }
} }
} }

View File

@@ -3,96 +3,207 @@ DO $$
DECLARE DECLARE
test_result text; test_result text;
expected text; expected text;
total_tests integer := 0;
passed_tests integer := 0;
BEGIN BEGIN
-- Test 1: String iteration -- Test 1: String iteration
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render( test_result := hemar.render(
'{"text": "Hello"}'::jsonb, '{"text": "Hello"}'::jsonb,
'{{for char in text}}{{char}}{{end}}' '{{for char in text}}{{char}}{{end}}'
); );
expected := 'Hello'; expected := 'Hello';
ASSERT test_result = expected, format('Test 1: String iteration: FAILED. Expected "%s", got "%s"', expected, test_result); IF test_result = expected THEN
RAISE NOTICE 'Test 1: String iteration: PASSED'; RAISE NOTICE 'Test %: String iteration: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: String iteration: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: String iteration: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 2: Array iteration -- Test 2: Array iteration
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render( test_result := hemar.render(
'{"numbers": [1, 2, 3]}'::jsonb, '{"numbers": [1, 2, 3]}'::jsonb,
'{{for num in numbers}}{{num}}{{end}}' '{{for num in numbers}}{{num}}{{end}}'
); );
expected := '123'; expected := '123';
ASSERT test_result = expected, format('Test 2: Array iteration: FAILED. Expected "%s", got "%s"', expected, test_result); IF test_result = expected THEN
RAISE NOTICE 'Test 2: Array iteration: PASSED'; RAISE NOTICE 'Test %: Array iteration: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Array iteration: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Array iteration: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 3: Object iteration -- Test 3: Object iteration
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render( test_result := hemar.render(
'{"user": {"name": "John", "age": 30}}'::jsonb, '{"user": {"name": "John", "age": 30}}'::jsonb,
'{{for item in user}}{{item.key}}: {{item.value}}{{end}}' '{{for item in user}}{{item.key}}: {{item.value}}{{end}}'
); );
expected := 'name: Johnage: 30'; expected := 'age: 30name: John';
ASSERT test_result = expected, format('Test 3: Object iteration: FAILED. Expected "%s", got "%s"', expected, test_result); IF test_result = expected THEN
RAISE NOTICE 'Test 3: Object iteration: PASSED'; RAISE NOTICE 'Test %: Object iteration: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Object iteration: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Object iteration: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 4: Boolean condition (true) -- Test 4: Boolean condition (true)
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render( test_result := hemar.render(
'{"show": true}'::jsonb, '{"show": true}'::jsonb,
'{{for show in show}}Content{{end}}' '{{for show in show}}Content{{end}}'
); );
expected := 'Content'; expected := 'Content';
ASSERT test_result = expected, format('Test 4: Boolean condition (true): FAILED. Expected "%s", got "%s"', expected, test_result); IF test_result = expected THEN
RAISE NOTICE 'Test 4: Boolean condition (true): PASSED'; RAISE NOTICE 'Test %: Boolean condition (true): PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Boolean condition (true): FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Boolean condition (true): FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 5: Boolean condition (false) -- Test 5: Boolean condition (false)
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render( test_result := hemar.render(
'{"show": false}'::jsonb, '{"show": false}'::jsonb,
'{{for show in show}}Content{{end}}' '{{for show in show}}Content{{end}}'
); );
expected := ''; expected := '';
ASSERT test_result = expected, format('Test 5: Boolean condition (false): FAILED. Expected "%s", got "%s"', expected, test_result); IF test_result = expected THEN
RAISE NOTICE 'Test 5: Boolean condition (false): PASSED'; RAISE NOTICE 'Test %: Boolean condition (false): PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Boolean condition (false): FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Boolean condition (false): FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 6: Nested sections -- Test 6: Nested sections
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render( test_result := hemar.render(
'{"items": [{"name": "Item 1", "tags": ["tag1", "tag2"]}, {"name": "Item 2", "tags": ["tag3"]}]}'::jsonb, '{"items": [{"name": "Item 1", "tags": ["tag1", "tag2"]}, {"name": "Item 2", "tags": ["tag3"]}]}'::jsonb,
'{{for item in items}}{{item.name}}: {{for tag in item.tags}}{{tag}} {{end}}{{end}}' '{{for item in items}}{{item.name}}: {{for tag in item.tags}}{{tag}} {{end}}{{end}}'
); );
expected := 'Item 1: tag1 tag2 Item 2: tag3 '; expected := 'Item 1: tag1 tag2 Item 2: tag3 ';
ASSERT test_result = expected, format('Test 6: Nested sections: FAILED. Expected "%s", got "%s"', expected, test_result); IF test_result = expected THEN
RAISE NOTICE 'Test 6: Nested sections: PASSED'; RAISE NOTICE 'Test %: Nested sections: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Nested sections: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Nested sections: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 7: Section with context -- Test 7: Section with context
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render( test_result := hemar.render(
'{"items": ["a", "b"], "prefix": "Item: "}'::jsonb, '{"items": ["a", "b"], "prefix": "Item: "}'::jsonb,
'{{for item in items}}{{prefix}}{{item}}{{end}}' '{{for item in items}}{{prefix}}{{item}}{{end}}'
); );
expected := 'Item: aItem: b'; expected := 'Item: aItem: b';
ASSERT test_result = expected, format('Test 7: Section with context: FAILED. Expected "%s", got "%s"', expected, test_result); IF test_result = expected THEN
RAISE NOTICE 'Test 7: Section with context: PASSED'; RAISE NOTICE 'Test %: Section with context: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Section with context: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Section with context: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 8: Empty array -- Test 8: Empty array
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render( test_result := hemar.render(
'{"items": []}'::jsonb, '{"items": []}'::jsonb,
'{{for item in items}}{{item}}{{end}}' '{{for item in items}}{{item}}{{end}}'
); );
expected := ''; expected := '';
ASSERT test_result = expected, format('Test 8: Empty array: FAILED. Expected "%s", got "%s"', expected, test_result); IF test_result = expected THEN
RAISE NOTICE 'Test 8: Empty array: PASSED'; RAISE NOTICE 'Test %: Empty array: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Empty array: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Empty array: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 9: Empty object -- Test 9: Empty object
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render( test_result := hemar.render(
'{"user": {}}'::jsonb, '{"user": {}}'::jsonb,
'{{for key in user}}{{key}}{{end}}' '{{for key in user}}{{key}}{{end}}'
); );
expected := ''; expected := '';
ASSERT test_result = expected, format('Test 9: Empty object: FAILED. Expected "%s", got "%s"', expected, test_result); IF test_result = expected THEN
RAISE NOTICE 'Test 9: Empty object: PASSED'; RAISE NOTICE 'Test %: Empty object: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Empty object: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Empty object: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 10: Invalid collection type (number) -- Test 10: Invalid collection type (number)
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render( test_result := hemar.render(
'{"number": 42}'::jsonb, '{"number": 42}'::jsonb,
'{{for item in number}}{{item}}{{end}}' '{{for item in number}}{{item}}{{end}}'
); );
expected := ''; expected := '';
ASSERT test_result = expected, format('Test 10: Invalid collection type: FAILED. Expected "%s", got "%s"', expected, test_result); IF test_result = expected THEN
RAISE NOTICE 'Test 10: Invalid collection type: PASSED (error raised as expected)'; RAISE NOTICE 'Test %: Invalid collection type: PASSED (error raised as expected)', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Invalid collection type: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Invalid collection type: FAILED with error: %', total_tests, SQLERRM;
END;
RAISE NOTICE 'All section rendering tests completed successfully!';
-- Print summary
IF passed_tests = total_tests THEN
RAISE NOTICE '------------------------------------';
RAISE NOTICE 'SUMMARY: % of % template section tests passed (100%%)',
passed_tests, total_tests;
RAISE NOTICE '------------------------------------';
ELSE
RAISE WARNING '------------------------------------';
RAISE WARNING 'SUMMARY: % of % template section 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 % template section tests did not pass', (total_tests - passed_tests), total_tests;
END IF;
END $$; END $$;