diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ab43ddcf..9787f0e88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,11 @@ **Breaking changes**: - Return type of `sentry_capture_minidump()` and `sentry_capture_minidump_n()` changed from `void` to `sentry_uuid_t` to retrieve the event-id for a successful minidump upload. ([#1138](https://github.com/getsentry/sentry-native/pull/1138)) - + +**Features**: + +- Add object item iterators. ([#1143](https://github.com/getsentry/sentry-native/pull/1143)) + **Fixes**: - Ensure that `sentry_capture_minidump()` fails if the provided minidump path cannot be attached, instead of sending a crash event without minidump. ([#1138](https://github.com/getsentry/sentry-native/pull/1138)) diff --git a/include/sentry.h b/include/sentry.h index feabc47a6..040e99655 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -244,6 +244,54 @@ SENTRY_API int sentry_value_remove_by_key(sentry_value_t value, const char *k); SENTRY_API int sentry_value_remove_by_key_n( sentry_value_t value, const char *k, size_t k_len); +/** + * Object item iterator. + * + * It's used to iterate over the key-value pairs of an object. + */ +struct sentry_item_iter_s; +typedef struct sentry_item_iter_s sentry_item_iter_t; + +/** + * Creates a new object item iterator. + * + * Returns `NULL` if the given value is not an object. + */ +SENTRY_API sentry_item_iter_t *sentry_value_new_item_iter(sentry_value_t value); + +/** + * Advances the item iterator to the next item. + */ +SENTRY_API void sentry_value_item_iter_next(sentry_item_iter_t *item_iter); + +/** + * Returns true if the item iterator is valid. + */ +SENTRY_API int sentry_value_item_iter_valid(sentry_item_iter_t *item_iter); + +/** + * Returns the key to the current item. + * + * Returns a `NULL` pointer if the iterator is invalid. + */ +SENTRY_API const char *sentry_value_item_iter_get_key( + sentry_item_iter_t *item_iter); + +/** + * Returns the value of the current item. + * + * Returns a null value if the iterator is invalid. + */ +SENTRY_API sentry_value_t sentry_value_item_iter_get_value( + sentry_item_iter_t *item_iter); + +/** + * Erases the current item and advances the iterator to the next item. + * + * Returns 1 if the iterator is exhausted or the object is frozen. + */ +SENTRY_API int sentry_value_item_iter_erase(sentry_item_iter_t *item_iter); + /** * Appends a value to a list. * diff --git a/src/sentry_value.c b/src/sentry_value.c index 1258ee818..25c1377b5 100644 --- a/src/sentry_value.c +++ b/src/sentry_value.c @@ -778,6 +778,75 @@ sentry_value_get_by_key_owned(sentry_value_t value, const char *k) return rv; } +struct sentry_item_iter_s { + size_t *len; // Pointer to length! + obj_pair_t *pairs; + size_t index; + int frozen; +}; + +sentry_item_iter_t * +sentry_value_new_item_iter(sentry_value_t value) +{ + const thing_t *thing = value_as_thing(value); + if (thing && thing_get_type(thing) == THING_TYPE_OBJECT) { + obj_t *o = thing->payload._ptr; + sentry_item_iter_t *item_iter = SENTRY_MAKE(sentry_item_iter_t); + item_iter->len = &o->len; + item_iter->pairs = o->pairs; + item_iter->index = 0; + item_iter->frozen = thing_is_frozen(thing); + return item_iter; + } + return NULL; +} + +void +sentry_value_item_iter_next(sentry_item_iter_t *item_iter) +{ + item_iter->index++; +} + +const char * +sentry_value_item_iter_get_key(sentry_item_iter_t *item_iter) +{ + if (item_iter->index >= *item_iter->len) { + return NULL; + } + return item_iter->pairs[item_iter->index].k; +} + +sentry_value_t +sentry_value_item_iter_get_value(sentry_item_iter_t *item_iter) +{ + if (item_iter->index >= *item_iter->len) { + return sentry_value_new_null(); + } + return item_iter->pairs[item_iter->index].v; +} + +int +sentry_value_item_iter_valid(sentry_item_iter_t *item_iter) +{ + return item_iter->index < *item_iter->len && item_iter->pairs != NULL; +} + +int +sentry_value_item_iter_erase(sentry_item_iter_t *item_iter) +{ + if (item_iter->frozen || item_iter->index >= *item_iter->len) { + return 1; + } + obj_pair_t *pair = &item_iter->pairs[item_iter->index]; + sentry_free(pair->k); + sentry_value_decref(pair->v); + memmove(item_iter->pairs + item_iter->index, + item_iter->pairs + item_iter->index + 1, + (*item_iter->len - item_iter->index - 1) * sizeof(item_iter->pairs[0])); + (*item_iter->len)--; + return 0; +} + sentry_value_t sentry_value_get_by_index(sentry_value_t value, size_t index) { diff --git a/tests/unit/test_value.c b/tests/unit/test_value.c index 8e738bc1d..88306f88f 100644 --- a/tests/unit/test_value.c +++ b/tests/unit/test_value.c @@ -290,6 +290,94 @@ SENTRY_TEST(value_object) sentry_value_decref(val); } +SENTRY_TEST(value_object_iteration) +{ + sentry_value_t obj = sentry_value_new_object(); + + // Populate. + for (size_t i = 0; i < 10; i++) { + char key[100]; + sprintf(key, "key%d", (int)i); + sentry_value_set_by_key(obj, key, sentry_value_new_int32((int32_t)i)); + } + + // Iterate over items. + { + sentry_item_iter_t *it = sentry_value_new_item_iter(obj); + size_t count = 0; + TEST_CHECK(it != NULL); + for (; sentry_value_item_iter_valid(it); + sentry_value_item_iter_next(it)) { + const char *key = sentry_value_item_iter_get_key(it); + sentry_value_t value = sentry_value_item_iter_get_value(it); + + TEST_CHECK(key != NULL); + TEST_CHECK(sentry_value_get_type(value) == SENTRY_VALUE_TYPE_INT32); + + int32_t key_idx; + sscanf(key, "key%d", &key_idx); + TEST_CHECK_INT_EQUAL(key_idx, sentry_value_as_int32(value)); + + count++; + } + TEST_CHECK_INT_EQUAL(count, 10); + sentry_free(it); + } + + // Erase even-numbered items. + { + sentry_item_iter_t *it = sentry_value_new_item_iter(obj); + TEST_CHECK(it != NULL); + size_t count = 0; + const char *prev_key = ""; + size_t i = 0; + while (sentry_value_item_iter_valid(it)) { + TEST_CHECK( + strcmp(prev_key, sentry_value_item_iter_get_key(it)) != 0); + prev_key = sentry_value_item_iter_get_key(it); + if (i % 2 == 0) { + int err = sentry_value_item_iter_erase(it); + TEST_CHECK_INT_EQUAL(err, 0); + } else { + sentry_value_item_iter_next(it); + count++; + } + i++; + } + TEST_CHECK_INT_EQUAL(sentry_value_get_length(obj), 5); + TEST_CHECK_INT_EQUAL(count, 5); + sentry_free(it); + } + + // Verify if the right items were removed. + { + sentry_item_iter_t *it = sentry_value_new_item_iter(obj); + for (; sentry_value_item_iter_valid(it); + sentry_value_item_iter_next(it)) { + const char *key = sentry_value_item_iter_get_key(it); + int32_t key_idx; + sscanf(key, "key%d", &key_idx); + TEST_CHECK(key_idx % 2 != 0); + } + sentry_free(it); + } + + // Erase the rest of the items. + { + sentry_item_iter_t *it = sentry_value_new_item_iter(obj); + size_t count = 0; + TEST_CHECK(it != NULL); + while (sentry_value_item_iter_erase(it) == 0) { + count++; + } + TEST_CHECK_INT_EQUAL(sentry_value_get_length(obj), 0); + TEST_CHECK_INT_EQUAL(count, 5); + sentry_free(it); + } + + sentry_value_decref(obj); +} + SENTRY_TEST(value_object_merge) { sentry_value_t dst = sentry_value_new_object();