diff --git a/CHANGELOG.md b/CHANGELOG.md index a21fb77ea..6897d20bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Android: Expose app-hang detection through the NDK bindings via `NdkOptions.setEnableAppHangTracking()` and `NdkOptions.setAppHangTimeoutMillis()`. ([#1823](https://github.com/getsentry/sentry-native/pull/1823)) - Windows: add wide-char versions of `sentry_capture_minidump` and `sentry_capture_minidump_n`. ([#1827](https://github.com/getsentry/sentry-native/pull/1827)) +- Add `sentry_update_context` to allow updating already set context objects with new key-value pairs, as well as new values for existing keys. ([#1835](https://github.com/getsentry/sentry-native/pull/1835)) **Fixes**: diff --git a/include/sentry.h b/include/sentry.h index 341d1af10..2c41e9dfd 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -2300,6 +2300,19 @@ SENTRY_API void sentry_scope_set_context( sentry_scope_t *scope, const char *key, sentry_value_t value); SENTRY_API void sentry_scope_set_context_n(sentry_scope_t *scope, const char *key, size_t key_len, sentry_value_t value); +/** + * Updates a context object by merging the provided values into it. + * Keys in `value` take precedence over keys in the context, updating the + * existing values. Keys in the existing context that are not present in `value` + * are preserved. If the context object does not exist yet, it will be created. + */ +SENTRY_API void sentry_update_context(const char *key, sentry_value_t value); +SENTRY_API void sentry_update_context_n( + const char *key, size_t key_len, sentry_value_t value); +SENTRY_API void sentry_scope_update_context( + sentry_scope_t *scope, const char *key, sentry_value_t value); +SENTRY_API void sentry_scope_update_context_n(sentry_scope_t *scope, + const char *key, size_t key_len, sentry_value_t value); /** * Removes the context object with the specified key. diff --git a/src/sentry_core.c b/src/sentry_core.c index 984e50a83..e169f7308 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -1075,6 +1075,22 @@ sentry_set_context_n(const char *key, size_t key_len, sentry_value_t value) } } +void +sentry_update_context(const char *key, sentry_value_t value) +{ + SENTRY_WITH_SCOPE_MUT (scope) { + sentry_scope_update_context(scope, key, value); + } +} + +void +sentry_update_context_n(const char *key, size_t key_len, sentry_value_t value) +{ + SENTRY_WITH_SCOPE_MUT (scope) { + sentry_scope_update_context_n(scope, key, key_len, value); + } +} + void sentry__set_propagation_context(const char *key, sentry_value_t value) { diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 4ea51b60e..1a6f54a55 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -599,6 +599,28 @@ sentry_scope_set_context_n(sentry_scope_t *scope, const char *key, sentry_value_set_by_key_n(scope->contexts, key, key_len, value); } +void +sentry_scope_update_context( + sentry_scope_t *scope, const char *key, sentry_value_t value) +{ + sentry_scope_update_context_n( + scope, key, sentry__guarded_strlen(key), value); +} + +void +sentry_scope_update_context_n(sentry_scope_t *scope, const char *key, + size_t key_len, sentry_value_t value) +{ + sentry_value_t context + = sentry_value_get_by_key_n(scope->contexts, key, key_len); + if (sentry_value_is_null(context)) { + sentry_value_set_by_key_n(scope->contexts, key, key_len, value); + } else { + sentry__value_merge_objects(value, context); + sentry_value_set_by_key_n(scope->contexts, key, key_len, value); + } +} + void sentry__scope_set_fingerprint_va( sentry_scope_t *scope, const char *fingerprint, va_list va) diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index fa74592f2..0ac3951ff 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -100,6 +100,90 @@ SENTRY_TEST(scope_contexts) sentry_close(); } +SENTRY_TEST(scope_update_context) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + // update into empty: context does not exist yet + { + sentry_value_t device = sentry_value_new_object(); + sentry_value_set_by_key( + device, "model", sentry_value_new_string("Xbox Series X")); + sentry_value_set_by_key( + device, "family", sentry_value_new_string("Xbox")); + sentry_update_context("device", device); + + SENTRY_WITH_SCOPE (scope) { + sentry_value_t ctx + = sentry_value_get_by_key(scope->contexts, "device"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(ctx, "model")), + "Xbox Series X"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(ctx, "family")), + "Xbox"); + } + } + + // update should overwrite existing keys + { + sentry_value_t extra = sentry_value_new_object(); + sentry_value_set_by_key(extra, "model", sentry_value_new_string("PC")); + sentry_value_set_by_key( + extra, "cpu_description", sentry_value_new_string("some cpu")); + sentry_update_context("device", extra); + + SENTRY_WITH_SCOPE (scope) { + sentry_value_t ctx + = sentry_value_get_by_key(scope->contexts, "device"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(ctx, "model")), + "PC"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(ctx, "family")), + "Xbox"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string( + sentry_value_get_by_key(ctx, "cpu_description")), + "some cpu"); + } + } + + // scoped update into empty + { + sentry_scope_t *local_scope = sentry_local_scope_new(); + + sentry_value_t os = sentry_value_new_object(); + sentry_value_set_by_key(os, "name", sentry_value_new_string("SteamOS")); + sentry_scope_update_context(local_scope, "os", os); + + sentry_value_t ctx + = sentry_value_get_by_key(local_scope->contexts, "os"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(ctx, "name")), + "SteamOS"); + + // scoped update overwrites existing keys + sentry_value_t os2 = sentry_value_new_object(); + sentry_value_set_by_key(os2, "name", sentry_value_new_string("Linux")); + sentry_value_set_by_key(os2, "version", sentry_value_new_string("6.1")); + sentry_scope_update_context(local_scope, "os", os2); + + ctx = sentry_value_get_by_key(local_scope->contexts, "os"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(ctx, "name")), + "Linux"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(ctx, "version")), + "6.1"); + + sentry__scope_free(local_scope); + } + + sentry_close(); +} + SENTRY_TEST(scope_extra) { SENTRY_TEST_OPTIONS_NEW(options); diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 8170e4570..86addc6da 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -285,6 +285,7 @@ XX(sampling_decision) XX(sampling_transaction) XX(scope_breadcrumbs) XX(scope_contexts) +XX(scope_update_context) XX(scope_extra) XX(scope_fingerprint) XX(scope_fingerprint_n)