]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib: lib-event - Add support for string lists
authorAki Tuomi <aki.tuomi@open-xchange.com>
Tue, 15 Dec 2020 07:17:57 +0000 (09:17 +0200)
committeraki.tuomi <aki.tuomi@open-xchange.com>
Wed, 29 Sep 2021 10:09:58 +0000 (10:09 +0000)
Provide API to create string lists. These are particularly
useful if you need to provide list of causes for an event,
such as why some mail was opened.

src/lib/Makefile.am
src/lib/event-filter.c
src/lib/lib-event.c
src/lib/lib-event.h
src/lib/test-event-filter.c
src/lib/test-event-flatten.c
src/lib/test-lib-event.c [new file with mode: 0644]
src/lib/test-lib.inc
src/stats/event-exporter-fmt-json.c
src/stats/event-exporter-fmt-tab-text.c
src/stats/stats-metrics.c

index 2d2fcdf1ef9b3c885e3b21026918736e59d53345..e8a4ccb83d2b7b45c5f0bae14a0e03c7b434b7fd 100644 (file)
@@ -420,6 +420,7 @@ test_lib_SOURCES = \
        test-istream-unix.c \
        test-json-parser.c \
        test-json-tree.c \
+       test-lib-event.c \
        test-lib-signals.c \
        test-llist.c \
        test-log-throttle.c \
index 88a998a6ee66897b17061f5baee7db0cf0b51ee0..5574f2bd2efba11c8531afeb6ad3e68788f99ff0 100644 (file)
@@ -472,6 +472,54 @@ event_has_category(struct event *event, struct event_filter_node *node,
        return FALSE;
 }
 
+static bool
+event_match_strlist_recursive(struct event *event,
+                             const struct event_field *wanted_field,
+                             bool use_strcmp, bool *seen)
+{
+       const char *wanted_value = wanted_field->value.str;
+       const struct event_field *field;
+       const char *value;
+       bool match;
+
+       if (event == NULL)
+               return FALSE;
+
+       field = event_find_field_nonrecursive(event, wanted_field->key);
+       if (field != NULL) {
+               i_assert(field->value_type == EVENT_FIELD_VALUE_TYPE_STRLIST);
+               array_foreach_elem(&field->value.strlist, value) {
+                       *seen = TRUE;
+                       match = use_strcmp ? strcmp(value, wanted_value) == 0 :
+                               wildcard_match_icase(value, wanted_value);
+                       if (match)
+                               return TRUE;
+               }
+       }
+       return event_match_strlist_recursive(event->parent, wanted_field,
+                                            use_strcmp, seen);
+}
+
+static bool
+event_match_strlist(struct event *event, const struct event_field *wanted_field,
+                   bool use_strcmp)
+{
+       bool seen = FALSE;
+
+       if (event_match_strlist_recursive(event, wanted_field,
+                                         use_strcmp, &seen))
+               return TRUE;
+       if (event_match_strlist_recursive(event_get_global(),
+                                         wanted_field, use_strcmp, &seen))
+               return TRUE;
+       if (wanted_field->value.str[0] == '\0' && !seen) {
+               /* strlist="" matches nonexistent strlist */
+               return TRUE;
+       }
+       return FALSE;
+
+}
+
 static bool
 event_match_field(struct event *event, const struct event_field *wanted_field,
                  enum event_filter_node_op op, bool use_strcmp)
@@ -535,6 +583,12 @@ event_match_field(struct event *event, const struct event_field *wanted_field,
        case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
                /* there's no point to support matching exact timestamps */
                return FALSE;
+       case EVENT_FIELD_VALUE_TYPE_STRLIST:
+               /* check if the value is (or is not) on the list,
+                  only string matching makes sense here. */
+               if (op != EVENT_FILTER_OP_CMP_EQ)
+                       return FALSE;
+               return event_match_strlist(event, wanted_field, use_strcmp);
        }
        i_unreached();
 }
