commit ecffbd075a8a36de47a21fdb749e98cb0f5bc79b
parent 200aae1502376b78a8ddb98e07f230382070faac
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date: Sat, 17 Jan 2026 23:55:00 +0100
putting template parsers to util
Diffstat:
10 files changed, 497 insertions(+), 404 deletions(-)
diff --git a/src/backend/taler-merchant-httpd_get-templates-ID.c b/src/backend/taler-merchant-httpd_get-templates-ID.c
@@ -525,7 +525,7 @@ TMH_get_templates_ID (
{
MHD_RESULT ret;
- switch (TMH_template_type_from_contract (tp.template_contract))
+ switch (TALER_MERCHANT_template_type_from_contract (tp.template_contract))
{
case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
ret = handle_get_templates_inventory (connection,
diff --git a/src/backend/taler-merchant-httpd_helper.c b/src/backend/taler-merchant-httpd_helper.c
@@ -490,216 +490,6 @@ TMH_validate_unit_price_array (const struct TALER_Amount *prices,
}
-enum TALER_MERCHANT_TemplateType
-TMH_template_type_from_contract (const json_t *template_contract)
-{
- const json_t *type_val;
-
- if (NULL == template_contract)
- return TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
- type_val = json_object_get (template_contract,
- "template_type");
- if (! json_is_string (type_val))
- return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER;
- return TALER_MERCHANT_template_type_from_string (
- json_string_value (type_val));
-}
-
-
-const char *
-TMH_template_type_to_string (enum TALER_MERCHANT_TemplateType template_type)
-{
- switch (template_type)
- {
- case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
- return "fixed-order";
- case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
- return "inventory-cart";
- case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
- return "paivana";
- case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
- break;
- }
- return NULL;
-}
-
-
-static bool
-inventory_template_contract_valid (const json_t *template_contract)
-{
- bool selected_all = false;
- bool choose_one = false;
- bool request_tip = false;
- const json_t *selected_categories = NULL;
- const json_t *selected_products = NULL;
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_bool ("request_tip",
- &request_tip),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_bool ("selected_all",
- &selected_all),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_array_const ("selected_categories",
- &selected_categories),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_array_const ("selected_products",
- &selected_products),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_bool ("choose_one",
- &choose_one),
- NULL),
- GNUNET_JSON_spec_end ()
- };
- const char *ename;
- unsigned int eline;
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (template_contract,
- ispec,
- &ename,
- &eline))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Invalid inventory template_contract for field %s\n",
- ename);
- return false;
- }
- if (NULL != selected_categories)
- {
- const json_t *entry;
- size_t idx;
-
- json_array_foreach ((json_t *) selected_categories, idx, entry)
- {
- if (! json_is_integer (entry))
- {
- GNUNET_break_op (0);
- return false;
- }
- if (0 > json_integer_value (entry))
- {
- GNUNET_break_op (0);
- return false;
- }
- }
- }
- if (NULL != selected_products)
- {
- const json_t *entry;
- size_t idx;
-
- json_array_foreach ((json_t *) selected_products, idx, entry)
- {
- if (! json_is_string (entry))
- {
- GNUNET_break_op (0);
- return false;
- }
- }
- }
- return true;
-}
-
-
-static bool
-paivana_template_contract_valid (const json_t *template_contract)
-{
- const char *paivana_id;
- struct GNUNET_JSON_Specification pspec[] = {
- GNUNET_JSON_spec_string ("paivana_id",
- &paivana_id),
- GNUNET_JSON_spec_end ()
- };
- const char *ename;
- unsigned int eline;
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (template_contract,
- pspec,
- &ename,
- &eline))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Invalid paivana template_contract for field %s\n",
- ename);
- return false;
- }
- (void) paivana_id;
- /* TODO: PAIVANA validate paivana-specific fields beyond presence. */
- return true;
-}
-
-
-bool
-TMH_template_contract_valid (const json_t *template_contract)
-{
- const char *summary;
- const char *currency;
- struct TALER_Amount amount = { .value = 0};
- uint32_t minimum_age = 0;
- struct GNUNET_TIME_Relative pay_duration = { 0 };
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("summary",
- &summary),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("currency",
- ¤cy),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_amount_any ("amount",
- &amount),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_uint32 ("minimum_age",
- &minimum_age),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_relative_time ("pay_duration",
- &pay_duration),
- NULL),
- GNUNET_JSON_spec_end ()
- };
- const char *ename;
- unsigned int eline;
- enum TALER_MERCHANT_TemplateType template_type;
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (template_contract,
- spec,
- &ename,
- &eline))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Invalid template_contract for field %s\n",
- ename);
- return false;
- }
- template_type = TMH_template_type_from_contract (template_contract);
- switch (template_type)
- {
- case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
- /* no further fields */
- return true;
- case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
- return inventory_template_contract_valid (template_contract);
- case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
- return paivana_template_contract_valid (template_contract);
- case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
- break;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Invalid template_type\n");
- return false;
-}
-
-
bool
TMH_category_set_contains (const struct TMH_CategorySet *set,
uint64_t id)
diff --git a/src/backend/taler-merchant-httpd_helper.h b/src/backend/taler-merchant-httpd_helper.h
@@ -87,23 +87,6 @@ enum GNUNET_GenericReturnValue
TMH_validate_unit_price_array (const struct TALER_Amount *prices,
size_t prices_len);
-/**
- * Determine template type from a template contract.
- *
- * @param template_contract contract JSON
- * @return template type (defaults to fixed order)
- */
-enum TALER_MERCHANT_TemplateType
-TMH_template_type_from_contract (const json_t *template_contract);
-
-/**
- * Convert template type to its string representation.
- *
- * @param template_type template type to convert
- * @return string name or NULL for invalid types
- */
-const char *
-TMH_template_type_to_string (enum TALER_MERCHANT_TemplateType template_type);
/**
* Set of category IDs.
@@ -222,18 +205,6 @@ TMH_unit_defaults_for_instance (const struct TMH_MerchantInstance *mi,
/**
- * Check if @a template_contract is a valid template_contract object in the sense of Taler's API
- * definition.
- *
- * @param template_contract object to check
- * @return true if @a template_location is an object
- * representing a template_location.
- */
-bool
-TMH_template_contract_valid (const json_t *template_contract);
-
-
-/**
* Setup new wire method for the given @ payto_uri.
*
* @param payto_uri already validated payto URI
@@ -373,13 +344,13 @@ TMH_make_order_status_url (struct MHD_Connection *con,
* @param hr a `TALER_EXCHANGE_HttpResponse`
*/
#define TMH_pack_exchange_reply(hr) \
- GNUNET_JSON_pack_uint64 ("exchange_code", (hr)->ec), \
- GNUNET_JSON_pack_uint64 ("exchange_http_status", (hr)->http_status), \
- GNUNET_JSON_pack_uint64 ("exchange_ec", (hr)->ec), /* LEGACY */ \
- GNUNET_JSON_pack_uint64 ("exchange_hc", (hr)->http_status), /* LEGACY */ \
- GNUNET_JSON_pack_allow_null ( \
- GNUNET_JSON_pack_object_incref ("exchange_reply", (json_t *) (hr)-> \
- reply))
+ GNUNET_JSON_pack_uint64 ("exchange_code", (hr)->ec), \
+ GNUNET_JSON_pack_uint64 ("exchange_http_status", (hr)->http_status), \
+ GNUNET_JSON_pack_uint64 ("exchange_ec", (hr)->ec), /* LEGACY */ \
+ GNUNET_JSON_pack_uint64 ("exchange_hc", (hr)->http_status), /* LEGACY */ \
+ GNUNET_JSON_pack_allow_null ( \
+ GNUNET_JSON_pack_object_incref ("exchange_reply", (json_t *) (hr)-> \
+ reply))
/**
diff --git a/src/backend/taler-merchant-httpd_post-using-templates.c b/src/backend/taler-merchant-httpd_post-using-templates.c
@@ -28,6 +28,7 @@
#include "taler-merchant-httpd_private-post-orders.h"
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_exchanges.h"
+#include "taler_merchant_util.h"
#include <taler/taler_json_lib.h>
/**
@@ -239,74 +240,7 @@ struct UseContext
/**
* Information set in the #USE_PHASE_PARSE_TEMPLATE phase.
*/
- struct
- {
-
- /**
- * Summary from the template contract.
- */
- const char *tsummary;
-
- /**
- * Minimum age required by the template.
- */
- uint32_t min_age;
-
- /**
- * How long does the customer have to pay for the
- * order. 0 if not specified (use instance default).
- */
- struct GNUNET_TIME_Relative pay_duration;
-
- /**
- * Currency from the template contract.
- */
- const char *tcurrency;
-
- /**
- * Amount from the template contract.
- */
- struct TALER_Amount tamount;
-
- /**
- * True if @e tamount was not provided.
- */
- bool no_tamount;
-
- /**
- * Parsed fields for inventory templates.
- */
- struct
- {
-
- /**
- * Selected categories from the template contract.
- */
- const json_t *selected_categories;
-
- /**
- * Selected products from the template contract.
- */
- const json_t *selected_products;
-
- /**
- * Whether all products are selectable.
- */
- bool selected_all;
-
- /**
- * Template requires exactly one selection.
- */
- bool choose_one;
-
- /**
- * Template allows tips.
- */
- bool request_tip;
-
- } inventory;
-
- } parse_template;
+ struct TALER_MERCHANT_TemplateContract template_contract;
/**
* Information set in the #USE_PHASE_COMPUTE_PRICE phase.
@@ -653,7 +587,7 @@ handle_phase_lookup_template (
break;
}
if (uc->template_type !=
- TMH_template_type_from_contract (
+ TALER_MERCHANT_template_type_from_contract (
uc->lookup_template.etp.template_contract))
{
GNUNET_break_op (0);
@@ -677,61 +611,15 @@ handle_phase_lookup_template (
* @param[in,out] uc use context
*/
static void
-handle_phase_parse_template (struct UseContext *uc)
+handle_phase_template_contract (struct UseContext *uc)
{
- struct GNUNET_JSON_Specification tcspec[] = {
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("summary",
- &uc->parse_template.tsummary),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("currency",
- &uc->parse_template.tcurrency),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_amount_any ("amount",
- &uc->parse_template.tamount),
- &uc->parse_template.no_tamount),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_uint32 ("minimum_age",
- &uc->parse_template.min_age),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_relative_time ("pay_duration",
- &uc->parse_template.pay_duration),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_array_const (
- "selected_categories",
- &uc->parse_template.inventory.selected_categories),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_array_const (
- "selected_products",
- &uc->parse_template.inventory.selected_products),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_bool ("choose_one",
- &uc->parse_template.inventory.choose_one),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_bool ("request_tip",
- &uc->parse_template.inventory.request_tip),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_bool ("selected_all",
- &uc->parse_template.inventory.selected_all),
- NULL),
- GNUNET_JSON_spec_end ()
- };
const char *err_name;
- unsigned int err_line;
enum GNUNET_GenericReturnValue res;
- res = GNUNET_JSON_parse (uc->lookup_template.etp.template_contract,
- tcspec,
- &err_name,
- &err_line);
+ res = TALER_MERCHANT_template_contract_parse (
+ uc->lookup_template.etp.template_contract,
+ &uc->template_contract,
+ &err_name);
if (GNUNET_OK != res)
{
GNUNET_break (0);
@@ -902,7 +790,7 @@ category_allowed (const json_t *allowed_categories,
static enum GNUNET_GenericReturnValue
verify_using_templates_inventory (struct UseContext *uc)
{
- if (uc->parse_template.inventory.choose_one &&
+ if (uc->template_contract.inventory.choose_one &&
(1 != uc->parse_request.inventory.items_len))
{
GNUNET_break_op (0);
@@ -912,7 +800,7 @@ verify_using_templates_inventory (struct UseContext *uc)
"inventory_selection");
return GNUNET_SYSERR;
}
- if (uc->parse_template.inventory.selected_all)
+ if (uc->template_contract.inventory.selected_all)
return GNUNET_OK;
for (unsigned int i = 0;
i < uc->parse_request.inventory.items_len;
@@ -960,11 +848,11 @@ verify_using_templates_inventory (struct UseContext *uc)
struct InventoryTemplateItemContext *item =
&uc->parse_request.inventory.items[i];
- if (product_id_allowed (uc->parse_template.inventory.selected_products,
+ if (product_id_allowed (uc->template_contract.inventory.selected_products,
item->product_id))
continue;
if (category_allowed (
- uc->parse_template.inventory.selected_categories,
+ uc->template_contract.inventory.selected_categories,
item->num_categories,
item->categories))
continue;
@@ -993,7 +881,7 @@ verify_using_templates_fixed (
struct UseContext *uc)
{
if ( (! uc->parse_request.no_amount) &&
- (! uc->parse_template.no_tamount) )
+ (! uc->template_contract.no_amount) )
{
GNUNET_break_op (0);
use_reply_with_error (uc,
@@ -1003,7 +891,7 @@ verify_using_templates_fixed (
return GNUNET_SYSERR;
}
if (uc->parse_request.no_amount &&
- uc->parse_template.no_tamount)
+ uc->template_contract.no_amount)
{
GNUNET_break_op (0);
use_reply_with_error (uc,
@@ -1044,7 +932,7 @@ handle_phase_verify (
enum GNUNET_GenericReturnValue res = GNUNET_SYSERR;
if ( (NULL != uc->parse_request.summary) &&
- (NULL != uc->parse_template.tsummary) )
+ (NULL != uc->template_contract.summary) )
{
GNUNET_break_op (0);
use_reply_with_error (uc,
@@ -1054,7 +942,7 @@ handle_phase_verify (
return;
}
if ( (NULL == uc->parse_request.summary) &&
- (NULL == uc->parse_template.tsummary) )
+ (NULL == uc->template_contract.summary) )
{
GNUNET_break_op (0);
use_reply_with_error (uc,
@@ -1064,15 +952,15 @@ handle_phase_verify (
return;
}
if ( (! uc->parse_request.no_amount) &&
- (NULL != uc->parse_template.tcurrency) &&
- (0 != strcasecmp (uc->parse_template.tcurrency,
+ (NULL != uc->template_contract.currency) &&
+ (0 != strcasecmp (uc->template_contract.currency,
uc->parse_request.amount.currency)) )
{
GNUNET_break_op (0);
use_reply_with_error (uc,
MHD_HTTP_CONFLICT,
TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
- uc->parse_template.tcurrency);
+ uc->template_contract.currency);
return;
}
switch (uc->template_type)
@@ -1355,13 +1243,13 @@ handle_phase_compute_price (struct UseContext *uc)
= 1;
if (uc->parse_request.no_amount)
{
- GNUNET_assert (! uc->parse_template.no_tamount);
+ GNUNET_assert (! uc->template_contract.no_amount);
*uc->compute_price.totals
- = uc->parse_template.tamount;
+ = uc->template_contract.amount;
}
else
{
- GNUNET_assert (uc->parse_template.no_tamount);
+ GNUNET_assert (uc->template_contract.no_amount);
*uc->compute_price.totals
= uc->parse_request.amount;
}
@@ -1373,7 +1261,7 @@ handle_phase_compute_price (struct UseContext *uc)
case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
GNUNET_assert (0);
}
- primary_currency = uc->parse_template.tcurrency;
+ primary_currency = uc->template_contract.currency;
if (! uc->parse_request.no_amount)
primary_currency = uc->parse_request.amount.currency;
if (! uc->parse_request.no_tip)
@@ -1609,7 +1497,7 @@ create_using_templates_inventory (struct UseContext *uc)
choices),
GNUNET_JSON_pack_string ("summary",
NULL == uc->parse_request.summary
- ? uc->parse_template.tsummary
+ ? uc->template_contract.summary
: uc->parse_request.summary),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("fulfillment_url",
@@ -1648,7 +1536,7 @@ create_using_templates_fixed (struct UseContext *uc)
GNUNET_JSON_pack_string (
"summary",
NULL == uc->parse_request.summary
- ? uc->parse_template.tsummary
+ ? uc->template_contract.summary
: uc->parse_request.summary),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("fulfillment_url",
@@ -1729,7 +1617,7 @@ TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh,
handle_phase_lookup_template (uc);
break;
case USE_PHASE_PARSE_TEMPLATE:
- handle_phase_parse_template (uc);
+ handle_phase_template_contract (uc);
break;
case USE_PHASE_DB_FETCH:
handle_phase_db_fetch (uc);
diff --git a/src/backend/taler-merchant-httpd_private-patch-templates-ID.c b/src/backend/taler-merchant-httpd_private-patch-templates-ID.c
@@ -133,7 +133,7 @@ TMH_private_patch_templates_ID (const struct TMH_RequestHandler *rh,
: MHD_NO;
}
- if (! TMH_template_contract_valid (tp.template_contract))
+ if (! TALER_MERCHANT_template_contract_valid (tp.template_contract))
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
diff --git a/src/backend/taler-merchant-httpd_private-post-templates.c b/src/backend/taler-merchant-httpd_private-post-templates.c
@@ -101,7 +101,7 @@ TMH_private_post_templates (const struct TMH_RequestHandler *rh,
: MHD_NO;
}
}
- if (! TMH_template_contract_valid (tp.template_contract))
+ if (! TALER_MERCHANT_template_contract_valid (tp.template_contract))
{
GNUNET_break_op (0);
json_dumpf (tp.template_contract,
diff --git a/src/include/taler_merchant_util.h b/src/include/taler_merchant_util.h
@@ -224,22 +224,144 @@ enum TALER_MERCHANT_TemplateType
* @param template_type string value (NULL means fixed-order)
* @return template type (defaults to fixed order)
*/
-static inline enum TALER_MERCHANT_TemplateType
-TALER_MERCHANT_template_type_from_string (const char *template_type)
+enum TALER_MERCHANT_TemplateType
+TALER_MERCHANT_template_type_from_string (const char *template_type);
+
+/**
+ * Convert template type to its string representation.
+ *
+ * @param template_type template type to convert
+ * @return string name or NULL for invalid types
+ */
+const char *
+TALER_MERCHANT_template_type_to_string (
+ enum TALER_MERCHANT_TemplateType template_type);
+
+/**
+ * Determine template type from a template contract.
+ *
+ * @param template_contract contract JSON
+ * @return template type (defaults to fixed order)
+ */
+enum TALER_MERCHANT_TemplateType
+TALER_MERCHANT_template_type_from_contract (const json_t *template_contract);
+
+/**
+ * Template contract fields for inventory templates.
+ */
+struct TALER_MERCHANT_TemplateContractInventory
+{
+ /**
+ * Selected categories from the template contract.
+ */
+ const json_t *selected_categories;
+
+ /**
+ * Selected products from the template contract.
+ */
+ const json_t *selected_products;
+
+ /**
+ * Whether all products are selectable.
+ */
+ bool selected_all;
+
+ /**
+ * Template requires exactly one selection.
+ */
+ bool choose_one;
+
+ /**
+ * Template allows tips.
+ */
+ bool request_tip;
+};
+
+/**
+ * Template contract fields for paivana templates.
+ */
+struct TALER_MERCHANT_TemplateContractPaivana
{
- if (NULL == template_type)
- return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER;
- if (0 == strcmp (template_type,
- "fixed-order"))
- return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER;
- if (0 == strcmp (template_type,
- "inventory-cart"))
- return TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART;
- if (0 == strcmp (template_type,
- "paivana"))
- return TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA;
- return TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
-}
+ /**
+ * Paivana reference ID.
+ */
+ const char *paivana_id;
+};
+
+/**
+ * Parsed template contract.
+ */
+struct TALER_MERCHANT_TemplateContract
+{
+ /**
+ * Template type.
+ */
+ enum TALER_MERCHANT_TemplateType type;
+
+ /**
+ * Summary from the template contract.
+ */
+ const char *summary;
+
+ /**
+ * Currency from the template contract.
+ */
+ const char *currency;
+
+ /**
+ * Amount from the template contract.
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * True if @e amount was not provided.
+ */
+ bool no_amount;
+
+ /**
+ * Minimum age required by the template.
+ */
+ uint32_t minimum_age;
+
+ /**
+ * How long does the customer have to pay for the order.
+ * 0 if not specified (use instance default).
+ */
+ struct GNUNET_TIME_Relative pay_duration;
+
+ /**
+ * Parsed fields for inventory templates.
+ */
+ struct TALER_MERCHANT_TemplateContractInventory inventory;
+
+ /**
+ * Parsed fields for paivana templates.
+ */
+ struct TALER_MERCHANT_TemplateContractPaivana paivana;
+};
+
+/**
+ * Parse template contract JSON into @a out.
+ *
+ * @param template_contract JSON object containing the template contract
+ * @param[out] out parsed template contract
+ * @param[out] error_name pointer to the name of the failed field, or NULL
+ * @return #GNUNET_SYSERR if @a template_contract is malformed; #GNUNET_OK otherwise
+ */
+enum GNUNET_GenericReturnValue
+TALER_MERCHANT_template_contract_parse (
+ const json_t *template_contract,
+ struct TALER_MERCHANT_TemplateContract *out,
+ const char **error_name);
+
+/**
+ * Check if @a template_contract is valid.
+ *
+ * @param template_contract template contract to validate
+ * @return true if @a template_contract is valid
+ */
+bool
+TALER_MERCHANT_template_contract_valid (const json_t *template_contract);
/**
diff --git a/src/lib/merchant_api_post_templates.c b/src/lib/merchant_api_post_templates.c
@@ -34,6 +34,24 @@
#include <taler/taler_curl_lib.h>
#include "taler_merchant_util.h"
+/* FIXME: Bohdan is to stupid to figure out how util can be used here */
+static enum TALER_MERCHANT_TemplateType
+template_type_from_string (const char *template_type)
+{
+ if (NULL == template_type)
+ return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER;
+ if (0 == strcmp (template_type,
+ "fixed-order"))
+ return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER;
+ if (0 == strcmp (template_type,
+ "inventory-cart"))
+ return TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART;
+ if (0 == strcmp (template_type,
+ "paivana"))
+ return TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA;
+ return TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
+}
+
/**
* Validate a fixed-order template contract.
@@ -360,8 +378,7 @@ test_template_contract_valid (const json_t *template_contract)
return false;
}
- template_type_enum =
- TALER_MERCHANT_template_type_from_string (template_type);
+ template_type_enum = template_type_from_string (template_type);
if (TALER_MERCHANT_TEMPLATE_TYPE_INVALID == template_type_enum)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
@@ -370,6 +387,7 @@ test_template_contract_valid (const json_t *template_contract)
return false;
}
+ /* FIXME: Bohdan understands that links have to be changed, but worried, that can crash something */
switch (template_type_enum)
{
case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
diff --git a/src/util/Makefile.am b/src/util/Makefile.am
@@ -45,6 +45,7 @@ libtalermerchantutil_la_SOURCES = \
json.c \
mfa.c \
os_installation.c \
+ template_parse.c \
value_kinds.c \
validators.c
libtalermerchantutil_la_LIBADD = \
diff --git a/src/util/template_parse.c b/src/util/template_parse.c
@@ -0,0 +1,303 @@
+/*
+ This file is part of TALER
+ (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file util/template_parse.c
+ * @brief shared logic for template contract parsing
+ * @author Bohdan Potuzhnyi
+ */
+#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <string.h>
+#include <taler/taler_json_lib.h>
+#include <taler/taler_util.h>
+#include "taler_merchant_util.h"
+
+
+enum TALER_MERCHANT_TemplateType
+TALER_MERCHANT_template_type_from_contract (const json_t *template_contract)
+{
+ const json_t *type_val;
+
+ if (NULL == template_contract)
+ return TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
+
+ type_val = json_object_get (template_contract,
+ "template_type");
+
+ if (! json_is_string (type_val))
+ return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER;
+
+ return TALER_MERCHANT_template_type_from_string (
+ json_string_value (type_val));
+}
+
+
+enum TALER_MERCHANT_TemplateType
+TALER_MERCHANT_template_type_from_string (const char *template_type)
+{
+ if (NULL == template_type)
+ return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER;
+ if (0 == strcmp (template_type,
+ "fixed-order"))
+ return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER;
+ if (0 == strcmp (template_type,
+ "inventory-cart"))
+ return TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART;
+ if (0 == strcmp (template_type,
+ "paivana"))
+ return TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA;
+ return TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
+}
+
+
+const char *
+TALER_MERCHANT_template_type_to_string (
+ enum TALER_MERCHANT_TemplateType template_type)
+{
+ switch (template_type)
+ {
+ case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
+ return "fixed-order";
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
+ return "inventory-cart";
+ case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
+ return "paivana";
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
+ break;
+ }
+ return NULL;
+}
+
+
+/**
+ * Parse inventory-specific fields from a template contract.
+ *
+ * @param template_contract json
+ * @param[out] out where to write parsed fields
+ * @param[out] error_name error description
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on parse/validation failure
+ */
+static enum GNUNET_GenericReturnValue
+parse_template_inventory (const json_t *template_contract,
+ struct TALER_MERCHANT_TemplateContract *out,
+ const char **error_name)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool ("request_tip",
+ &out->inventory.request_tip),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool ("selected_all",
+ &out->inventory.selected_all),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("selected_categories",
+ &out->inventory.selected_categories),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("selected_products",
+ &out->inventory.selected_products),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool ("choose_one",
+ &out->inventory.choose_one),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse ((json_t *) template_contract,
+ spec,
+ error_name,
+ NULL))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid inventory template_contract for field %s\n",
+ *error_name);
+ return GNUNET_SYSERR;
+ }
+
+ if (NULL != out->inventory.selected_categories)
+ {
+ const json_t *entry;
+ size_t idx;
+
+ json_array_foreach ((json_t *) out->inventory.selected_categories, idx,
+ entry)
+ {
+ if ( (! json_is_integer (entry)) ||
+ (0 > json_integer_value (entry)) )
+ {
+ GNUNET_break_op (0);
+ *error_name = "selected_categories";
+ return GNUNET_SYSERR;
+ }
+ }
+ }
+
+ if (NULL != out->inventory.selected_products)
+ {
+ const json_t *entry;
+ size_t idx;
+
+ json_array_foreach ((json_t *) out->inventory.selected_products, idx, entry)
+ {
+ if (! json_is_string (entry))
+ {
+ GNUNET_break_op (0);
+ *error_name = "selected_products";
+ return GNUNET_SYSERR;
+ }
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse paivana-specific fields from a template contract.
+ *
+ * @param template_contract json
+ * @param[out] out where to write parsed fields
+ * @param[out] error_name error description
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on parse/validation failure
+ */
+static enum GNUNET_GenericReturnValue
+parse_template_paivana (const json_t *template_contract,
+ struct TALER_MERCHANT_TemplateContract *out,
+ const char **error_name)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("paivana_id",
+ &out->paivana.paivana_id),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse ((json_t *) template_contract,
+ spec,
+ error_name,
+ NULL))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid paivana template_contract for field %s\n",
+ *error_name);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_MERCHANT_template_contract_parse (
+ const json_t *template_contract,
+ struct TALER_MERCHANT_TemplateContract *out,
+ const char **error_name)
+{
+ const char *template_type_str = NULL;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("template_type",
+ &template_type_str),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("summary",
+ &out->summary),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("currency",
+ &out->currency),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_amount_any ("amount",
+ &out->amount),
+ &out->no_amount),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("minimum_age",
+ &out->minimum_age),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_relative_time ("pay_duration",
+ &out->pay_duration),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (NULL == template_contract)
+ {
+ *error_name = "template_contract is NULL";
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse ((json_t *) template_contract,
+ spec,
+ error_name,
+ NULL))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid input for field %s\n",
+ *error_name);
+ return GNUNET_SYSERR;
+ }
+
+ out->type = TALER_MERCHANT_template_type_from_string (template_type_str);
+ if (TALER_MERCHANT_TEMPLATE_TYPE_INVALID == out->type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid template_type used '%s'\n",
+ template_type_str);
+ *error_name = "Invalid template_type used";
+ return GNUNET_SYSERR;
+ }
+
+ /* Parse additional fields for each specific type */
+ switch (out->type)
+ {
+ case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
+ return GNUNET_OK;
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
+ return parse_template_inventory (template_contract,
+ out,
+ error_name);
+ case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
+ return parse_template_paivana (template_contract,
+ out,
+ error_name);
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
+ break;
+ }
+
+ /* I think we are never supposed to reach it */
+ GNUNET_break_op (0);
+ *error_name = "template_type";
+ return GNUNET_SYSERR;
+}
+
+
+bool
+TALER_MERCHANT_template_contract_valid (const json_t *template_contract)
+{
+ struct TALER_MERCHANT_TemplateContract tmp;
+
+ return (GNUNET_OK ==
+ TALER_MERCHANT_template_contract_parse (template_contract,
+ &tmp,
+ NULL));
+}