commit 09c8beb73e3de044f17d512b8ad75ed091bcc0e6
parent 59f53d4016cd7c6618e1820cf4ad862e3b0a8701
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date: Wed, 12 Nov 2025 01:21:59 +0100
TALER_JSON_spec_amount_any_array + TALER_JSON_pack_amount_array
Diffstat:
4 files changed, 259 insertions(+), 21 deletions(-)
diff --git a/src/include/taler/taler_json_lib.h b/src/include/taler/taler_json_lib.h
@@ -41,9 +41,9 @@
* @deprecated
*/
#define TALER_json_warn(error) \
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING, \
- "JSON parsing failed at %s:%u: %s (%s)\n", \
- __FILE__, __LINE__, error.text, error.source)
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, \
+ "JSON parsing failed at %s:%u: %s (%s)\n", \
+ __FILE__, __LINE__, error.text, error.source)
/**
@@ -67,8 +67,8 @@ TALER_JSON_pack_time_abs_human (
* @param ec error code to encode using canonical field names
*/
#define TALER_JSON_pack_ec(ec) \
- GNUNET_JSON_pack_string ("hint", TALER_ErrorCode_get_hint (ec)), \
- GNUNET_JSON_pack_uint64 ("code", ec)
+ GNUNET_JSON_pack_string ("hint", TALER_ErrorCode_get_hint (ec)), \
+ GNUNET_JSON_pack_uint64 ("code", ec)
/**
@@ -210,6 +210,21 @@ TALER_JSON_pack_amount (
/**
+ * Generate packer instruction for a JSON field that contains an array of
+ * amounts (as strings). If @a amounts is NULL, emits JSON null.
+ *
+ * @param name field name
+ * @param len number of elements in @a amounts
+ * @param amounts array to encode
+ * @return json pack specification
+ */
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_amount_array (const char *name,
+ size_t len,
+ const struct TALER_Amount *amounts);
+
+
+/**
* Generate packer instruction for a JSON field of type
* full payto.
*
@@ -347,6 +362,21 @@ TALER_JSON_spec_amount (const char *name,
/**
+ * Result specification for an array of amounts. Elements must be strings in
+ * the usual "CUR:VAL.FRAC" notation. Allocates *@a amounts and sets
+ * @a amounts_len on success. Use GNUNET_JSON_parse_free() to release the array.
+ *
+ * @param field name of the field to parse
+ * @param amounts_len where to store the array length
+ * @param amounts where the allocated array pointer is written
+ */
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_amount_any_array (const char *field,
+ size_t *amounts_len,
+ struct TALER_Amount **amounts);
+
+
+/**
* Provide specification to parse given JSON object to
* a currency specification.
*
@@ -451,10 +481,10 @@ TALER_JSON_spec_kycte (const char *name,
* @param[out] dfs a `struct TALER_DenomFeeSet` to initialize
*/
#define TALER_JSON_SPEC_DENOM_FEES(pfx,currency,dfs) \
- TALER_JSON_spec_amount (pfx "_withdraw", (currency), &(dfs)->withdraw), \
- TALER_JSON_spec_amount (pfx "_deposit", (currency), &(dfs)->deposit), \
- TALER_JSON_spec_amount (pfx "_refresh", (currency), &(dfs)->refresh), \
- TALER_JSON_spec_amount (pfx "_refund", (currency), &(dfs)->refund)
+ TALER_JSON_spec_amount (pfx "_withdraw", (currency), &(dfs)->withdraw), \
+ TALER_JSON_spec_amount (pfx "_deposit", (currency), &(dfs)->deposit), \
+ TALER_JSON_spec_amount (pfx "_refresh", (currency), &(dfs)->refresh), \
+ TALER_JSON_spec_amount (pfx "_refund", (currency), &(dfs)->refund)
/**
@@ -465,10 +495,10 @@ TALER_JSON_spec_kycte (const char *name,
* @param dfs a `struct TALER_DenomFeeSet` to pack
*/
#define TALER_JSON_PACK_DENOM_FEES(pfx, dfs) \
- TALER_JSON_pack_amount (pfx "_withdraw", &(dfs)->withdraw), \
- TALER_JSON_pack_amount (pfx "_deposit", &(dfs)->deposit), \
- TALER_JSON_pack_amount (pfx "_refresh", &(dfs)->refresh), \
- TALER_JSON_pack_amount (pfx "_refund", &(dfs)->refund)
+ TALER_JSON_pack_amount (pfx "_withdraw", &(dfs)->withdraw), \
+ TALER_JSON_pack_amount (pfx "_deposit", &(dfs)->deposit), \
+ TALER_JSON_pack_amount (pfx "_refresh", &(dfs)->refresh), \
+ TALER_JSON_pack_amount (pfx "_refund", &(dfs)->refund)
/**
@@ -478,9 +508,9 @@ TALER_JSON_spec_kycte (const char *name,
* @param[out] gfs a `struct TALER_GlobalFeeSet` to initialize
*/
#define TALER_JSON_SPEC_GLOBAL_FEES(currency,gfs) \
- TALER_JSON_spec_amount ("history_fee", (currency), &(gfs)->history), \
- TALER_JSON_spec_amount ("account_fee", (currency), &(gfs)->account), \
- TALER_JSON_spec_amount ("purse_fee", (currency), &(gfs)->purse)
+ TALER_JSON_spec_amount ("history_fee", (currency), &(gfs)->history), \
+ TALER_JSON_spec_amount ("account_fee", (currency), &(gfs)->account), \
+ TALER_JSON_spec_amount ("purse_fee", (currency), &(gfs)->purse)
/**
* Macro to pack all of the global fees.
@@ -488,9 +518,9 @@ TALER_JSON_spec_kycte (const char *name,
* @param gfs a `struct TALER_GlobalFeeSet` to pack
*/
#define TALER_JSON_PACK_GLOBAL_FEES(gfs) \
- TALER_JSON_pack_amount ("history_fee", &(gfs)->history), \
- TALER_JSON_pack_amount ("account_fee", &(gfs)->account), \
- TALER_JSON_pack_amount ("purse_fee", &(gfs)->purse)
+ TALER_JSON_pack_amount ("history_fee", &(gfs)->history), \
+ TALER_JSON_pack_amount ("account_fee", &(gfs)->account), \
+ TALER_JSON_pack_amount ("purse_fee", &(gfs)->purse)
/**
diff --git a/src/json/json_helper.c b/src/json/json_helper.c
@@ -149,6 +149,124 @@ TALER_JSON_spec_amount_any (const char *name,
/**
+ * Closure for parsing amount arrays.
+ */
+struct AmountArrayCtx
+{
+ /**
+ * Pointer where to store the resulting array length.
+ */
+ size_t *len;
+};
+
+
+/**
+ * Parse a JSON array of arbitrary amounts.
+ */
+static enum GNUNET_GenericReturnValue
+parse_amount_any_array (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct AmountArrayCtx *ctx = cls;
+ struct TALER_Amount **out = spec->ptr;
+
+ GNUNET_assert (NULL != ctx);
+ GNUNET_assert (NULL != out);
+ *out = NULL;
+ if (NULL != ctx->len)
+ *ctx->len = 0;
+
+ if (! json_is_array (root))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ {
+ size_t len = json_array_size (root);
+
+ if (NULL != ctx->len)
+ *ctx->len = len;
+ if (0 == len)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ *out = GNUNET_new_array (len,
+ struct TALER_Amount);
+ json_t *entry;
+ size_t idx;
+
+ json_array_foreach (root, idx, entry) {
+ const char *amount_str;
+
+ if (! json_is_string (entry))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ amount_str = json_string_value (entry);
+ if (GNUNET_OK !=
+ TALER_string_to_amount (amount_str,
+ &(*out)[idx]))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Cleanup helper for the amount array parser.
+ */
+static void
+clean_amount_any_array (void *cls,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct AmountArrayCtx *ctx = cls;
+
+ if (NULL != spec->ptr)
+ {
+ GNUNET_free (*(void **) spec->ptr);
+ *(void **) spec->ptr = NULL;
+ }
+ if ( (NULL != ctx) &&
+ (NULL != ctx->len) )
+ *ctx->len = 0;
+ GNUNET_free (ctx);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_amount_any_array (const char *field,
+ size_t *amounts_len,
+ struct TALER_Amount **amounts)
+{
+ struct AmountArrayCtx *ctx;
+
+ GNUNET_assert (NULL != amounts_len);
+ GNUNET_assert (NULL != amounts);
+ ctx = GNUNET_new (struct AmountArrayCtx);
+ ctx->len = amounts_len;
+ {
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_amount_any_array,
+ .cleaner = &clean_amount_any_array,
+ .cls = ctx,
+ .field = field,
+ .ptr = amounts
+ };
+
+ return ret;
+ }
+}
+
+
+/**
* Parse given JSON object to currency spec.
*
* @param cls closure, NULL
@@ -1911,7 +2029,7 @@ parse_array_fixed (void *cls,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- ptr+=entry_size;
+ ptr += entry_size;
}
}
return GNUNET_OK;
@@ -2011,7 +2129,7 @@ parse_array_of_data (void *cls,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- ptr+=info->entry_size;
+ ptr += info->entry_size;
}
}
return GNUNET_OK;
diff --git a/src/json/json_pack.c b/src/json/json_pack.c
@@ -441,6 +441,45 @@ TALER_JSON_pack_amount (const char *name,
struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_amount_array (const char *name,
+ size_t len,
+ const struct TALER_Amount *amounts)
+{
+ struct GNUNET_JSON_PackSpec ps = {
+ .field_name = name,
+ };
+
+ if (NULL == amounts)
+ {
+ ps.object = json_null ();
+ return ps;
+ }
+ {
+ json_t *array = json_array ();
+
+ GNUNET_assert (NULL != array);
+ for (size_t i = 0; i<len; i++)
+ {
+ json_t *entry;
+
+ char *amount_str = TALER_amount_to_string (&amounts[i]);
+ GNUNET_assert (NULL != amount_str);
+
+ entry = json_string (amount_str);
+
+ GNUNET_free (amount_str);
+ GNUNET_assert (NULL != entry);
+ GNUNET_assert (0 ==
+ json_array_append_new (array,
+ entry));
+ }
+ ps.object = array;
+ }
+ return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
TALER_JSON_pack_full_payto (
const char *name,
const struct TALER_FullPayto payto)
diff --git a/src/json/test_json.c b/src/json/test_json.c
@@ -58,6 +58,55 @@ test_amount (void)
}
+/**
+ * Verify JSON packing/parsing for amount arrays.
+ *
+ * @return 0 on success
+ */
+static int
+test_amount_array (void)
+{
+ struct TALER_Amount amounts[2];
+ struct TALER_Amount *parsed = NULL;
+ size_t parsed_len = 0;
+ struct GNUNET_JSON_Specification spec[2];
+ json_t *doc;
+ const size_t num_amounts = sizeof (amounts) / sizeof (amounts[0]);
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("EUR:1.2",
+ &amounts[0]));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("EUR:3.4",
+ &amounts[1]));
+
+ spec[0] = TALER_JSON_spec_amount_any_array ("amounts",
+ &parsed_len,
+ &parsed);
+ spec[1] = GNUNET_JSON_spec_end ();
+
+ doc = GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount_array ("amounts",
+ num_amounts,
+ amounts));
+ GNUNET_assert (NULL != doc);
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_JSON_parse (doc,
+ spec,
+ NULL,
+ NULL));
+ GNUNET_assert (parsed_len == num_amounts);
+ for (size_t i = 0; i<num_amounts; i++)
+ GNUNET_assert (0 ==
+ TALER_amount_cmp (&amounts[i],
+ &parsed[i]));
+ GNUNET_JSON_parse_free (spec);
+ json_decref (doc);
+
+ return 0;
+}
+
+
struct TestPath_Closure
{
const char **object_ids;
@@ -474,6 +523,8 @@ main (int argc,
NULL);
if (0 != test_amount ())
return 1;
+ if (0 != test_amount_array ())
+ return 1;
if (0 != test_contract ())
return 2;
if (0 != test_json_canon ())