index 31a4b39410dd2f83ceaa6e0b701c650e4ec7c699..26ce4aafc0befdec7cf2825ab053df4b0c81962f 100644 (file)
@@ -20,6 +20,7 @@ enum event_code {
        EVENT_CODE_FIELD_INTMAX         = 'I',
        EVENT_CODE_FIELD_STR            = 'S',
        EVENT_CODE_FIELD_TIMEVAL        = 'T',
+       EVENT_CODE_FIELD_STRLIST        = 'L',
 };
 
 /* Internal event category state.
@@ -153,6 +154,9 @@ void event_copy_categories(struct event *to, struct event *from)
 void event_copy_fields(struct event *to, struct event *from)
 {
        const struct event_field *fld;
+       unsigned int count;
+       const char *const *values;
+
        if (!array_is_created(&from->fields))
                return;
        array_foreach(&from->fields, fld) {
@@ -166,6 +170,11 @@ void event_copy_fields(struct event *to, struct event *from)
                case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
                        event_add_timeval(to, fld->key, &fld->value.timeval);
                        break;
+               case EVENT_FIELD_VALUE_TYPE_STRLIST:
+                       values = array_get(&fld->value.strlist, &count);
+                       for (unsigned int i = 0; i < count; i++)
+                               event_strlist_append(to, fld->key, values[i]);
+                       break;
                default:
                        break;
                }
@@ -829,6 +838,33 @@ event_find_field_recursive(const struct event *event, const char *key)
        return NULL;
 }
 
+static void
+event_get_recursive_strlist(const struct event *event, pool_t pool,
+                           const char *key, ARRAY_TYPE(const_string) *dest)
+{
+       const struct event_field *field;
+       const char *str;
+
+       if (event == NULL)
+               return;
+
+       field = event_find_field_nonrecursive(event, key);
+       if (field != NULL) {
+               if (field->value_type != EVENT_FIELD_VALUE_TYPE_STRLIST) {
+                       /* Value type unexpectedly changed. Stop recursing. */
+                       return;
+               }
+               array_foreach_elem(&field->value.strlist, str) {
+                       if (array_lsearch(dest, &str, i_strcmp_p) == NULL) {
+                               if (pool != NULL)
+                                       str = p_strdup(pool, str);
+                               array_push_back(dest, &str);
+                       }
+               }
+       }
+       event_get_recursive_strlist(event->parent, pool, key, dest);
+}
+
 const char *
 event_find_field_recursive_str(const struct event *event, const char *key)
 {
@@ -847,6 +883,14 @@ event_find_field_recursive_str(const struct event *event, const char *key)
                return t_strdup_printf("%"PRIdTIME_T".%u",
                        field->value.timeval.tv_sec,
                        (unsigned int)field->value.timeval.tv_usec);
+       case EVENT_FIELD_VALUE_TYPE_STRLIST: {
+               ARRAY_TYPE(const_string) list;
+               t_array_init(&list, 8);
+               /* This is a bit different, because it needs to be merging
+                  all of the parent events' lists together. */
+               event_get_recursive_strlist(event, NULL, key, &list);
+               return t_array_const_string_join(&list, ",");
+       }
        }
        i_unreached();
 }
@@ -884,6 +928,53 @@ event_add_str(struct event *event, const char *key, const char *value)
        return event;
 }
 
+struct event *
+event_strlist_append(struct event *event, const char *key, const char *value)
+{
+       struct event_field *field = event_get_field(event, key);
+
+       if (field->value_type != EVENT_FIELD_VALUE_TYPE_STRLIST)
+               i_zero(&field->value);
+       field->value_type = EVENT_FIELD_VALUE_TYPE_STRLIST;
+
+       if (!array_is_created(&field->value.strlist))
+               p_array_init(&field->value.strlist, event->pool, 1);
+
+       /* lets not add empty values there though */
+       if (value == NULL)
+               return event;
+
+       const char *str = p_strdup(event->pool, value);
+       if (array_lsearch(&field->value.strlist, &str, i_strcmp_p) == NULL)
+               array_push_back(&field->value.strlist, &str);
+       return event;
+}
+
+struct event *
+event_strlist_replace(struct event *event, const char *key,
+                     const char *const *values, unsigned int count)
+{
+       struct event_field *field = event_get_field(event, key);
+       i_zero(&field->value);
+       field->value_type = EVENT_FIELD_VALUE_TYPE_STRLIST;
+
+       for (unsigned int i = 0; i < count; i++)
+               event_strlist_append(event, key, values[i]);
+       return event;
+}
+
+struct event *
+event_strlist_copy_recursive(struct event *dest, const struct event *src,
+                            const char *key)
+{
+       event_strlist_append(dest, key, NULL);
+       struct event_field *field = event_get_field(dest, key);
+       i_assert(field != NULL);
+       event_get_recursive_strlist(src, dest->pool, key,
+                                   &field->value.strlist);
+       return dest;
+}
+
 struct event *
 event_add_int(struct event *event, const char *key, intmax_t num)
 {
@@ -1058,6 +1149,18 @@ event_export_field_value(string_t *dest, const struct event_field *field)
                            field->value.timeval.tv_sec,
                            (unsigned int)field->value.timeval.tv_usec);
                break;
