merchant_api_get-private-statistics-counter-SLUG.c (12159B)
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 merchant_api_get-private-statistics-counter-SLUG-new.c 19 * @brief Implementation of the GET /private/statistics-counter/$SLUG request 20 * @author Christian Grothoff 21 */ 22 #include "taler/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/taler-merchant/get-private-statistics-counter-SLUG.h> 29 #include "merchant_api_curl_defaults.h" 30 #include <taler/taler_json_lib.h> 31 32 /** 33 * Maximum number of statistics we return. 34 */ 35 #define MAX_STATISTICS 1024 36 37 38 /** 39 * Handle for a GET /private/statistics-counter/$SLUG operation. 40 */ 41 struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle 42 { 43 /** 44 * Base URL of the merchant backend. 45 */ 46 char *base_url; 47 48 /** 49 * The full URL for this request. 50 */ 51 char *url; 52 53 /** 54 * Handle for the request. 55 */ 56 struct GNUNET_CURL_Job *job; 57 58 /** 59 * Function to call with the result. 60 */ 61 TALER_MERCHANT_GetPrivateStatisticsCounterCallback cb; 62 63 /** 64 * Closure for @a cb. 65 */ 66 TALER_MERCHANT_GET_PRIVATE_STATISTICS_COUNTER_RESULT_CLOSURE *cb_cls; 67 68 /** 69 * Reference to the execution context. 70 */ 71 struct GNUNET_CURL_Context *ctx; 72 73 /** 74 * Statistics slug to retrieve. 75 */ 76 char *slug; 77 78 /** 79 * Aggregation filter type, or -1 if not set. 80 */ 81 enum TALER_MERCHANT_StatisticsType stype; 82 83 /** 84 * Whether the type option was set. 85 */ 86 bool type_set; 87 }; 88 89 90 /** 91 * Parse interval and bucket information from the JSON response. 92 * 93 * @param json overall JSON reply 94 * @param jbuckets JSON array with bucket data 95 * @param buckets_description human-readable description for the buckets 96 * @param jintervals JSON array with interval data 97 * @param intervals_description human-readable description for the intervals 98 * @param sch operation handle 99 * @return #GNUNET_OK on success 100 */ 101 static enum GNUNET_GenericReturnValue 102 parse_intervals_and_buckets ( 103 const json_t *json, 104 const json_t *jbuckets, 105 const char *buckets_description, 106 const json_t *jintervals, 107 const char *intervals_description, 108 struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle *sch) 109 { 110 unsigned int resp_buckets_len = (unsigned int) json_array_size (jbuckets); 111 unsigned int resp_intervals_len = (unsigned int) json_array_size (jintervals); 112 113 if ( (json_array_size (jbuckets) != (size_t) resp_buckets_len) || 114 (json_array_size (jintervals) != (size_t) resp_intervals_len) || 115 (resp_buckets_len > MAX_STATISTICS) || 116 (resp_intervals_len > MAX_STATISTICS) ) 117 { 118 GNUNET_break (0); 119 return GNUNET_SYSERR; 120 } 121 { 122 struct TALER_MERCHANT_GetPrivateStatisticsCounterByBucket resp_buckets[ 123 GNUNET_NZL (resp_buckets_len)]; 124 struct TALER_MERCHANT_GetPrivateStatisticsCounterByInterval resp_intervals[ 125 GNUNET_NZL (resp_intervals_len)]; 126 size_t index; 127 json_t *value; 128 129 json_array_foreach (jintervals, index, value) { 130 struct TALER_MERCHANT_GetPrivateStatisticsCounterByInterval *ji 131 = &resp_intervals[index]; 132 struct GNUNET_JSON_Specification spec[] = { 133 GNUNET_JSON_spec_timestamp ("start_time", 134 &ji->start_time), 135 GNUNET_JSON_spec_uint64 ("cumulative_counter", 136 &ji->cumulative_counter), 137 GNUNET_JSON_spec_end () 138 }; 139 140 if (GNUNET_OK != 141 GNUNET_JSON_parse (value, 142 spec, 143 NULL, NULL)) 144 { 145 GNUNET_break_op (0); 146 return GNUNET_SYSERR; 147 } 148 } 149 json_array_foreach (jbuckets, index, value) { 150 struct TALER_MERCHANT_GetPrivateStatisticsCounterByBucket *jb 151 = &resp_buckets[index]; 152 struct GNUNET_JSON_Specification spec[] = { 153 GNUNET_JSON_spec_timestamp ("start_time", 154 &jb->start_time), 155 GNUNET_JSON_spec_timestamp ("end_time", 156 &jb->end_time), 157 GNUNET_JSON_spec_string ("range", 158 &jb->range), 159 GNUNET_JSON_spec_uint64 ("cumulative_counter", 160 &jb->cumulative_counter), 161 GNUNET_JSON_spec_end () 162 }; 163 164 if (GNUNET_OK != 165 GNUNET_JSON_parse (value, 166 spec, 167 NULL, NULL)) 168 { 169 GNUNET_break_op (0); 170 return GNUNET_SYSERR; 171 } 172 } 173 { 174 struct TALER_MERCHANT_GetPrivateStatisticsCounterResponse scgr = { 175 .hr.http_status = MHD_HTTP_OK, 176 .hr.reply = json, 177 .details.ok.buckets_length = resp_buckets_len, 178 .details.ok.buckets = resp_buckets, 179 .details.ok.buckets_description = buckets_description, 180 .details.ok.intervals_length = resp_intervals_len, 181 .details.ok.intervals = resp_intervals, 182 .details.ok.intervals_description = intervals_description, 183 }; 184 sch->cb (sch->cb_cls, 185 &scgr); 186 sch->cb = NULL; 187 } 188 return GNUNET_OK; 189 } 190 } 191 192 193 /** 194 * Function called when we're done processing the 195 * HTTP GET /private/statistics-counter/$SLUG request. 196 * 197 * @param cls the `struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle` 198 * @param response_code HTTP response code, 0 on error 199 * @param response response body, NULL if not in JSON 200 */ 201 static void 202 handle_get_statistics_counter_finished (void *cls, 203 long response_code, 204 const void *response) 205 { 206 struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle *sch = cls; 207 const json_t *json = response; 208 struct TALER_MERCHANT_GetPrivateStatisticsCounterResponse res = { 209 .hr.http_status = (unsigned int) response_code, 210 .hr.reply = json 211 }; 212 213 sch->job = NULL; 214 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 215 "Got /private/statistics-counter/$SLUG response with status code %u\n", 216 (unsigned int) response_code); 217 switch (response_code) 218 { 219 case MHD_HTTP_OK: 220 { 221 const json_t *buckets; 222 const json_t *intervals; 223 const char *buckets_description = NULL; 224 const char *intervals_description = NULL; 225 struct GNUNET_JSON_Specification spec[] = { 226 GNUNET_JSON_spec_array_const ("buckets", 227 &buckets), 228 GNUNET_JSON_spec_mark_optional ( 229 GNUNET_JSON_spec_string ("buckets_description", 230 &buckets_description), 231 NULL), 232 GNUNET_JSON_spec_array_const ("intervals", 233 &intervals), 234 GNUNET_JSON_spec_mark_optional ( 235 GNUNET_JSON_spec_string ("intervals_description", 236 &intervals_description), 237 NULL), 238 GNUNET_JSON_spec_end () 239 }; 240 241 if (GNUNET_OK != 242 GNUNET_JSON_parse (json, 243 spec, 244 NULL, NULL)) 245 { 246 res.hr.http_status = 0; 247 res.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 248 break; 249 } 250 if (GNUNET_OK == 251 parse_intervals_and_buckets (json, 252 buckets, 253 buckets_description, 254 intervals, 255 intervals_description, 256 sch)) 257 { 258 TALER_MERCHANT_get_private_statistics_counter_cancel (sch); 259 return; 260 } 261 res.hr.http_status = 0; 262 res.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 263 break; 264 } 265 case MHD_HTTP_UNAUTHORIZED: 266 res.hr.ec = TALER_JSON_get_error_code (json); 267 res.hr.hint = TALER_JSON_get_error_hint (json); 268 break; 269 case MHD_HTTP_NOT_FOUND: 270 res.hr.ec = TALER_JSON_get_error_code (json); 271 res.hr.hint = TALER_JSON_get_error_hint (json); 272 break; 273 default: 274 res.hr.ec = TALER_JSON_get_error_code (json); 275 res.hr.hint = TALER_JSON_get_error_hint (json); 276 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 277 "Unexpected response code %u/%d\n", 278 (unsigned int) response_code, 279 (int) res.hr.ec); 280 break; 281 } 282 sch->cb (sch->cb_cls, 283 &res); 284 TALER_MERCHANT_get_private_statistics_counter_cancel (sch); 285 } 286 287 288 struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle * 289 TALER_MERCHANT_get_private_statistics_counter_create ( 290 struct GNUNET_CURL_Context *ctx, 291 const char *url, 292 const char *slug) 293 { 294 struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle *sch; 295 296 sch = GNUNET_new (struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle); 297 sch->ctx = ctx; 298 sch->base_url = GNUNET_strdup (url); 299 sch->slug = GNUNET_strdup (slug); 300 sch->type_set = false; 301 return sch; 302 } 303 304 305 enum GNUNET_GenericReturnValue 306 TALER_MERCHANT_get_private_statistics_counter_set_options_ ( 307 struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle *sch, 308 unsigned int num_options, 309 const struct TALER_MERCHANT_GetPrivateStatisticsCounterOptionValue *options) 310 { 311 for (unsigned int i = 0; i < num_options; i++) 312 { 313 switch (options[i].option) 314 { 315 case TALER_MERCHANT_GET_PRIVATE_STATISTICS_COUNTER_OPTION_END: 316 return GNUNET_OK; 317 case TALER_MERCHANT_GET_PRIVATE_STATISTICS_COUNTER_OPTION_TYPE: 318 sch->stype = options[i].details.type; 319 sch->type_set = true; 320 break; 321 default: 322 GNUNET_break (0); 323 return GNUNET_SYSERR; 324 } 325 } 326 return GNUNET_OK; 327 } 328 329 330 enum TALER_ErrorCode 331 TALER_MERCHANT_get_private_statistics_counter_start ( 332 struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle *sch, 333 TALER_MERCHANT_GetPrivateStatisticsCounterCallback cb, 334 TALER_MERCHANT_GET_PRIVATE_STATISTICS_COUNTER_RESULT_CLOSURE *cb_cls) 335 { 336 CURL *eh; 337 338 sch->cb = cb; 339 sch->cb_cls = cb_cls; 340 { 341 const char *filter = NULL; 342 char *path; 343 344 if (sch->type_set) 345 { 346 switch (sch->stype) 347 { 348 case TALER_MERCHANT_STATISTICS_BY_BUCKET: 349 filter = "bucket"; 350 break; 351 case TALER_MERCHANT_STATISTICS_BY_INTERVAL: 352 filter = "interval"; 353 break; 354 case TALER_MERCHANT_STATISTICS_ALL: 355 filter = NULL; 356 break; 357 } 358 } 359 GNUNET_asprintf (&path, 360 "private/statistics-counter/%s", 361 sch->slug); 362 sch->url = TALER_url_join (sch->base_url, 363 path, 364 "by", 365 filter, 366 NULL); 367 GNUNET_free (path); 368 } 369 if (NULL == sch->url) 370 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 371 eh = TALER_MERCHANT_curl_easy_get_ (sch->url); 372 if (NULL == eh) 373 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 374 sch->job = GNUNET_CURL_job_add (sch->ctx, 375 eh, 376 &handle_get_statistics_counter_finished, 377 sch); 378 if (NULL == sch->job) 379 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 380 return TALER_EC_NONE; 381 } 382 383 384 void 385 TALER_MERCHANT_get_private_statistics_counter_cancel ( 386 struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle *sch) 387 { 388 if (NULL != sch->job) 389 { 390 GNUNET_CURL_job_cancel (sch->job); 391 sch->job = NULL; 392 } 393 GNUNET_free (sch->slug); 394 GNUNET_free (sch->url); 395 GNUNET_free (sch->base_url); 396 GNUNET_free (sch); 397 } 398 399 400 /* end of merchant_api_get-private-statistics-counter-SLUG-new.c */