From 56fa799d53f1eb90c471b30926b8736fabcee86d Mon Sep 17 00:00:00 2001 From: yukkop Date: Wed, 14 May 2025 14:09:48 +0000 Subject: [PATCH] fix: more parsing errors handling, section body fix --- flake.nix | 4 + package/c/hemar/hemar.c | 427 +++++++++++++++++++++++++--------------- package/c/hemar/hemar.h | 10 +- 3 files changed, 274 insertions(+), 167 deletions(-) diff --git a/flake.nix b/flake.nix index a4a08a0..3c13033 100644 --- a/flake.nix +++ b/flake.nix @@ -319,12 +319,16 @@ ]; initialScript = pkgs.writeText "init-sql-script" '' SET log_min_messages TO DEBUG1; + SET client_min_messages TO DEBUG1; ALTER DATABASE postgres SET log_min_messages TO DEBUG1; + ALTER DATABASE postgres SET client_min_messages TO DEBUG1; CREATE EXTENSION "hemar"; -- SELECT hemar.parse('{% zalupa %}'); SELECT hemar.render('{"a": "b"}'::JSONB, 'a {% a %}'); SELECT hemar.render('{"a": ["b", "c"]}'::JSONB, 'a {% for i in a do text %}'); + SELECT hemar.render('{"a": {"g": ["b", "c"]}}'::JSONB, 'a {% for i in a.g do {% i %} %}'); + SELECT hemar.render('{"a": {"g": ["b", "c"], "b": [{"c": "a"}, {"c": "b"}]}}'::JSONB, 'a {% for i in a.b do {% i.c %} %}'); ''; }; diff --git a/package/c/hemar/hemar.c b/package/c/hemar/hemar.c index fdf755b..cde7633 100755 --- a/package/c/hemar/hemar.c +++ b/package/c/hemar/hemar.c @@ -33,6 +33,7 @@ static void get_include_data(Datum include_data, char **template_out, Datum *con static void template_node_to_string(TemplateNode *node, StringInfo result, int indent); static void debug_jsonb_value(Datum jsonb_value, const char *label); static bool is_jsonb_container_valid(JsonbContainer *container); +static JsonbValue *get_jsonb_value_by_path(Jsonb *jb, const char *path, bool *found); /* Implementation of a simplified validity check for JsonbContainer */ static bool @@ -121,8 +122,9 @@ init_template_node(MemoryContext context, TemplateNodeType type) /* Error code to string conversion */ const char * -template_error_to_string(TemplateErrorCode code) +template_error_to_string(TemplateErrorCode code, TemplateConfig *config) { + char *message = ""; switch (code) { case TEMPLATE_ERROR_NONE: @@ -131,8 +133,32 @@ template_error_to_string(TemplateErrorCode code) return "Unknown tag"; case TEMPLATE_ERROR_NESTED_INTERPOLATION: return "Nested interpolation"; - case TEMPLATE_ERROR_NESTED_SECTION_ITERATOR: - return "Nested section iterator"; + case TEMPLATE_UNEXPECTED_OPEN_BRACES_AFFTER_SECTION_CONTROLE: + message = "Found `"; + strcat(message, config->Syntax.Braces.open); + strcat(message, "` in `"); + strcat(message, config->Syntax.Section.control); + strcat(message, "` in section block"); + return message; + case TEMPLATE_UNEXPECTED_OPEN_BRACES_AFFTER_SECTION_SOURCE: + message = "Found `"; + strcat(message, config->Syntax.Braces.open); + strcat(message, "` in `"); + strcat(message, config->Syntax.Section.source); + strcat(message, "` in section block"); + return message; + case TEMPLATE_ERROR_UNEXPECTED_INTERPOLATION_END: + return "Unexpected interpolation end"; + case TEMPLATE_ERROR_NO_SOURSE_IN_SECTION: + message = "Not found `"; + strcat(message, config->Syntax.Section.source); + strcat(message, "` keyword in section block"); + return message; + case TEMPLATE_ERROR_NO_BEGIN_IN_SECTION: + message = "Not found `"; + strcat(message, config->Syntax.Section.begin); + strcat(message, "` keyword in section block"); + return message; case TEMPLATE_ERROR_UNEXPECTED_SECTION_END: return "Unexpected section end"; case TEMPLATE_ERROR_NESTED_INCLUDE: @@ -298,6 +324,7 @@ template_parse_interpolation(MemoryContext context, const char **s_ptr, key_len = *s - key_start; node->value->interpolate.key = MemoryContextStrdup(context, pnstrdup(key_start, key_len)); + elog(DEBUG1, "Parsing: %s", node->value->interpolate.key); *s = skip_whitespace(*s); @@ -305,7 +332,7 @@ template_parse_interpolation(MemoryContext context, const char **s_ptr, if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) != 0) { if (error_code) - *error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END; + *error_code = TEMPLATE_ERROR_UNEXPECTED_INTERPOLATION_END; template_free_node(node); return NULL; } @@ -345,7 +372,7 @@ template_parse_section(MemoryContext context, const char **s_ptr, if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0) { if (error_code) - *error_code = TEMPLATE_ERROR_NESTED_SECTION_ITERATOR; + *error_code = TEMPLATE_UNEXPECTED_OPEN_BRACES_AFFTER_SECTION_CONTROLE; template_free_node(node); return NULL; } @@ -353,7 +380,7 @@ template_parse_section(MemoryContext context, const char **s_ptr, if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0) { if (error_code) - *error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END; + *error_code = TEMPLATE_ERROR_NO_SOURSE_IN_SECTION; template_free_node(node); return NULL; } @@ -371,7 +398,7 @@ template_parse_section(MemoryContext context, const char **s_ptr, if (strncmp(*s, config->Syntax.Section.source, strlen(config->Syntax.Section.source)) != 0) { if (error_code) - *error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END; + *error_code = TEMPLATE_ERROR_NO_SOURSE_IN_SECTION; template_free_node(node); return NULL; } @@ -389,7 +416,7 @@ template_parse_section(MemoryContext context, const char **s_ptr, if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0) { if (error_code) - *error_code = TEMPLATE_ERROR_NESTED_SECTION_ITERATOR; + *error_code = TEMPLATE_UNEXPECTED_OPEN_BRACES_AFFTER_SECTION_SOURCE; template_free_node(node); return NULL; } @@ -397,7 +424,7 @@ template_parse_section(MemoryContext context, const char **s_ptr, if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0) { if (error_code) - *error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END; + *error_code = TEMPLATE_ERROR_NO_BEGIN_IN_SECTION; template_free_node(node); return NULL; } @@ -411,10 +438,11 @@ template_parse_section(MemoryContext context, const char **s_ptr, /* Check for 'do' keyword */ *s = skip_whitespace(*s); + // TODO: why check begin second time, first in while if (strncmp(*s, config->Syntax.Section.begin, strlen(config->Syntax.Section.begin)) != 0) { if (error_code) - *error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END; + *error_code = TEMPLATE_UNEXPECTED_OPEN_BRACES_AFFTER_SECTION_SOURCE; template_free_node(node); return NULL; } @@ -435,10 +463,29 @@ template_parse_section(MemoryContext context, const char **s_ptr, /* Parse the body as a normal template */ const char *body_start = *s; const char *original_s = *s; + + int inner_braces_opened_count = 0; /* Find the end of the section */ - while (**s && strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) != 0) + while (**s) { + // s = {% a %} %} + elog(DEBUG1, "Step, braces opened: %d, s: %s", inner_braces_opened_count, *s); + if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0) { + elog(DEBUG1, "inner_braces_opened_count++"); + inner_braces_opened_count++; + } + if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0) { + if (inner_braces_opened_count > 0) { + elog(DEBUG1, "inner_braces_opened_count--"); + inner_braces_opened_count--; + } + else { + elog(DEBUG1, "exit"); + break; + } + } (*s)++; + } if (!**s) { @@ -1243,7 +1290,6 @@ get_jsonb_path_value(Datum jsonb_context, const char *path, bool *found) { Jsonb *jb = (Jsonb *) DatumGetPointer(jsonb_context); JsonbValue *jbv_result; - JsonbValue key; JsonbIterator *it; JsonbValue v; JsonbIteratorToken token; @@ -1266,89 +1312,73 @@ get_jsonb_path_value(Datum jsonb_context, const char *path, bool *found) PG_TRY(); { - /* Handle simple top-level key */ - if (strchr(path, '.') == NULL && strchr(path, '[') == NULL) + /* Use the new path traversal function */ + jbv_result = get_jsonb_value_by_path(jb, path, found); + + if (*found && jbv_result) { - key.type = jbvString; - key.val.string.val = (char *) path; - key.val.string.len = strlen(path); - - jbv_result = findJsonbValueFromContainer(&jb->root, JB_FOBJECT, &key); - - if (jbv_result) + if (jbv_result->type == jbvString) { - *found = true; - - if (jbv_result->type == jbvString) + result = pnstrdup(jbv_result->val.string.val, jbv_result->val.string.len); + elog(DEBUG1, "Found string value for key %s: %s", path, result); + } + else if (jbv_result->type == jbvNumeric) + { + Numeric num = jbv_result->val.numeric; + result = DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(num))); + elog(DEBUG1, "Found numeric value for key %s: %s", path, result); + } + else if (jbv_result->type == jbvBool) + { + result = pstrdup(jbv_result->val.boolean ? "true" : "false"); + elog(DEBUG1, "Found boolean value for key %s: %s", path, result); + } + else if (jbv_result->type == jbvNull) + { + result = pstrdup(""); + elog(DEBUG1, "Found null value for key %s", path); + } + else if (jbv_result->type == jbvBinary) + { + /* Check if it's an array first */ + if (is_jsonb_container_valid((JsonbContainer *)jbv_result->val.binary.data)) { - result = pnstrdup(jbv_result->val.string.val, jbv_result->val.string.len); - elog(DEBUG1, "Found string value for key %s: %s", path, result); - } - else if (jbv_result->type == jbvNumeric) - { - Numeric num = jbv_result->val.numeric; - result = DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(num))); - elog(DEBUG1, "Found numeric value for key %s: %s", path, result); - } - else if (jbv_result->type == jbvBool) - { - result = pstrdup(jbv_result->val.boolean ? "true" : "false"); - elog(DEBUG1, "Found boolean value for key %s: %s", path, result); - } - else if (jbv_result->type == jbvNull) - { - result = pstrdup(""); - elog(DEBUG1, "Found null value for key %s", path); - } - else if (jbv_result->type == jbvBinary) - { - /* Check if it's an array first */ - if (is_jsonb_container_valid((JsonbContainer *)&jbv_result->val.binary)) + it = JsonbIteratorInit((JsonbContainer *)jbv_result->val.binary.data); + token = JsonbIteratorNext(&it, &v, false); + + if (token == WJB_BEGIN_ARRAY) { - it = JsonbIteratorInit((JsonbContainer *)&jbv_result->val.binary); - token = JsonbIteratorNext(&it, &v, false); - - if (token == WJB_BEGIN_ARRAY) - { - /* For arrays, convert to "[Array]" placeholder */ - result = pstrdup("[Array]"); - elog(DEBUG1, "Found array value for key %s", path); - } - else if (token == WJB_BEGIN_OBJECT) - { - /* For objects, convert to "{Object}" placeholder */ - result = pstrdup("{Object}"); - elog(DEBUG1, "Found object value for key %s", path); - } - else - { - /* Convert binary type to string representation */ - StringInfoData buf; - - initStringInfo(&buf); - appendStringInfoString(&buf, "[Complex Value]"); - result = buf.data; - elog(DEBUG1, "Found complex value for key %s", path); - } + /* For arrays, convert to "[Array]" placeholder */ + result = pstrdup("[Array]"); + elog(DEBUG1, "Found array value for key %s", path); + } + else if (token == WJB_BEGIN_OBJECT) + { + /* For objects, convert to "{Object}" placeholder */ + result = pstrdup("{Object}"); + elog(DEBUG1, "Found object value for key %s", path); } else { - result = pstrdup("[Invalid Binary]"); - elog(DEBUG1, "Found invalid binary value for key %s", path); + /* Convert binary type to string representation */ + StringInfoData buf; + + initStringInfo(&buf); + appendStringInfoString(&buf, "[Complex Value]"); + result = buf.data; + elog(DEBUG1, "Found complex value for key %s", path); } } - } - else - { - elog(DEBUG1, "Key %s not found in object", path); + else + { + result = pstrdup("[Invalid Binary]"); + elog(DEBUG1, "Found invalid binary value for key %s", path); + } } } else { - /* Handle nested paths using a JSON path expression */ - elog(DEBUG1, "Complex path not fully supported: %s", path); - result = pstrdup("[Complex Path]"); - *found = true; + elog(DEBUG1, "Path %s not found in object", path); } } PG_CATCH(); @@ -1372,7 +1402,6 @@ get_jsonb_array(Datum jsonb_context, const char *path, bool *found) { Jsonb *jb = (Jsonb *) DatumGetPointer(jsonb_context); JsonbValue *jbv_result; - JsonbValue key; JsonbIterator *it; JsonbValue v; JsonbIteratorToken token; @@ -1393,109 +1422,98 @@ get_jsonb_array(Datum jsonb_context, const char *path, bool *found) return result; } - /* Handle simple top-level key */ - if (strchr(path, '.') == NULL) + elog(DEBUG1, "Looking for array at path: %s", path); + + /* Use PG_TRY/PG_CATCH to handle any errors during iteration */ + PG_TRY(); { - elog(DEBUG1, "Looking for array at path: %s", path); + /* Use the new path traversal function */ + bool path_found = false; + jbv_result = get_jsonb_value_by_path(jb, path, &path_found); - /* Use PG_TRY/PG_CATCH to handle any errors during iteration */ - PG_TRY(); + if (path_found && jbv_result) { - key.type = jbvString; - key.val.string.val = (char *) path; - key.val.string.len = strlen(path); + elog(DEBUG1, "Found value for key %s (type=%d)", path, jbv_result->type); - jbv_result = findJsonbValueFromContainer(&jb->root, JB_FOBJECT, &key); - - if (jbv_result) + if (jbv_result->type == jbvBinary) { - elog(DEBUG1, "Found value for key %s (type=%d) value=%s", path, jbv_result->type, jbv_result->val.string.val); + /* Get more details about the binary data */ + elog(DEBUG1, "Binary value found, trying to examine structure"); - if (jbv_result->type == jbvBinary) + /* Validate the binary container before iterating */ + if (!is_jsonb_container_valid(jbv_result->val.binary.data)) { - /* Get more details about the binary data */ - elog(DEBUG1, "Binary value found, trying to examine structure"); + elog(WARNING, "Invalid binary JSONB container for key: %s", path); + return result; + } + + elog(DEBUG1, "Trying to initialize the iterator..."); + /* Try to initialize the iterator */ + PG_TRY(); + { + /* Log raw pointer for debugging */ + elog(DEBUG1, "Binary container address: %p", jbv_result->val.binary.data); - /* Validate the binary container before iterating */ - if (!is_jsonb_container_valid(jbv_result->val.binary.data)) + /* Try to get the first 4 bytes of the binary data */ + uint32 header = *(uint32 *)jbv_result->val.binary.data; + elog(DEBUG1, "Binary container header: %u", header); + + /* Initialize the iterator with careful error handling */ + elog(DEBUG1, "Initializing iterator for binary container"); + it = JsonbIteratorInit(jbv_result->val.binary.data); + elog(DEBUG1, "Iterator initialized successfully"); + + elog(DEBUG1, "Getting first token"); + token = JsonbIteratorNext(&it, &v, false); + elog(DEBUG1, "First token retrieved: %d", token); + + if (token == WJB_BEGIN_ARRAY) { - elog(WARNING, "Invalid binary JSONB container for key: %s", path); + /* It's a valid array */ + *found = true; + result = PointerGetDatum(JsonbValueToJsonb(jbv_result)); + elog(DEBUG1, "Found array at path %s (result=%p)", path, DatumGetPointer(result)); + debug_jsonb_value(result, "Array"); return result; } - - elog(DEBUG1, "Trying to initialize the iterator..."); - /* Try to initialize the iterator */ - PG_TRY(); + else { - /* Log raw pointer for debugging */ - elog(DEBUG1, "Binary container address: %p", &jbv_result->val.binary); - - /* Try to get the first 4 bytes of the binary data */ - uint32 header = *(uint32 *)&jbv_result->val.binary; - elog(DEBUG1, "Binary container header: %u", header); - - /* Initialize the iterator with careful error handling */ - elog(DEBUG1, "Initializing iterator for binary container"); - it = JsonbIteratorInit(jbv_result->val.binary.data); - elog(DEBUG1, "Iterator initialized successfully"); - - elog(DEBUG1, "Getting first token"); - token = JsonbIteratorNext(&it, &v, false); - elog(DEBUG1, "First token retrieved: %d", token); - - if (token == WJB_BEGIN_ARRAY) - { - /* It's a valid array */ - *found = true; - result = PointerGetDatum(JsonbValueToJsonb(jbv_result)); - elog(DEBUG1, "Found array at path %s (result=%p)", path, DatumGetPointer(result)); - debug_jsonb_value(result, "Array"); - return result; - } - else - { - elog(DEBUG1, "Path %s exists but is not an array (token type: %d)", path, token); - } + elog(DEBUG1, "Path %s exists but is not an array (token type: %d)", path, token); } - PG_CATCH(); - { - elog(WARNING, "Error initializing JSON iterator for path %s", path); - /* Get more details about the error */ - ErrorData *edata = CopyErrorData(); - elog(WARNING, "Error message: %s", edata->message); - elog(WARNING, "Error detail: %s", edata->detail ? edata->detail : "none"); - elog(WARNING, "Error hint: %s", edata->hint ? edata->hint : "none"); - elog(WARNING, "Error context: %s", edata->context ? edata->context : "none"); - FreeErrorData(edata); - FlushErrorState(); - } - PG_END_TRY(); } - else + PG_CATCH(); { - elog(DEBUG1, "Path %s exists but is not binary JSONB (type: %d)", path, jbv_result->type); + elog(WARNING, "Error initializing JSON iterator for path %s", path); + /* Get more details about the error */ + ErrorData *edata = CopyErrorData(); + elog(WARNING, "Error message: %s", edata->message); + elog(WARNING, "Error detail: %s", edata->detail ? edata->detail : "none"); + elog(WARNING, "Error hint: %s", edata->hint ? edata->hint : "none"); + elog(WARNING, "Error context: %s", edata->context ? edata->context : "none"); + FreeErrorData(edata); + FlushErrorState(); } + PG_END_TRY(); } else { - elog(DEBUG1, "Path %s not found in JSONB", path); + elog(DEBUG1, "Path %s exists but is not binary JSONB (type: %d)", path, jbv_result->type); } } - PG_CATCH(); + else { - elog(WARNING, "Exception while processing array at path %s", path); - ErrorData *edata = CopyErrorData(); - elog(WARNING, "Error message: %s", edata->message); - FreeErrorData(edata); - FlushErrorState(); + elog(DEBUG1, "Path %s not found in JSONB", path); } - PG_END_TRY(); } - else { - elog(DEBUG1, "Complex path not fully supported: %s", path); - result = pstrdup("[Complex Path]"); - *found = true; + PG_CATCH(); + { + elog(WARNING, "Exception while processing array at path %s", path); + ErrorData *edata = CopyErrorData(); + elog(WARNING, "Error message: %s", edata->message); + FreeErrorData(edata); + FlushErrorState(); } + PG_END_TRY(); return result; } @@ -2327,7 +2345,7 @@ pg_template_parse(PG_FUNCTION_ARGS) { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("Template parsing error: %s", template_error_to_string(error_code)))); + errmsg("Template parsing error: %s", template_error_to_string(error_code, &config)))); } /* Convert the parsed template to a string representation for debugging */ @@ -2408,7 +2426,7 @@ pg_render(PG_FUNCTION_ARGS) { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("Template parsing error: %s", template_error_to_string(error_code)))); + errmsg("Template parsing error: %s", template_error_to_string(error_code, &config)))); } elog(DEBUG1, "Template parsed successfully, starting render"); @@ -2586,4 +2604,85 @@ direct_array_check(Datum jsonb_context, const char *path, bool *found) return result; } - \ No newline at end of file + +/* Function to get JsonbValue by dot-separated path */ +static JsonbValue * +get_jsonb_value_by_path(Jsonb *jb, const char *path, bool *found) +{ + JsonbValue *result = NULL; + char *path_copy, *token, *saveptr; + JsonbValue key; + JsonbContainer *container; + + *found = false; + + if (!jb || !path || !is_jsonb_container_valid(&jb->root)) + { + elog(DEBUG1, "Invalid JSONB or path in get_jsonb_value_by_path"); + return NULL; + } + + /* Make a copy of the path to tokenize */ + path_copy = pstrdup(path); + container = &jb->root; + + /* Use strtok_r to split the path by dots */ + token = strtok_r(path_copy, ".", &saveptr); + + while (token != NULL) + { + /* Check if we're still working with an object (?) */ + if (!(container->header & JB_FOBJECT)) + { + elog(DEBUG1, "Path segment '%s' cannot be applied to non-object", token); + pfree(path_copy); + return NULL; + } + + /* Set up the key to search for */ + key.type = jbvString; + key.val.string.val = token; + key.val.string.len = strlen(token); + + /* Find the value for this key */ + result = findJsonbValueFromContainer(container, JB_FOBJECT, &key); + + if (!result) + { + elog(DEBUG1, "Key '%s' not found in object", token); + pfree(path_copy); + return NULL; + } + + /* If there are more path segments, we need to continue with the next container */ + token = strtok_r(NULL, ".", &saveptr); + + if (token != NULL) + { + /* We need to go deeper, so the current result must be a container */ + if (result->type != jbvBinary) + { + elog(DEBUG1, "Path segment '%s' points to a non-container value", token); + pfree(path_copy); + return NULL; + } + + /* Move to the next container */ + container = (JsonbContainer *)result->val.binary.data; + + /* Validate the container */ + if (!is_jsonb_container_valid(container)) + { + elog(WARNING, "Invalid JSONB container during path traversal"); + pfree(path_copy); + return NULL; + } + } + } + + /* If we got here, we found the value */ + *found = true; + pfree(path_copy); + return result; +} + diff --git a/package/c/hemar/hemar.h b/package/c/hemar/hemar.h index 61337e2..0b9fe23 100755 --- a/package/c/hemar/hemar.h +++ b/package/c/hemar/hemar.h @@ -15,9 +15,13 @@ typedef enum { TEMPLATE_ERROR_NONE = 0, TEMPLATE_ERROR_UNKNOWN_TAG, + TEMPLATE_UNEXPECTED_OPEN_BRACES_AFFTER_SECTION_CONTROLE, + TEMPLATE_UNEXPECTED_OPEN_BRACES_AFFTER_SECTION_SOURCE, + TEMPLATE_ERROR_UNEXPECTED_INTERPOLATION_END, + TEMPLATE_ERROR_NO_SOURSE_IN_SECTION, TEMPLATE_ERROR_NESTED_INTERPOLATION, - TEMPLATE_ERROR_NESTED_SECTION_ITERATOR, TEMPLATE_ERROR_UNEXPECTED_SECTION_END, + TEMPLATE_ERROR_NO_BEGIN_IN_SECTION, TEMPLATE_ERROR_NESTED_INCLUDE, TEMPLATE_ERROR_NESTED_EXECUTE, TEMPLATE_ERROR_INVALID_CONFIG, @@ -106,6 +110,6 @@ TemplateConfig template_default_config(MemoryContext context); bool template_validate_config(const TemplateConfig *config, TemplateErrorCode *error_code); TemplateNode *template_parse(MemoryContext context, const char **s, const TemplateConfig *config, bool inner_parse, TemplateErrorCode *error_code); void template_free_node(TemplateNode *node); -const char *template_error_to_string(TemplateErrorCode code); +const char *template_error_to_string(TemplateErrorCode code, TemplateConfig *config); -#endif /* HEMAR_TEMPLATE_H */ \ No newline at end of file +#endif /* HEMAR_TEMPLATE_H */