+       case EVENT_FIELD_VALUE_TYPE_STRLIST: {
+               unsigned int count;
+               const char *const *strlist =
+                       array_get(&field->value.strlist, &count);
+               str_append_c(dest, EVENT_CODE_FIELD_STRLIST);
+               str_append_tabescaped(dest, field->key);
+               str_printfa(dest, "\t%u", count);
+               for (unsigned int i = 0; i < count; i++) {
+                       str_append_c(dest, '\t');
+                       str_append_tabescaped(dest, strlist[i]);
+               }
+       }
        }
 }
 
@@ -1137,6 +1240,33 @@ static bool event_import_tv(const char *arg_secs, const char *arg_usecs,
        return TRUE;
 }
 
+static bool
+event_import_strlist(struct event *event, struct event_field *field,
+                    const char *const **_args, const char **error_r)
+{
+       const char *const *args = *_args;
+       unsigned int count, i;
+
+       field->value_type = EVENT_FIELD_VALUE_TYPE_STRLIST;
+       if (str_to_uint(args[0], &count) < 0) {
+               *error_r = t_strdup_printf("Field '%s' has invalid count: '%s'",
+                                          field->key, args[0]);
+               return FALSE;
+       }
+       p_array_init(&field->value.strlist, event->pool, count);
+       for (i = 1; i <= count && args[i] != NULL; i++) {
+               const char *str = p_strdup(event->pool, args[i]);
+               array_push_back(&field->value.strlist, &str);
+       }
+       if (i < count) {
+               *error_r = t_strdup_printf("Field '%s' has too few values",
+                                          field->key);
+               return FALSE;
+       }
+       *_args += count;
+       return TRUE;
+}
+
 static bool
 event_import_field(struct event *event, enum event_code code, const char *arg,
                   const char *const **_args, const char **error_r)
@@ -1183,6 +1313,10 @@ event_import_field(struct event *event, enum event_code code, const char *arg,
                }
                args++;
                break;
+       case EVENT_CODE_FIELD_STRLIST:
+               if (!event_import_strlist(event, field, &args, error_r))
+                       return FALSE;
+               break;
        default:
                i_unreached();
        }
@@ -1248,6 +1382,7 @@ event_import_arg(struct event *event, const char *const **_args,
        }
        case EVENT_CODE_FIELD_INTMAX:
        case EVENT_CODE_FIELD_STR:
+       case EVENT_CODE_FIELD_STRLIST:
        case EVENT_CODE_FIELD_TIMEVAL: {
                args++;
                if (!event_import_field(event, code, arg, &args, error_r))
@@ -1396,6 +1531,21 @@ event_passthrough_add_str(const char *key, const char *value)
        return event_last_passthrough;
 }
 
