merchant_api_get-private-statistics-amount-SLUG.c (15185B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2025-2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Lesser General Public License as published by the Free Software 7 Foundation; either version 2.1, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 12 13 You should have received a copy of the GNU Lesser General Public License along with 14 TALER; see the file COPYING.LGPL. If not, see 15 <http://www.gnu.org/licenses/> 16 */ 17 /** 18 * @file src/lib/merchant_api_get-private-statistics-amount-SLUG.c 19 * @brief Implementation of the GET /private/statistics-amount/$SLUG request 20 * @author Christian Grothoff 21 */ 22 #include "platform.h" 23 #include <curl/curl.h> 24 #include <jansson.h> 25 #include <microhttpd.h> /* just for HTTP status codes */ 26 #include <gnunet/gnunet_util_lib.h> 27 #include <gnunet/gnunet_curl_lib.h> 28 #include <taler/merchant/get-private-statistics-amount-SLUG.h> 29 #include "merchant_api_curl_defaults.h" 30 #include <taler/taler_json_lib.h> 31 32 33 /** 34 * Maximum number of statistics entries we return. 35 */ 36 #define MAX_STATISTICS 1024 37 38 39 /** 40 * Handle for a GET /private/statistics-amount/$SLUG operation. 41 */ 42 struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle 43 { 44 /** 45 * Base URL of the merchant backend. 46 */ 47 char *base_url; 48 49 /** 50 * The full URL for this request. 51 */ 52 char *url; 53 54 /** 55 * Handle for the request. 56 */ 57 struct GNUNET_CURL_Job *job; 58 59 /** 60 * Function to call with the result. 61 */ 62 TALER_MERCHANT_GetPrivateStatisticsAmountCallback cb; 63 64 /** 65 * Closure for @a cb. 66 */ 67 TALER_MERCHANT_GET_PRIVATE_STATISTICS_AMOUNT_RESULT_CLOSURE *cb_cls; 68 69 /** 70 * Reference to the execution context. 71 */ 72 struct GNUNET_CURL_Context *ctx; 73 74 /** 75 * Statistics slug. 76 */ 77 char *slug; 78 79 /** 80 * Aggregation mode. 81 */ 82 enum TALER_MERCHANT_StatisticsType stype; 83 84 /** 85 * Whether stype was explicitly set. 86 */ 87 bool have_stype; 88 }; 89 90 91 /** 92 * Parse interval and bucket data from the JSON response. 93 * 94 * @param json overall JSON reply 95 * @param jbuckets JSON array with bucket data 96 * @param buckets_description human-readable description for buckets 97 * @param jintervals JSON array with interval data 98 * @param intervals_description human-readable description for intervals 99 * @param sah operation handle 100 * @return #GNUNET_OK on success 101 */ 102 static enum GNUNET_GenericReturnValue 103 parse_intervals_and_buckets_amt ( 104 const json_t *json, 105 const json_t *jbuckets, 106 const char *buckets_description, 107 const json_t *jintervals, 108 const char *intervals_description, 109 struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah) 110 { 111 unsigned int resp_buckets_len = json_array_size (jbuckets); 112 unsigned int resp_intervals_len = json_array_size (jintervals); 113 size_t total_amounts = 0; 114 size_t amounts_pos = 0; 115 116 if ( (json_array_size (jbuckets) != (size_t) resp_buckets_len) || 117 (json_array_size (jintervals) != (size_t) resp_intervals_len) || 118 (resp_buckets_len > MAX_STATISTICS) || 119 (resp_intervals_len > MAX_STATISTICS) ) 120 { 121 GNUNET_break (0); 122 return GNUNET_SYSERR; 123 } 124 { 125 size_t index; 126 json_t *value; 127 128 json_array_foreach (jintervals, index, value) { 129 const json_t *amounts_arr; 130 struct GNUNET_JSON_Specification spec[] = { 131 GNUNET_JSON_spec_array_const ("cumulative_amounts", 132 &amounts_arr), 133 GNUNET_JSON_spec_end () 134 }; 135 136 if (GNUNET_OK != 137 GNUNET_JSON_parse (value, 138 spec, 139 NULL, NULL)) 140 { 141 GNUNET_break_op (0); 142 return GNUNET_SYSERR; 143 } 144 total_amounts += json_array_size (amounts_arr); 145 } 146 147 json_array_foreach (jbuckets, index, value) { 148 const json_t *amounts_arr; 149 struct GNUNET_JSON_Specification spec[] = { 150 GNUNET_JSON_spec_array_const ("cumulative_amounts", 151 &amounts_arr), 152 GNUNET_JSON_spec_end () 153 }; 154 155 if (GNUNET_OK != 156 GNUNET_JSON_parse (value, 157 spec, 158 NULL, NULL)) 159 { 160 GNUNET_break_op (0); 161 return GNUNET_SYSERR; 162 } 163 total_amounts += json_array_size (amounts_arr); 164 } 165 } 166 { 167 struct TALER_Amount glob_amt_arr[GNUNET_NZL (total_amounts)]; 168 struct TALER_MERCHANT_GetPrivateStatisticsAmountByBucket resp_buckets[ 169 GNUNET_NZL (resp_buckets_len)]; 170 struct TALER_MERCHANT_GetPrivateStatisticsAmountByInterval resp_intervals[ 171 GNUNET_NZL (resp_intervals_len)]; 172 size_t index; 173 json_t *value; 174 enum GNUNET_GenericReturnValue ret; 175 176 ret = GNUNET_OK; 177 json_array_foreach (jintervals, index, value) { 178 struct TALER_MERCHANT_GetPrivateStatisticsAmountByInterval *jinterval 179 = &resp_intervals[index]; 180 const json_t *amounts_arr; 181 size_t amounts_len; 182 struct GNUNET_JSON_Specification spec[] = { 183 GNUNET_JSON_spec_timestamp ("start_time", 184 &jinterval->start_time), 185 GNUNET_JSON_spec_array_const ("cumulative_amounts", 186 &amounts_arr), 187 GNUNET_JSON_spec_end () 188 }; 189 190 if (GNUNET_OK != 191 GNUNET_JSON_parse (value, 192 spec, 193 NULL, NULL)) 194 { 195 GNUNET_break_op (0); 196 return GNUNET_SYSERR; 197 } 198 amounts_len = json_array_size (amounts_arr); 199 { 200 struct TALER_Amount *amt_arr = &glob_amt_arr[amounts_pos]; 201 size_t aindex; 202 json_t *avalue; 203 204 amounts_pos += amounts_len; 205 jinterval->cumulative_amount_len = amounts_len; 206 jinterval->cumulative_amounts = amt_arr; 207 json_array_foreach (amounts_arr, aindex, avalue) { 208 if (! json_is_string (avalue)) 209 { 210 GNUNET_break_op (0); 211 return GNUNET_SYSERR; 212 } 213 if (GNUNET_OK != 214 TALER_string_to_amount (json_string_value (avalue), 215 &amt_arr[aindex])) 216 { 217 GNUNET_break_op (0); 218 return GNUNET_SYSERR; 219 } 220 } 221 } 222 } 223 json_array_foreach (jbuckets, index, value) { 224 struct TALER_MERCHANT_GetPrivateStatisticsAmountByBucket *jbucket 225 = &resp_buckets[index]; 226 const json_t *amounts_arr; 227 size_t amounts_len; 228 struct GNUNET_JSON_Specification spec[] = { 229 GNUNET_JSON_spec_timestamp ("start_time", 230 &jbucket->start_time), 231 GNUNET_JSON_spec_timestamp ("end_time", 232 &jbucket->end_time), 233 GNUNET_JSON_spec_string ("range", 234 &jbucket->range), 235 GNUNET_JSON_spec_array_const ("cumulative_amounts", 236 &amounts_arr), 237 GNUNET_JSON_spec_end () 238 }; 239 240 if (GNUNET_OK != 241 GNUNET_JSON_parse (value, 242 spec, 243 NULL, NULL)) 244 { 245 GNUNET_break_op (0); 246 ret = GNUNET_SYSERR; 247 break; 248 } 249 amounts_len = json_array_size (amounts_arr); 250 { 251 struct TALER_Amount *amt_arr = &glob_amt_arr[amounts_pos]; 252 size_t aindex; 253 json_t *avalue; 254 255 amounts_pos += amounts_len; 256 jbucket->cumulative_amount_len = amounts_len; 257 jbucket->cumulative_amounts = amt_arr; 258 json_array_foreach (amounts_arr, aindex, avalue) { 259 if (! json_is_string (avalue)) 260 { 261 GNUNET_break_op (0); 262 return GNUNET_SYSERR; 263 } 264 if (GNUNET_OK != 265 TALER_string_to_amount (json_string_value (avalue), 266 &amt_arr[aindex])) 267 { 268 GNUNET_break_op (0); 269 return GNUNET_SYSERR; 270 } 271 } 272 } 273 } 274 if (GNUNET_OK == ret) 275 { 276 struct TALER_MERCHANT_GetPrivateStatisticsAmountResponse sagr = { 277 .hr.http_status = MHD_HTTP_OK, 278 .hr.reply = json, 279 .details.ok.buckets_length = resp_buckets_len, 280 .details.ok.buckets = resp_buckets, 281 .details.ok.buckets_description = buckets_description, 282 .details.ok.intervals_length = resp_intervals_len, 283 .details.ok.intervals = resp_intervals, 284 .details.ok.intervals_description = intervals_description, 285 }; 286 287 sah->cb (sah->cb_cls, 288 &sagr); 289 sah->cb = NULL; 290 } 291 return ret; 292 } 293 } 294 295 296 /** 297 * Function called when we're done processing the 298 * HTTP GET /private/statistics-amount/$SLUG request. 299 * 300 * @param cls the `struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle` 301 * @param response_code HTTP response code, 0 on error 302 * @param response response body, NULL if not in JSON 303 */ 304 static void 305 handle_get_statistics_amount_finished (void *cls, 306 long response_code, 307 const void *response) 308 { 309 struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah = cls; 310 const json_t *json = response; 311 struct TALER_MERCHANT_GetPrivateStatisticsAmountResponse sagr = { 312 .hr.http_status = (unsigned int) response_code, 313 .hr.reply = json 314 }; 315 316 sah->job = NULL; 317 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 318 "Got /private/statistics-amount/$SLUG response with status code %u\n", 319 (unsigned int) response_code); 320 switch (response_code) 321 { 322 case MHD_HTTP_OK: 323 { 324 const json_t *buckets; 325 const json_t *intervals; 326 const char *buckets_description = NULL; 327 const char *intervals_description = NULL; 328 struct GNUNET_JSON_Specification spec[] = { 329 GNUNET_JSON_spec_array_const ("buckets", 330 &buckets), 331 GNUNET_JSON_spec_mark_optional ( 332 GNUNET_JSON_spec_string ("buckets_description", 333 &buckets_description), 334 NULL), 335 GNUNET_JSON_spec_array_const ("intervals", 336 &intervals), 337 GNUNET_JSON_spec_mark_optional ( 338 GNUNET_JSON_spec_string ("intervals_description", 339 &intervals_description), 340 NULL), 341 GNUNET_JSON_spec_end () 342 }; 343 344 if (GNUNET_OK != 345 GNUNET_JSON_parse (json, 346 spec, 347 NULL, NULL)) 348 { 349 sagr.hr.http_status = 0; 350 sagr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 351 break; 352 } 353 if (GNUNET_OK == 354 parse_intervals_and_buckets_amt (json, 355 buckets, 356 buckets_description, 357 intervals, 358 intervals_description, 359 sah)) 360 { 361 TALER_MERCHANT_get_private_statistics_amount_cancel (sah); 362 return; 363 } 364 sagr.hr.http_status = 0; 365 sagr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 366 break; 367 } 368 case MHD_HTTP_UNAUTHORIZED: 369 sagr.hr.ec = TALER_JSON_get_error_code (json); 370 sagr.hr.hint = TALER_JSON_get_error_hint (json); 371 break; 372 case MHD_HTTP_NOT_FOUND: 373 sagr.hr.ec = TALER_JSON_get_error_code (json); 374 sagr.hr.hint = TALER_JSON_get_error_hint (json); 375 break; 376 default: 377 sagr.hr.ec = TALER_JSON_get_error_code (json); 378 sagr.hr.hint = TALER_JSON_get_error_hint (json); 379 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 380 "Unexpected response code %u/%d\n", 381 (unsigned int) response_code, 382 (int) sagr.hr.ec); 383 break; 384 } 385 sah->cb (sah->cb_cls, 386 &sagr); 387 TALER_MERCHANT_get_private_statistics_amount_cancel (sah); 388 } 389 390 391 struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle * 392 TALER_MERCHANT_get_private_statistics_amount_create ( 393 struct GNUNET_CURL_Context *ctx, 394 const char *url, 395 const char *slug) 396 { 397 struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah; 398 399 sah = GNUNET_new (struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle); 400 sah->ctx = ctx; 401 sah->base_url = GNUNET_strdup (url); 402 sah->slug = GNUNET_strdup (slug); 403 sah->stype = TALER_MERCHANT_STATISTICS_ALL; 404 return sah; 405 } 406 407 408 enum GNUNET_GenericReturnValue 409 TALER_MERCHANT_get_private_statistics_amount_set_options_ ( 410 struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah, 411 unsigned int num_options, 412 const struct TALER_MERCHANT_GetPrivateStatisticsAmountOptionValue *options) 413 { 414 for (unsigned int i = 0; i < num_options; i++) 415 { 416 const struct TALER_MERCHANT_GetPrivateStatisticsAmountOptionValue *opt = 417 &options[i]; 418 419 switch (opt->option) 420 { 421 case TALER_MERCHANT_GET_PRIVATE_STATISTICS_AMOUNT_OPTION_END: 422 return GNUNET_OK; 423 case TALER_MERCHANT_GET_PRIVATE_STATISTICS_AMOUNT_OPTION_TYPE: 424 sah->stype = opt->details.type; 425 sah->have_stype = true; 426 break; 427 default: 428 GNUNET_break (0); 429 return GNUNET_NO; 430 } 431 } 432 return GNUNET_OK; 433 } 434 435 436 enum TALER_ErrorCode 437 TALER_MERCHANT_get_private_statistics_amount_start ( 438 struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah, 439 TALER_MERCHANT_GetPrivateStatisticsAmountCallback cb, 440 TALER_MERCHANT_GET_PRIVATE_STATISTICS_AMOUNT_RESULT_CLOSURE *cb_cls) 441 { 442 CURL *eh; 443 444 sah->cb = cb; 445 sah->cb_cls = cb_cls; 446 { 447 const char *filter = NULL; 448 char *path; 449 450 switch (sah->stype) 451 { 452 case TALER_MERCHANT_STATISTICS_BY_BUCKET: 453 filter = "bucket"; 454 break; 455 case TALER_MERCHANT_STATISTICS_BY_INTERVAL: 456 filter = "interval"; 457 break; 458 case TALER_MERCHANT_STATISTICS_ALL: 459 filter = NULL; 460 break; 461 } 462 GNUNET_asprintf (&path, 463 "private/statistics-amount/%s", 464 sah->slug); 465 sah->url = TALER_url_join (sah->base_url, 466 path, 467 "by", 468 filter, 469 NULL); 470 GNUNET_free (path); 471 } 472 if (NULL == sah->url) 473 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 474 eh = TALER_MERCHANT_curl_easy_get_ (sah->url); 475 if (NULL == eh) 476 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 477 sah->job = GNUNET_CURL_job_add (sah->ctx, 478 eh, 479 &handle_get_statistics_amount_finished, 480 sah); 481 if (NULL == sah->job) 482 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 483 return TALER_EC_NONE; 484 } 485 486 487 void 488 TALER_MERCHANT_get_private_statistics_amount_cancel ( 489 struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah) 490 { 491 if (NULL != sah->job) 492 { 493 GNUNET_CURL_job_cancel (sah->job); 494 sah->job = NULL; 495 } 496 GNUNET_free (sah->url); 497 GNUNET_free (sah->base_url); 498 GNUNET_free (sah->slug); 499 GNUNET_free (sah); 500 } 501 502 503 /* end of merchant_api_get-private-statistics-amount-SLUG-new.c */