+static struct event_passthrough *
+event_passthrough_strlist_append(const char *key, const char *value)
+{
+        event_strlist_append(last_passthrough_event(), key, value);
+        return event_last_passthrough;
+}
+
+static struct event_passthrough *
+event_passthrough_strlist_replace(const char *key, const char *const *values,
+                                 unsigned int count)
+{
+        event_strlist_replace(last_passthrough_event(), key, values, count);
+        return event_last_passthrough;
+}
+
 static struct event_passthrough *
 event_passthrough_add_int(const char *key, intmax_t num)
 {
@@ -1444,6 +1594,8 @@ const struct event_passthrough event_passthrough_vfuncs = {
        .add_int = event_passthrough_add_int,
        .add_timeval = event_passthrough_add_timeval,
        .inc_int = event_passthrough_inc_int,
+       .strlist_append = event_passthrough_strlist_append,
+       .strlist_replace = event_passthrough_strlist_replace,
        .clear_field = event_passthrough_clear_field,
        .event = event_passthrough_event,
 };
index 8609e74ef28e853d35eb60a20c79f707f672dc3f..dc27a79d8e555ec3a8bdbce98a9b6ae4f5006dfe 100644 (file)
@@ -29,6 +29,7 @@ enum event_field_value_type {
        EVENT_FIELD_VALUE_TYPE_STR,
        EVENT_FIELD_VALUE_TYPE_INTMAX,
        EVENT_FIELD_VALUE_TYPE_TIMEVAL,
+       EVENT_FIELD_VALUE_TYPE_STRLIST,
 };
 
 struct event_field {
@@ -38,6 +39,7 @@ struct event_field {
                const char *str;
                intmax_t intmax;
                struct timeval timeval;
+               ARRAY_TYPE(const_string) strlist;
        } value;
 };
 
@@ -82,6 +84,12 @@ struct event_passthrough {
        struct event_passthrough *
                (*inc_int)(const char *key, intmax_t num);
 
+       struct event_passthrough *
+               (*strlist_append)(const char *key, const char *value);
+       struct event_passthrough *
+               (*strlist_replace)(const char *key, const char *const *value,
+                                  unsigned int count);
+
        struct event_passthrough *
                (*clear_field)(const char *key);
 
@@ -284,6 +292,20 @@ event_inc_int(struct event *event, const char *key, intmax_t num);
 struct event *
 event_add_timeval(struct event *event, const char *key,
                  const struct timeval *tv);
+/* Append new value to list. If the key is not a list, it will
+   be cleared first. NULL values are ignored. Duplicate values are ignored. */
+struct event *
+event_strlist_append(struct event *event, const char *key, const char *value);
+/* Replace value with this strlist. */
+struct event *
+event_strlist_replace(struct event *event, const char *key,
+                     const char *const *value, unsigned int count);
+/* Copy the string list from src and its parents to dest. This can be especially
+   useful to copy the current global events' reason_codes to a more permanent
+   (e.g. async) event that can exist after the global events are popped out. */
+struct event *
+event_strlist_copy_recursive(struct event *dest, const struct event *src,
+                            const char *key);
 /* Same as event_add_str/int(), but do it via event_field struct. The fields
    terminates with key=NULL. Returns the event parameter. */
 struct event *
index 6c7d42b30456f3f928166473817be65940e17e97..ddb284f87ae11e9bd340405aac00ea3836635f34 100644 (file)
@@ -180,10 +180,104 @@ static void test_event_filter_parent_category_match(void)
        test_end();
 }
 
+static void test_event_filter_strlist(void)
+{
+       struct event_filter *filter;
+       const struct failure_context failure_ctx = {
+               .type = LOG_TYPE_DEBUG
+       };
+
+       test_begin("event filter: match string list");
+
+       struct event *e = event_create(NULL);
+
+       filter = event_filter_create();
+       /* should match empty list */
+       event_filter_parse("abc=\"\"", filter, NULL);
+       test_assert(event_filter_match(filter, e, &failure_ctx));
+       /* should still be empty */
+       event_strlist_append(e, "abc", NULL);
+       test_assert(event_filter_match(filter, e, &failure_ctx));
+
+       /* should not match non-empty list */
+       event_strlist_append(e, "abc", "one");
+       test_assert(!event_filter_match(filter, e, &failure_ctx));
+       event_filter_unref(&filter);
+
+       /* should match non-empty list that has value 'one' */
+       filter = event_filter_create();
+       event_strlist_append(e, "abc", "two");
+       event_filter_parse("abc=one", filter, NULL);
+       test_assert(event_filter_match(filter, e, &failure_ctx));
+       event_filter_unref(&filter);
+
+       /* should match non-empty list that has no value 'three' */
+       filter = event_filter_create();
+       event_filter_parse("abc=one AND NOT abc=three", filter, NULL);
+       test_assert(event_filter_match(filter, e, &failure_ctx));
+       event_filter_unref(&filter);
+
+       event_unref(&e);
+       test_end();
+}
+
+static void test_event_filter_strlist_recursive(void)
+{
+       struct event_filter *filter;
+       const struct failure_context failure_ctx = {
+               .type = LOG_TYPE_DEBUG
+       };
+
+       test_begin("event filter: match string list - recursive");
+
+       struct event *parent = event_create(NULL);
+       struct event *e = event_create(parent);
+
+       /* empty filter: parent is non-empty */
+       filter = event_filter_create();
+       event_filter_parse("list1=\"\"", filter, NULL);
+       test_assert(event_filter_match(filter, e, &failure_ctx));
+       event_strlist_append(parent, "list1", "foo");
+       test_assert(!event_filter_match(filter, e, &failure_ctx));
+       event_filter_unref(&filter);
+
+       /* matching filter: matches parent */
+       filter = event_filter_create();
+       event_filter_parse("list2=parent", filter, NULL);
+       /* empty: */
+       test_assert(!event_filter_match(filter, e, &failure_ctx));
+       /* set parent but no child: */
+       event_strlist_append(parent, "list2", "parent");
+       test_assert(event_filter_match(filter, e, &failure_ctx));
+       /* set child to non-matching: */
+       event_strlist_append(e, "list2", "child");
+       test_assert(event_filter_match(filter, e, &failure_ctx));
+       event_filter_unref(&filter);
+
+       /* matching filter: matches child */
+       filter = event_filter_create();
+       event_filter_parse("list3=child", filter, NULL);
+       /* empty: */
+       test_assert(!event_filter_match(filter, e, &failure_ctx));
+       /* set child but no parent: */
+       event_strlist_append(e, "list3", "child");
+       test_assert(event_filter_match(filter, e, &failure_ctx));
+       /* set parent to non-matching: */
+       event_strlist_append(e, "list3", "parent");
+       test_assert(event_filter_match(filter, e, &failure_ctx));
+       event_filter_unref(&filter);
+
+       event_unref(&e);
+       event_unref(&parent);
+       test_end();
+}
+
 void test_event_filter(void)
 {
        test_event_filter_override_parent_fields();
        test_event_filter_clear_parent_fields();
        test_event_filter_inc_int();
        test_event_filter_parent_category_match();
+       test_event_filter_strlist();
+       test_event_filter_strlist_recursive();
 }
index aa1187233925bcdcff472a568b71be711873f2e4..c3a5b3cc87e661f3a5285dc76d9c7aa70d1b9df6 100644 (file)
@@ -5,6 +5,8 @@
 #include "time-util.h"
 #include "lib-event-private.h"
 #include "failures-private.h"
+#include "array.h"
+#include "str.h"
 
 #define CHECK_FLATTEN_SAME(e) \
        check_event_same(event_flatten(e), (e))
@@ -35,6 +37,7 @@ static void check_event_diff_fields(const struct event_field *got, unsigned int
                                    const struct event_field *exp, unsigned int nexp)
 {
        unsigned int i;
+       const char *got_str;
 
        test_assert(ngot == nexp);
 
@@ -56,6 +59,10 @@ static void check_event_diff_fields(const struct event_field *got, unsigned int
                        test_assert(timeval_cmp(&exp[i].value.timeval,
                                                &got[i].value.timeval) == 0);
                        break;
+               case EVENT_FIELD_VALUE_TYPE_STRLIST:
+                       got_str = t_array_const_string_join(&got[i].value.strlist, ",");
+                       test_assert_strcmp(exp[i].value.str, got_str);
+                       break;
                }
        }
 }
@@ -191,11 +198,47 @@ static void test_event_flatten_one_parent(void)
                        }
                },
        };
+       static struct event_field exp_1str1int1strlist[3] = {
+               {
+                       .key = "abc",
+                       .value_type = EVENT_FIELD_VALUE_TYPE_STR,
+                       .value = {
+                               .str = "foo",
+                               .intmax = 0,
+                               .timeval = {0,0},
+                       }
+               },
+               {
+                       .key = "def",
+                       .value_type = EVENT_FIELD_VALUE_TYPE_INTMAX,
+                       .value = {
+                               .intmax = 49,
+                               .str = NULL,
+                               .timeval = {0,0},
+                       }
+               },
+               {
+                       .key = "cba",
+                       .value_type = EVENT_FIELD_VALUE_TYPE_STRLIST,
+                       .value = {
+                               .str = "one,two,three",
+                       },
+               },
+       };
+
        struct event *parent;
        struct event *e;
 
        test_begin("event flatten: one parent");
 
+       t_array_init(&exp_1str1int1strlist[0].value.strlist, 3);
+       const char *str = "one";
+       array_push_back(&exp_1str1int1strlist[0].value.strlist, &str);
+       str = "two";
+       array_push_back(&exp_1str1int1strlist[0].value.strlist, &str);
+       str = "three";
+       array_push_back(&exp_1str1int1strlist[0].value.strlist, &str);
+
        parent = event_create(NULL);
 
        e = event_create(parent);
@@ -217,6 +260,11 @@ static void test_event_flatten_one_parent(void)
        event_add_category(e, &cats[1]);
        CHECK_FLATTEN_DIFF(e, exp_2cat, 2, exp_1str1int, 2);
 
+       event_strlist_append(e, "cba", "one");
+       event_strlist_append(e, "cba", "two");
+       event_strlist_append(e, "cba", "three");
+       CHECK_FLATTEN_DIFF(e, exp_2cat, 2, exp_1str1int1strlist, 3);
+
        event_unref(&e);
        event_unref(&parent);
 
@@ -290,9 +338,54 @@ static void test_event_flatten_override_parent_field(void)
        test_end();
 }
 
+static void test_event_strlist_flatten(void)
+{
+       test_begin("event flatten: strlist");
+       struct event *l1 = event_create(NULL);
+       event_strlist_append(l1, "test", "l3");
+       struct event *l2 = event_create(l1);
+       event_strlist_append(l2, "test", "l1");
+       struct event *l3 = event_create(l2);
+       unsigned int line = __LINE__ - 1;
+       event_strlist_append(l3, "test", "l2");
+
+       string_t *dest = t_str_new(32);
+       struct event *event = event_flatten(l3);
+
+       event_export(event, dest);
+       /* see if it matches .. */
+       const char *reference = t_strdup_printf("%"PRIdTIME_T"\t%zu"
+                                               "\tstest-event-flatten.c"
+                                               "\t%u\tLtest\t3\tl3\tl1\tl2",
+                                       event->tv_created.tv_sec,
+                                       event->tv_created.tv_usec,
+                                       line);
+       test_assert_strcmp(str_c(dest), reference);
+
+       /* these should not end up duplicated */
+       event_strlist_append(event, "test", "l1");
+       event_strlist_append(event, "test", "l2");
+       event_strlist_append(event, "test", "l3");
+
+       /* and export should look the same */
+       str_truncate(dest, 0);
+       event_export(event, dest);
+       test_assert_strcmp(str_c(dest), reference);
+
+       event_unref(&event);
+
+       /* export event */
+       event_unref(&l3);
+       event_unref(&l2);
+       event_unref(&l1);
+
+       test_end();
+}
+
 void test_event_flatten(void)
 {
        test_event_flatten_no_parent();
        test_event_flatten_one_parent();
        test_event_flatten_override_parent_field();
+       test_event_strlist_flatten();
 }
diff --git a/src/lib/test-lib-event.c b/src/lib/test-lib-event.c
new file mode 100644 (file)
index 0000000..b0d9532
--- /dev/null
@@ -0,0 +1,35 @@
+/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */
+
+#include "test-lib.h"
+
+static void test_event_strlist(void)
+{
+       test_begin("event strlist");
+       struct event *e1 = event_create(NULL);
+       event_strlist_append(e1, "key", "s1");
+       event_strlist_append(e1, "key", "s2");
+       struct event *e2 = event_create(e1);
+       event_strlist_append(e2, "key", "s3");
+       event_strlist_append(e2, "key", "s2");
+
+       test_assert_strcmp(event_find_field_recursive_str(e1, "key"), "s1,s2");
+       test_assert_strcmp(event_find_field_recursive_str(e2, "key"), "s3,s2,s1");
+
+       const char *new_strlist[] = { "new1", "new2", "new2", "s2" };
+       event_strlist_replace(e2, "key", new_strlist, N_ELEMENTS(new_strlist));
+       test_assert_strcmp(event_find_field_recursive_str(e2, "key"), "new1,new2,s2,s1");
+
+       struct event *e3 = event_create(NULL);
+       event_strlist_copy_recursive(e3, e2, "key");
+       test_assert_strcmp(event_find_field_recursive_str(e3, "key"), "new1,new2,s2,s1");
+       event_unref(&e3);
+
+       event_unref(&e1);
+       event_unref(&e2);
+       test_end();
+}
+
+void test_lib_event(void)
+{
+       test_event_strlist();
+}
index 549c7bd4e15336d22cfeb0995ce47d364656af25..462c490dff868f421f7fd58e8a6364eef5b446cd 100644 (file)
@@ -61,6 +61,7 @@ TEST(test_istream_try)
 TEST(test_istream_unix)
 TEST(test_json_parser)
 TEST(test_json_tree)
+TEST(test_lib_event)
 TEST(test_lib_signals)
 TEST(test_llist)
 TEST(test_log_throttle)
index 76a1ed2e6956c21c48993da06cac24bc6b0adabf..fe70a2dd607eae72951e404b80b76040b5a4a79d 100644 (file)
@@ -68,6 +68,8 @@ static void append_field_value(string_t *dest, const struct event_field *field,
                append_time(dest, &field->value.timeval,
                            info->exporter->time_format);
                break;
+       case EVENT_FIELD_VALUE_TYPE_STRLIST:
+               break;
        }
 }
 
index fa408a19b5111f4ee179613bc5d48f6a64db4139..415bd661a783da555ba45b564b9b1a7ab7b97380 100644 (file)
@@ -57,6 +57,8 @@ static void append_field_value(string_t *dest, const struct event_field *field,
                append_time(dest, &field->value.timeval,
                            info->exporter->time_format);
                break;
+       case EVENT_FIELD_VALUE_TYPE_STRLIST:
+               break;
        }
 }
 
index 5ca3038af1334f24fffc23221e7dc3d0352a0104..a13c1d8f8d4b560f0005b971c8f2ccc1115cbed4 100644 (file)
@@ -359,6 +359,8 @@ stats_metric_group_by_discrete(const struct event_field *field,
                return TRUE;
        case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
                return FALSE;
+       case EVENT_FIELD_VALUE_TYPE_STRLIST:
+               return FALSE;
        }
 
        i_unreached();
@@ -373,6 +375,7 @@ stats_metric_group_by_quantized(const struct event_field *field,
        switch (field->value_type) {
        case EVENT_FIELD_VALUE_TYPE_STR:
        case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
+       case EVENT_FIELD_VALUE_TYPE_STRLIST:
                return FALSE;
        case EVENT_FIELD_VALUE_TYPE_INTMAX:
                break;
@@ -406,6 +409,7 @@ stats_metric_group_by_quantized_label(const struct event_field *field,
        switch (field->value_type) {
        case EVENT_FIELD_VALUE_TYPE_STR:
        case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
+       case EVENT_FIELD_VALUE_TYPE_STRLIST:
                i_unreached();
        case EVENT_FIELD_VALUE_TYPE_INTMAX:
                break;
@@ -524,6 +528,7 @@ stats_metric_event_field(struct event *event, const char *fieldname,
 
        switch (field->value_type) {
        case EVENT_FIELD_VALUE_TYPE_STR:
+       case EVENT_FIELD_VALUE_TYPE_STRLIST:
                break;
        case EVENT_FIELD_VALUE_TYPE_INTMAX:
                num = field->value.intmax;