diff --git a/examples/cpp/beta/sync/authentication.cpp b/examples/cpp/beta/sync/authentication.cpp index 4a12269b8d..9f41ec212f 100644 --- a/examples/cpp/beta/sync/authentication.cpp +++ b/examples/cpp/beta/sync/authentication.cpp @@ -20,8 +20,9 @@ static const std::string APP_ID = "cpp-tester-uliix"; TEST_CASE("create and log in an email/password user", "[realm][sync]") { // :snippet-start: beta-register-user - auto app = realm::App(APP_ID); - app.get_sync_manager().set_log_level(realm::logger::level::warn); // :remove: + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); auto userEmail = "testUser" + random_string() + "@example.com"; auto userPassword = "password1234"; @@ -43,8 +44,9 @@ TEST_CASE("create and log in an email/password user", "[realm][sync]") { TEST_CASE("create and log in an anonymous user", "[realm][sync]") { // :snippet-start: beta-anonymous-login - auto app = realm::App(APP_ID); - app.get_sync_manager().set_log_level(realm::logger::level::warn); // :remove: + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::anonymous()).get(); // :snippet-end: @@ -59,8 +61,9 @@ TEST_CASE("test custom function authentication", "[realm][sync]") { // The parameter details vary depending on how you define your custom authentication function. realm::bson::BsonDocument params = {{ "username", "bob" }}; - auto app = realm::App(APP_ID); - app.get_sync_manager().set_log_level(realm::logger::level::warn); // :remove: + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::function(params)).get(); // :snippet-end: @@ -70,8 +73,9 @@ TEST_CASE("test custom function authentication", "[realm][sync]") { } TEST_CASE("test get user access token", "[realm][sync]") { - auto app = realm::App(APP_ID); - app.get_sync_manager().set_log_level(realm::logger::level::warn); // :remove: + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::anonymous()).get(); // :snippet-start: get-user-access-token @@ -93,8 +97,9 @@ void testAPIKeyAuthSyntax() { auto API_KEY = "this was tested with a valid API key when written"; // :snippet-start: beta-api-key - auto app = realm::App(APP_ID); - app.get_sync_manager().set_log_level(realm::logger::level::warn); // :remove: + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::api_key(API_KEY)).get(); // :snippet-end: @@ -104,8 +109,9 @@ void testCustomJWTAuthSyntax() { // :snippet-start: beta-custom-jwt auto token = ""; - auto app = realm::App(APP_ID); - app.get_sync_manager().set_log_level(realm::logger::level::warn); // :remove: + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::custom(token)).get(); // :snippet-end: @@ -113,8 +119,9 @@ void testCustomJWTAuthSyntax() { void testSignInWithFacebookAuthSyntax() { // :snippet-start: beta-facebook - auto app = realm::App(APP_ID); - app.get_sync_manager().set_log_level(realm::logger::level::warn); // :remove: + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); auto accessToken = ""; @@ -124,8 +131,9 @@ void testSignInWithFacebookAuthSyntax() { void testSignInWithAppleAuthSyntax() { // :snippet-start: beta-apple - auto app = realm::App(APP_ID); - app.get_sync_manager().set_log_level(realm::logger::level::warn); // :remove: + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); auto idToken = ""; @@ -138,8 +146,9 @@ void testSignInWithGoogleAuthCodeSyntax() { auto authCode = realm::App::credentials::auth_code{ myAuthCode }; // :snippet-start: beta-google-auth-code - auto app = realm::App(APP_ID); - app.get_sync_manager().set_log_level(realm::logger::level::warn); // :remove: + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); // The auth_code below is the user's server auth code you got from Google auto user = app.login(realm::App::credentials::google(authCode)).get(); @@ -151,8 +160,9 @@ void testSignInWithGoogleIdTokenSyntax() { auto idToken = realm::App::credentials::id_token{ myIdToken }; // :snippet-start: beta-google-id-token - auto app = realm::App(APP_ID); - app.get_sync_manager().set_log_level(realm::logger::level::warn); // :remove: + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); // The id_token below is the user's OpenID Connect id_token you got from the Google OAuth response auto user = app.login(realm::App::credentials::google(idToken)).get(); @@ -160,8 +170,9 @@ void testSignInWithGoogleIdTokenSyntax() { } TEST_CASE("get the current user", "[realm][sync]") { - auto app = realm::App(APP_ID); - app.get_sync_manager().set_log_level(realm::logger::level::warn); + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::anonymous()).get(); // :snippet-start: beta-get-current-user @@ -174,8 +185,9 @@ TEST_CASE("get the current user", "[realm][sync]") { } TEST_CASE("confirm the user is logged in", "[realm][sync]") { - auto app = realm::App(APP_ID); - app.get_sync_manager().set_log_level(realm::logger::level::warn); + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); // :snippet-start: check-user-is-logged-in auto user = app.login(realm::App::credentials::anonymous()).get(); CHECK(user.is_logged_in()); diff --git a/examples/cpp/beta/sync/call-function.cpp b/examples/cpp/beta/sync/call-function.cpp index a8d2e474d7..b901eb47ce 100644 --- a/examples/cpp/beta/sync/call-function.cpp +++ b/examples/cpp/beta/sync/call-function.cpp @@ -10,9 +10,10 @@ TEST_CASE("call a function", "[realm][sync]") // :snippet-start: beta-call-a-function // Connect to an App Services App and authenticate a user // :snippet-start: connect-app-services - auto app = realm::App(APP_ID); + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); // :snippet-end: - app.get_sync_manager().set_log_level(realm::logger::level::warn); // :remove: auto user = app.login(realm::App::credentials::anonymous()).get(); auto sync_config = user.flexible_sync_configuration(); diff --git a/examples/cpp/beta/sync/custom-user-data.cpp b/examples/cpp/beta/sync/custom-user-data.cpp index dd4e56dbbe..85949465f6 100644 --- a/examples/cpp/beta/sync/custom-user-data.cpp +++ b/examples/cpp/beta/sync/custom-user-data.cpp @@ -6,7 +6,9 @@ static const std::string APP_ID = "cpp-tester-uliix"; TEST_CASE("custom user data", "[realm][sync]") { - auto app = realm::App(APP_ID); + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); // :snippet-start: beta-create auto user = app.login(realm::App::credentials::anonymous()).get(); diff --git a/examples/cpp/beta/sync/flexible-sync.cpp b/examples/cpp/beta/sync/flexible-sync.cpp index 896e72b50b..ba8028ada0 100644 --- a/examples/cpp/beta/sync/flexible-sync.cpp +++ b/examples/cpp/beta/sync/flexible-sync.cpp @@ -38,7 +38,9 @@ struct Alpha_Sync_Dog : realm::object { }; TEST_CASE("subscribe to a all objects of a type", "[sync]") { - auto app = realm::App(APP_ID); + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::anonymous()).get(); auto syncConfig = user.flexible_sync_configuration(); auto syncedRealmRef = realm::async_open(syncConfig).get_future().get(); @@ -73,7 +75,9 @@ TEST_CASE("subscribe to a all objects of a type", "[sync]") { TEST_CASE("subscribe to a subset of objects", "[sync]") { // :snippet-start: flexible-sync-prerequisites // Initialize the App, authenticate a user, and open the realm - auto app = realm::App(APP_ID); + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::anonymous()).get(); auto syncConfig = user.flexible_sync_configuration(); auto syncedRealmRef = realm::async_open(syncConfig).get_future().get(); @@ -133,7 +137,9 @@ REALM_SCHEMA(Beta_FlexibleSync_Dog, _id, name, age) // so I'm removing the Bluehawk markup but keeping the test to // verify it all works as expected in the experimental namespace TEST_CASE("beta subscribe to a all objects of a type", "[sync]") { - auto app = realm::App(APP_ID); + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::anonymous()).get(); auto syncConfig = user.flexible_sync_configuration(); auto syncedRealm = realm::experimental::db(syncConfig); @@ -158,7 +164,9 @@ TEST_CASE("beta subscribe to a all objects of a type", "[sync]") { TEST_CASE("beta subscribe to a subset of objects", "[sync]") { // :snippet-start: beta-flexible-sync-prerequisites // Initialize the App, authenticate a user, and open the realm - auto app = realm::App(APP_ID); + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::anonymous()).get(); auto syncConfig = user.flexible_sync_configuration(); auto syncedRealm = realm::experimental::db(syncConfig); @@ -196,7 +204,9 @@ TEST_CASE("beta subscribe to a subset of objects", "[sync]") { TEST_CASE("open a synced realm with old syntax", "[realm][sync]") { // :snippet-start: open-a-synced-realm - auto app = realm::App(APP_ID); + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); // Ensure anonymous authentication is enabled in the App Services App auto user = app.login(realm::App::credentials::anonymous()).get(); auto sync_config = user.flexible_sync_configuration(); diff --git a/examples/cpp/beta/sync/quick-start.cpp b/examples/cpp/beta/sync/quick-start.cpp index 93cbdd0e6e..949061b389 100644 --- a/examples/cpp/beta/sync/quick-start.cpp +++ b/examples/cpp/beta/sync/quick-start.cpp @@ -109,7 +109,9 @@ TEST_CASE("local quick start", "[realm][write]") { // After skipping and then un-skipping this test with #if 0/#endif, I can't // seem to replicate the issue. May require debugging in the future. TEST_CASE("sync quick start", "[realm][write][sync][sync-logger]") { - auto app = realm::App(APP_ID); + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); auto logLevel = realm::logger::level::info; app.get_sync_manager().set_log_level(logLevel); // :snippet-start: beta-authenticate-user diff --git a/examples/cpp/beta/sync/sync-errors.cpp b/examples/cpp/beta/sync/sync-errors.cpp index 46bd655ee9..111356c66b 100644 --- a/examples/cpp/beta/sync/sync-errors.cpp +++ b/examples/cpp/beta/sync/sync-errors.cpp @@ -3,10 +3,12 @@ #include #include #include +#include // :replace-start: { // "terms": { -// "SyncError_": "" +// "SyncError_": "", +// "Beta_SyncError_": "" // } // } @@ -32,9 +34,21 @@ struct Beta_SyncError_Dog { }; REALM_SCHEMA(Beta_SyncError_Dog, _id, name, age) +// :snippet-start: compensating-write-model +struct Beta_SyncError_Item { + primary_key _id{realm::object_id::generate()}; + std::string ownerId; + std::string itemName; + int64_t complexity; +}; +REALM_SCHEMA(Beta_SyncError_Item, _id, ownerId, itemName, complexity) +// :snippet-end: + TEST_CASE("set a sync error handler", "[error]") { // :snippet-start: create-error-handler - auto app = realm::App(APP_ID); + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::anonymous()).get(); auto dbConfig = user.flexible_sync_configuration(); @@ -46,13 +60,13 @@ TEST_CASE("set a sync error handler", "[error]") { auto syncRealmRef = realm::async_open(dbConfig).get_future().get(); auto syncRealm = syncRealmRef.resolve(); // :snippet-end: - + auto syncSession = syncRealm.get_sync_session(); auto updateSubscriptionSuccess = syncRealm.subscriptions().update([](realm::mutable_sync_subscription_set &subs) { subs.clear(); }).get(); CHECK(updateSubscriptionSuccess == true); CHECK(syncRealm.subscriptions().size() == 0); - sleep(5); + syncSession->wait_for_upload_completion().get(); updateSubscriptionSuccess = syncRealm.subscriptions().update([](realm::mutable_sync_subscription_set &subs) { subs.add("dogs"); }).get(); @@ -66,12 +80,14 @@ TEST_CASE("set a sync error handler", "[error]") { syncRealm.write([&syncRealm, &dog] { syncRealm.add(dog); }); - sleep(5); + syncSession->wait_for_upload_completion().get(); } TEST_CASE("beta set a sync error handler", "[error]") { // :snippet-start: beta-create-error-handler - auto app = realm::App(APP_ID); + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::anonymous()).get(); auto dbConfig = user.flexible_sync_configuration(); @@ -82,13 +98,13 @@ TEST_CASE("beta set a sync error handler", "[error]") { auto syncRealm = realm::experimental::db(dbConfig); // :snippet-end: - + auto syncSession = syncRealm.get_sync_session(); auto updateSubscriptionSuccess = syncRealm.subscriptions().update([](realm::mutable_sync_subscription_set &subs) { subs.clear(); }).get(); CHECK(updateSubscriptionSuccess == true); CHECK(syncRealm.subscriptions().size() == 0); - sleep(5); + syncSession->wait_for_upload_completion().get(); updateSubscriptionSuccess = syncRealm.subscriptions().update([](realm::mutable_sync_subscription_set &subs) { subs.add("dogs"); }).get(); @@ -102,6 +118,174 @@ TEST_CASE("beta set a sync error handler", "[error]") { syncRealm.write([&] { syncRealm.add(std::move(dog)); }); - sleep(5); + syncSession->wait_for_upload_completion().get(); +} + +TEST_CASE("beta compensating write error outside query", "[error]") { + // :snippet-start: compensating-write-setup + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); + auto user = app.login(realm::App::credentials::anonymous()).get(); + auto dbConfig = user.flexible_sync_configuration(); + // :remove-start: + realm::object_id primaryKey = { realm::object_id::generate() }; + + std::promise errorPromise; + std::future future = errorPromise.get_future(); + + dbConfig.sync_config().set_error_handler([&](const realm::sync_session& session, std::optional error) { + std::cerr << "A sync error occurred. Message: " << error->message() << std::endl; + errorPromise.set_value(*error); // :remove: + }); + // :remove-end: + auto syncRealm = realm::experimental::db(dbConfig); + // :remove-start: + auto syncSession = syncRealm.get_sync_session(); + auto updateSubscriptionSuccess = syncRealm.subscriptions().update([](realm::mutable_sync_subscription_set &subs) { + subs.clear(); + }).get(); + CHECK(updateSubscriptionSuccess == true); + CHECK(syncRealm.subscriptions().size() == 0); + syncSession->wait_for_upload_completion().get(); + // :remove-end: + // Add subscription + auto subscriptionUpdateSuccess = syncRealm.subscriptions().update([](realm::mutable_sync_subscription_set &subs) { + // Get Items from Atlas that match this query. + // Uses the queryable field `complexity`. + // Sync Item objects with complexity less than or equal to 4. + subs.add("simple items", [](auto &obj) { + return obj.complexity <= 4; + }); + }).get(); + // :snippet-end: + REQUIRE(subscriptionUpdateSuccess == true); + CHECK(syncRealm.subscriptions().size() == 1); + + // :snippet-start: successful-write-example + // Per the Device Sync permissions, users can only read and write data + // where the `Item.ownerId` property matches their own user ID. + auto simpleItem = Beta_SyncError_Item { + .ownerId = user.identifier(), + .itemName = "This item meets sync criteria", + .complexity = 3 + }; + + // `simpleItem` successfully writes to the realm and syncs to Atlas + // because its data matches the subscription query (complexity <= 4) + // and its `ownerId` field matches the user ID. + syncRealm.write([&] { + syncRealm.add(std::move(simpleItem)); + }); + // :snippet-end: + + auto syncedItems = syncRealm.objects(); + CHECK(syncedItems.size() == 1); + auto specificItem = syncedItems[0]; + syncRealm.write([&] { + syncRealm.remove(specificItem); + }); + syncSession->wait_for_upload_completion().get(); + + // :snippet-start: compensating-write-example + // The complexity of this item is `7`. This is outside the bounds + // of the subscription query, which triggers a compensating write. + auto complexItem = Beta_SyncError_Item { + ._id = primaryKey, + .ownerId = user.identifier(), + .itemName = "Test compensating writes", + .complexity = 7 + }; + + // This should trigger a compensating write error when it tries to sync + // due to server-side permissions, which gets logged with the error handler. + syncRealm.write([&] { + syncRealm.add(std::move(complexItem)); + }); + // :snippet-end: + syncSession->wait_for_upload_completion().get(); + + std::string pkString = primaryKey.to_string(); + auto receivedSyncError = future.get(); + // :snippet-start: get-compensating-write-error-info + auto info = receivedSyncError.compensating_writes_info(); + for (auto& v : info) { + std::cout << "A write was rejected with a compensating write error.\n"; + std::cout << "An object of type " << v.object_name << "\n"; + std::cout << "was rejected because " << v.reason << ".\n"; + // :remove-start: + REQUIRE(v.primary_key == realm::object_id(primaryKey)); + REQUIRE(v.object_name == "Beta_SyncError_Item"); + REQUIRE(v.reason == "write to ObjectID(\"" + pkString + "\") in table \"Beta_SyncError_Item\" not allowed; object is outside of the current query view"); + // :remove-end: + } + // :snippet-end: +} + +TEST_CASE("beta compensating write error write doesn't match permissions", "[error]") { + auto appConfig = realm::App::configuration(); + appConfig.app_id = APP_ID; + auto app = realm::App(appConfig); + auto user = app.login(realm::App::credentials::anonymous()).get(); + auto dbConfig = user.flexible_sync_configuration(); + realm::object_id primaryKey = { realm::object_id::generate() }; + + std::promise errorPromise; + std::future future = errorPromise.get_future(); + + dbConfig.sync_config().set_error_handler([&](const realm::sync_session& session, std::optional error) { + std::cerr << "A sync error occurred. Message: " << error->message() << std::endl; + errorPromise.set_value(*error); + }); + auto syncRealm = realm::experimental::db(dbConfig); + auto syncSession = syncRealm.get_sync_session(); + auto updateSubscriptionSuccess = syncRealm.subscriptions().update([](realm::mutable_sync_subscription_set &subs) { + subs.clear(); + }).get(); + CHECK(updateSubscriptionSuccess == true); + CHECK(syncRealm.subscriptions().size() == 0); + syncSession->wait_for_upload_completion().get(); + // Add subscription + auto subscriptionUpdateSuccess = syncRealm.subscriptions().update([](realm::mutable_sync_subscription_set &subs) { + // Get Items from Atlas that match this query. + // Uses the queryable field `complexity`. + // Sync Item objects with complexity less than or equal to 4. + subs.add("simple items", [](auto &obj) { + return obj.complexity <= 4; + }); + }).get(); + REQUIRE(subscriptionUpdateSuccess == true); + CHECK(syncRealm.subscriptions().size() == 1); + + // :snippet-start: write-not-matching-permissions + // The `ownerId` of this item does not match the user ID of the logged-in + // user. The user does not have permissions to make this write, which + // triggers a compensating write. + auto itemWithWrongOwner = Beta_SyncError_Item { + ._id = primaryKey, // :remove: + .ownerId = "not the current user", + .itemName = "Trigger an incorrect permissions compensating write", + .complexity = 1 + }; + + syncRealm.write([&] { + syncRealm.add(std::move(itemWithWrongOwner)); + }); + // :snippet-end: + syncSession->wait_for_upload_completion().get(); + + std::string pkString = primaryKey.to_string(); + auto receivedSyncError = future.get(); + auto info = receivedSyncError.compensating_writes_info(); + for (auto& v : info) { + std::cout << "A write was rejected with a compensating write error.\n"; + std::cout << "An object of type " << v.object_name << "\n"; + std::cout << "was rejected because " << v.reason << ".\n"; + // :remove-start: + REQUIRE(v.primary_key == realm::object_id(primaryKey)); + REQUIRE(v.object_name == "Beta_SyncError_Item"); + REQUIRE(v.reason == "write to ObjectID(\"" + pkString + "\") in table \"Beta_SyncError_Item\" not allowed"); + // :remove-end: + } } // :replace-end: diff --git a/source/examples/generated/cpp/authentication.snippet.beta-anonymous-login.cpp b/source/examples/generated/cpp/authentication.snippet.beta-anonymous-login.cpp index a95519c716..c5d1922601 100644 --- a/source/examples/generated/cpp/authentication.snippet.beta-anonymous-login.cpp +++ b/source/examples/generated/cpp/authentication.snippet.beta-anonymous-login.cpp @@ -1,3 +1,5 @@ -auto app = realm::App(APP_ID); +auto appConfig = realm::App::configuration(); +appConfig.app_id = APP_ID; +auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::anonymous()).get(); diff --git a/source/examples/generated/cpp/authentication.snippet.beta-api-key.cpp b/source/examples/generated/cpp/authentication.snippet.beta-api-key.cpp index 326c8ae940..84e293bbd2 100644 --- a/source/examples/generated/cpp/authentication.snippet.beta-api-key.cpp +++ b/source/examples/generated/cpp/authentication.snippet.beta-api-key.cpp @@ -1,3 +1,5 @@ -auto app = realm::App(APP_ID); +auto appConfig = realm::App::configuration(); +appConfig.app_id = APP_ID; +auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::api_key(API_KEY)).get(); diff --git a/source/examples/generated/cpp/authentication.snippet.beta-apple.cpp b/source/examples/generated/cpp/authentication.snippet.beta-apple.cpp index 5d0ab76ddd..afae7361ab 100644 --- a/source/examples/generated/cpp/authentication.snippet.beta-apple.cpp +++ b/source/examples/generated/cpp/authentication.snippet.beta-apple.cpp @@ -1,4 +1,6 @@ -auto app = realm::App(APP_ID); +auto appConfig = realm::App::configuration(); +appConfig.app_id = APP_ID; +auto app = realm::App(appConfig); auto idToken = ""; diff --git a/source/examples/generated/cpp/authentication.snippet.beta-custom-function.cpp b/source/examples/generated/cpp/authentication.snippet.beta-custom-function.cpp index 3725fb41e7..bbb94b61b6 100644 --- a/source/examples/generated/cpp/authentication.snippet.beta-custom-function.cpp +++ b/source/examples/generated/cpp/authentication.snippet.beta-custom-function.cpp @@ -2,6 +2,8 @@ // The parameter details vary depending on how you define your custom authentication function. realm::bson::BsonDocument params = {{ "username", "bob" }}; -auto app = realm::App(APP_ID); +auto appConfig = realm::App::configuration(); +appConfig.app_id = APP_ID; +auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::function(params)).get(); diff --git a/source/examples/generated/cpp/authentication.snippet.beta-custom-jwt.cpp b/source/examples/generated/cpp/authentication.snippet.beta-custom-jwt.cpp index b6ade61003..592145a544 100644 --- a/source/examples/generated/cpp/authentication.snippet.beta-custom-jwt.cpp +++ b/source/examples/generated/cpp/authentication.snippet.beta-custom-jwt.cpp @@ -1,5 +1,7 @@ auto token = ""; -auto app = realm::App(APP_ID); +auto appConfig = realm::App::configuration(); +appConfig.app_id = APP_ID; +auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::custom(token)).get(); diff --git a/source/examples/generated/cpp/authentication.snippet.beta-facebook.cpp b/source/examples/generated/cpp/authentication.snippet.beta-facebook.cpp index 1c046867aa..89e29fa0a5 100644 --- a/source/examples/generated/cpp/authentication.snippet.beta-facebook.cpp +++ b/source/examples/generated/cpp/authentication.snippet.beta-facebook.cpp @@ -1,4 +1,6 @@ -auto app = realm::App(APP_ID); +auto appConfig = realm::App::configuration(); +appConfig.app_id = APP_ID; +auto app = realm::App(appConfig); auto accessToken = ""; diff --git a/source/examples/generated/cpp/authentication.snippet.beta-google-auth-code.cpp b/source/examples/generated/cpp/authentication.snippet.beta-google-auth-code.cpp index 51c3b118c3..1216354302 100644 --- a/source/examples/generated/cpp/authentication.snippet.beta-google-auth-code.cpp +++ b/source/examples/generated/cpp/authentication.snippet.beta-google-auth-code.cpp @@ -1,4 +1,6 @@ -auto app = realm::App(APP_ID); +auto appConfig = realm::App::configuration(); +appConfig.app_id = APP_ID; +auto app = realm::App(appConfig); // The auth_code below is the user's server auth code you got from Google auto user = app.login(realm::App::credentials::google(authCode)).get(); diff --git a/source/examples/generated/cpp/authentication.snippet.beta-google-id-token.cpp b/source/examples/generated/cpp/authentication.snippet.beta-google-id-token.cpp index ece8a938f8..539e68a689 100644 --- a/source/examples/generated/cpp/authentication.snippet.beta-google-id-token.cpp +++ b/source/examples/generated/cpp/authentication.snippet.beta-google-id-token.cpp @@ -1,4 +1,6 @@ -auto app = realm::App(APP_ID); +auto appConfig = realm::App::configuration(); +appConfig.app_id = APP_ID; +auto app = realm::App(appConfig); // The id_token below is the user's OpenID Connect id_token you got from the Google OAuth response auto user = app.login(realm::App::credentials::google(idToken)).get(); diff --git a/source/examples/generated/cpp/authentication.snippet.beta-register-user.cpp b/source/examples/generated/cpp/authentication.snippet.beta-register-user.cpp index e67bcd257c..58a4a1a6b4 100644 --- a/source/examples/generated/cpp/authentication.snippet.beta-register-user.cpp +++ b/source/examples/generated/cpp/authentication.snippet.beta-register-user.cpp @@ -1,4 +1,6 @@ -auto app = realm::App(APP_ID); +auto appConfig = realm::App::configuration(); +appConfig.app_id = APP_ID; +auto app = realm::App(appConfig); auto userEmail = "testUser" + random_string() + "@example.com"; auto userPassword = "password1234"; diff --git a/source/examples/generated/cpp/call-function.snippet.beta-call-a-function.cpp b/source/examples/generated/cpp/call-function.snippet.beta-call-a-function.cpp index 47cf4589ca..8744f72856 100644 --- a/source/examples/generated/cpp/call-function.snippet.beta-call-a-function.cpp +++ b/source/examples/generated/cpp/call-function.snippet.beta-call-a-function.cpp @@ -1,5 +1,7 @@ // Connect to an App Services App and authenticate a user -auto app = realm::App(APP_ID); +auto appConfig = realm::App::configuration(); +appConfig.app_id = APP_ID; +auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::anonymous()).get(); auto sync_config = user.flexible_sync_configuration(); diff --git a/source/examples/generated/cpp/call-function.snippet.connect-app-services.cpp b/source/examples/generated/cpp/call-function.snippet.connect-app-services.cpp index fc41042859..5748e55568 100644 --- a/source/examples/generated/cpp/call-function.snippet.connect-app-services.cpp +++ b/source/examples/generated/cpp/call-function.snippet.connect-app-services.cpp @@ -1 +1,3 @@ -auto app = realm::App(APP_ID); +auto appConfig = realm::App::configuration(); +appConfig.app_id = APP_ID; +auto app = realm::App(appConfig); diff --git a/source/examples/generated/cpp/flexible-sync.snippet.beta-flexible-sync-prerequisites.cpp b/source/examples/generated/cpp/flexible-sync.snippet.beta-flexible-sync-prerequisites.cpp index 7602b8e49e..3e2dfa830b 100644 --- a/source/examples/generated/cpp/flexible-sync.snippet.beta-flexible-sync-prerequisites.cpp +++ b/source/examples/generated/cpp/flexible-sync.snippet.beta-flexible-sync-prerequisites.cpp @@ -1,5 +1,7 @@ // Initialize the App, authenticate a user, and open the realm -auto app = realm::App(APP_ID); +auto appConfig = realm::App::configuration(); +appConfig.app_id = APP_ID; +auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::anonymous()).get(); auto syncConfig = user.flexible_sync_configuration(); auto syncedRealm = realm::experimental::db(syncConfig); diff --git a/source/examples/generated/cpp/flexible-sync.snippet.flexible-sync-prerequisites.cpp b/source/examples/generated/cpp/flexible-sync.snippet.flexible-sync-prerequisites.cpp index 7381434064..12e55160e3 100644 --- a/source/examples/generated/cpp/flexible-sync.snippet.flexible-sync-prerequisites.cpp +++ b/source/examples/generated/cpp/flexible-sync.snippet.flexible-sync-prerequisites.cpp @@ -1,5 +1,7 @@ // Initialize the App, authenticate a user, and open the realm -auto app = realm::App(APP_ID); +auto appConfig = realm::App::configuration(); +appConfig.app_id = APP_ID; +auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::anonymous()).get(); auto syncConfig = user.flexible_sync_configuration(); auto syncedRealmRef = realm::async_open(syncConfig).get_future().get(); diff --git a/source/examples/generated/cpp/flexible-sync.snippet.open-a-synced-realm.cpp b/source/examples/generated/cpp/flexible-sync.snippet.open-a-synced-realm.cpp index 9ce67bbf77..f336f1c354 100644 --- a/source/examples/generated/cpp/flexible-sync.snippet.open-a-synced-realm.cpp +++ b/source/examples/generated/cpp/flexible-sync.snippet.open-a-synced-realm.cpp @@ -1,4 +1,6 @@ -auto app = realm::App(APP_ID); +auto appConfig = realm::App::configuration(); +appConfig.app_id = APP_ID; +auto app = realm::App(appConfig); // Ensure anonymous authentication is enabled in the App Services App auto user = app.login(realm::App::credentials::anonymous()).get(); auto sync_config = user.flexible_sync_configuration(); diff --git a/source/examples/generated/cpp/sync-errors.snippet.beta-create-error-handler.cpp b/source/examples/generated/cpp/sync-errors.snippet.beta-create-error-handler.cpp index 862c97d5d9..04bf33b7fb 100644 --- a/source/examples/generated/cpp/sync-errors.snippet.beta-create-error-handler.cpp +++ b/source/examples/generated/cpp/sync-errors.snippet.beta-create-error-handler.cpp @@ -1,4 +1,6 @@ -auto app = realm::App(APP_ID); +auto appConfig = realm::App::configuration(); +appConfig.app_id = APP_ID; +auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::anonymous()).get(); auto dbConfig = user.flexible_sync_configuration(); diff --git a/source/examples/generated/cpp/sync-errors.snippet.compensating-write-example.cpp b/source/examples/generated/cpp/sync-errors.snippet.compensating-write-example.cpp new file mode 100644 index 0000000000..4770cfa0ea --- /dev/null +++ b/source/examples/generated/cpp/sync-errors.snippet.compensating-write-example.cpp @@ -0,0 +1,14 @@ +// The complexity of this item is `7`. This is outside the bounds +// of the subscription query, which triggers a compensating write. +auto complexItem = Item { + ._id = primaryKey, + .ownerId = user.identifier(), + .itemName = "Test compensating writes", + .complexity = 7 +}; + +// This should trigger a compensating write error when it tries to sync +// due to server-side permissions, which gets logged with the error handler. +syncRealm.write([&] { + syncRealm.add(std::move(complexItem)); +}); diff --git a/source/examples/generated/cpp/sync-errors.snippet.compensating-write-model.cpp b/source/examples/generated/cpp/sync-errors.snippet.compensating-write-model.cpp new file mode 100644 index 0000000000..108181c63b --- /dev/null +++ b/source/examples/generated/cpp/sync-errors.snippet.compensating-write-model.cpp @@ -0,0 +1,7 @@ +struct Item { + primary_key _id{realm::object_id::generate()}; + std::string ownerId; + std::string itemName; + int64_t complexity; +}; +REALM_SCHEMA(Item, _id, ownerId, itemName, complexity) diff --git a/source/examples/generated/cpp/sync-errors.snippet.compensating-write-setup.cpp b/source/examples/generated/cpp/sync-errors.snippet.compensating-write-setup.cpp new file mode 100644 index 0000000000..092cb02812 --- /dev/null +++ b/source/examples/generated/cpp/sync-errors.snippet.compensating-write-setup.cpp @@ -0,0 +1,15 @@ +auto appConfig = realm::App::configuration(); +appConfig.app_id = APP_ID; +auto app = realm::App(appConfig); +auto user = app.login(realm::App::credentials::anonymous()).get(); +auto dbConfig = user.flexible_sync_configuration(); +auto syncRealm = realm::experimental::db(dbConfig); +// Add subscription +auto subscriptionUpdateSuccess = syncRealm.subscriptions().update([](realm::mutable_sync_subscription_set &subs) { + // Get Items from Atlas that match this query. + // Uses the queryable field `complexity`. + // Sync Item objects with complexity less than or equal to 4. + subs.add("simple items", [](auto &obj) { + return obj.complexity <= 4; + }); +}).get(); diff --git a/source/examples/generated/cpp/sync-errors.snippet.create-error-handler.cpp b/source/examples/generated/cpp/sync-errors.snippet.create-error-handler.cpp index 678e234e6b..8eff04c3a5 100644 --- a/source/examples/generated/cpp/sync-errors.snippet.create-error-handler.cpp +++ b/source/examples/generated/cpp/sync-errors.snippet.create-error-handler.cpp @@ -1,4 +1,6 @@ -auto app = realm::App(APP_ID); +auto appConfig = realm::App::configuration(); +appConfig.app_id = APP_ID; +auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::anonymous()).get(); auto dbConfig = user.flexible_sync_configuration(); diff --git a/source/examples/generated/cpp/sync-errors.snippet.get-compensating-write-error-info.cpp b/source/examples/generated/cpp/sync-errors.snippet.get-compensating-write-error-info.cpp new file mode 100644 index 0000000000..8aebf3022f --- /dev/null +++ b/source/examples/generated/cpp/sync-errors.snippet.get-compensating-write-error-info.cpp @@ -0,0 +1,6 @@ +auto info = receivedSyncError.compensating_writes_info(); +for (auto& v : info) { + std::cout << "A write was rejected with a compensating write error.\n"; + std::cout << "An object of type " << v.object_name << "\n"; + std::cout << "was rejected because " << v.reason << ".\n"; +} diff --git a/source/examples/generated/cpp/sync-errors.snippet.successful-write-example.cpp b/source/examples/generated/cpp/sync-errors.snippet.successful-write-example.cpp new file mode 100644 index 0000000000..988460afe2 --- /dev/null +++ b/source/examples/generated/cpp/sync-errors.snippet.successful-write-example.cpp @@ -0,0 +1,14 @@ +// Per the Device Sync permissions, users can only read and write data +// where the `Item.ownerId` property matches their own user ID. +auto simpleItem = Item { + .ownerId = user.identifier(), + .itemName = "This item meets sync criteria", + .complexity = 3 +}; + +// `simpleItem` successfully writes to the realm and syncs to Atlas +// because its data matches the subscription query (complexity <= 4) +// and its `ownerId` field matches the user ID. +syncRealm.write([&] { + syncRealm.add(std::move(simpleItem)); +}); diff --git a/source/examples/generated/cpp/sync-errors.snippet.write-not-matching-permissions.cpp b/source/examples/generated/cpp/sync-errors.snippet.write-not-matching-permissions.cpp new file mode 100644 index 0000000000..024337e031 --- /dev/null +++ b/source/examples/generated/cpp/sync-errors.snippet.write-not-matching-permissions.cpp @@ -0,0 +1,12 @@ +// The `ownerId` of this item does not match the user ID of the logged-in +// user. The user does not have permissions to make this write, which +// triggers a compensating write. +auto itemWithWrongOwner = Item { + .ownerId = "not the current user", + .itemName = "Trigger an incorrect permissions compensating write", + .complexity = 1 +}; + +syncRealm.write([&] { + syncRealm.add(std::move(itemWithWrongOwner)); +}); diff --git a/source/sdk/cpp/app-services/call-a-function.txt b/source/sdk/cpp/app-services/call-a-function.txt index 7e6404275e..01f68d7987 100644 --- a/source/sdk/cpp/app-services/call-a-function.txt +++ b/source/sdk/cpp/app-services/call-a-function.txt @@ -4,6 +4,13 @@ Call a Function - C++ SDK Preview ================================= +.. meta:: + :keywords: code example + +.. facet:: + :name: genre + :values: reference + .. contents:: On this page :local: :backlinks: none @@ -35,6 +42,7 @@ function as a string for the first parameter. This function takes two arguments, which we provide as a ``BsonArray`` of arguments: .. versionchanged:: 0.2.0 Returns std::future instead of std::promise +.. versionchanged:: v0.4.0-preview Replaces deprecated realm::App(...) with realm::App(const realm::App::configuration&) .. literalinclude:: /examples/generated/cpp/call-function.snippet.beta-call-a-function.cpp :language: cpp diff --git a/source/sdk/cpp/app-services/connect-to-app.txt b/source/sdk/cpp/app-services/connect-to-app.txt index fd362d95b1..a192fb26ab 100644 --- a/source/sdk/cpp/app-services/connect-to-app.txt +++ b/source/sdk/cpp/app-services/connect-to-app.txt @@ -4,6 +4,13 @@ Connect to App Services - C++ SDK Preview ========================================= +.. meta:: + :keywords: code example + +.. facet:: + :name: genre + :values: reference + .. contents:: On this page :local: :backlinks: none @@ -20,13 +27,20 @@ your App's functions. Prerequisites ------------- -#. :ref:`Create an App Services app ` +Before you can connect to Atlas App Services, you need an App Services App +with an App ID. To get started, refer to :ref:`Create an App ` +in the App Services documentation. + +To learn how to find your App ID in the App Services UI, refer to +:ref:`Find Your App ID ` in the App Services documentation. .. _cpp-access-the-app-client: Access the App Client --------------------- +.. versionchanged:: v0.4.0-preview Replaces deprecated realm::App(...) with realm::App(const realm::App::configuration&) + #. :ref:`Find the App ID in the Realm UI `. #. Create an :cpp-sdk:`App object ` with your App's ID as the argument. You use this ``App`` instance to access diff --git a/source/sdk/cpp/realm-files/configure-and-open-a-realm.txt b/source/sdk/cpp/realm-files/configure-and-open-a-realm.txt index 46fba1d318..2a2c23c0e9 100644 --- a/source/sdk/cpp/realm-files/configure-and-open-a-realm.txt +++ b/source/sdk/cpp/realm-files/configure-and-open-a-realm.txt @@ -5,6 +5,13 @@ Configure & Open a Realm - C++ SDK Preview ========================================== +.. meta:: + :keywords: code example + +.. facet:: + :name: genre + :values: tutorial + .. contents:: On this page :local: :backlinks: none @@ -205,6 +212,8 @@ To open a synced realm: .. tab:: Current :tabid: current + .. versionchanged:: v0.4.0-preview Replaces deprecated realm::App(...) with realm::App(const realm::App::configuration&) + .. literalinclude:: /examples/generated/cpp/flexible-sync.snippet.beta-flexible-sync-prerequisites.cpp :language: cpp diff --git a/source/sdk/cpp/sync.txt b/source/sdk/cpp/sync.txt index 75d5739546..a5ba2b76fe 100644 --- a/source/sdk/cpp/sync.txt +++ b/source/sdk/cpp/sync.txt @@ -9,6 +9,7 @@ Sync Data Between Devices - C++ SDK Preview :titlesonly: Manage Sync Subscriptions + Write to a Synced Realm Manage Sync Sessions Handle Sync Errors Set the Sync Client Log Level diff --git a/source/sdk/cpp/sync/handle-sync-errors.txt b/source/sdk/cpp/sync/handle-sync-errors.txt index de463a5856..09ee4cb126 100644 --- a/source/sdk/cpp/sync/handle-sync-errors.txt +++ b/source/sdk/cpp/sync/handle-sync-errors.txt @@ -4,6 +4,13 @@ Handle Sync Errors - C++ SDK Preview ==================================== +.. meta:: + :keywords: code example + +.. facet:: + :name: genre + :values: tutorial + .. contents:: On this page :local: :backlinks: none @@ -32,6 +39,8 @@ occurred. .. tab:: Current :tabid: current + .. versionchanged:: v0.4.0-preview Replaces deprecated realm::App(...) with realm::App(const realm::App::configuration&) + .. literalinclude:: /examples/generated/cpp/sync-errors.snippet.beta-create-error-handler.cpp :language: cpp @@ -41,6 +50,7 @@ occurred. :tabid: deprecated .. versionchanged:: 0.2.0 app.login returns std::future instead of std::promise + .. versionchanged:: v0.4.0-preview Replaces deprecated realm::App(...) with realm::App(const realm::App::configuration&) .. literalinclude:: /examples/generated/cpp/sync-errors.snippet.create-error-handler.cpp :language: cpp diff --git a/source/sdk/cpp/sync/stream-data-to-atlas.txt b/source/sdk/cpp/sync/stream-data-to-atlas.txt index 4d95ea89c9..c0f2be32cc 100644 --- a/source/sdk/cpp/sync/stream-data-to-atlas.txt +++ b/source/sdk/cpp/sync/stream-data-to-atlas.txt @@ -4,6 +4,13 @@ Stream Data to Atlas - C++ SDK Preview ====================================== +.. meta:: + :keywords: code example + +.. facet:: + :name: genre + :values: tutorial + .. contents:: On this page :local: :backlinks: none @@ -56,6 +63,7 @@ Sync Data Unidirectionally from a Client Application and :ref:`authenticate a user `. .. versionchanged:: 0.2.0 app.login returns std::future instead of std::promise + .. versionchanged:: v0.4.0-preview Replaces deprecated realm::App(...) with realm::App(const realm::App::configuration&) .. literalinclude:: /examples/generated/cpp/asymmetric-sync.snippet.beta-connect-and-authenticate.cpp :language: cpp diff --git a/source/sdk/cpp/sync/sync-subscriptions.txt b/source/sdk/cpp/sync/sync-subscriptions.txt index 2f32733ef5..5ee98fe3c9 100644 --- a/source/sdk/cpp/sync/sync-subscriptions.txt +++ b/source/sdk/cpp/sync/sync-subscriptions.txt @@ -5,6 +5,13 @@ Manage Sync Subscriptions - C++ SDK Preview =========================================== +.. meta:: + :keywords: code example + +.. facet:: + :name: genre + :values: reference + .. contents:: On this page :local: :backlinks: none @@ -35,6 +42,8 @@ The setup code for the examples on this page handles these prerequisites: .. tab:: Current :tabid: current + .. versionchanged:: v0.4.0-preview Replaces deprecated realm::App(...) with realm::App(const realm::App::configuration&) + .. literalinclude:: /examples/generated/cpp/flexible-sync.snippet.beta-flexible-sync-prerequisites.cpp :language: cpp @@ -48,6 +57,8 @@ The setup code for the examples on this page handles these prerequisites: .. tab:: Deprecated :tabid: deprecated + .. versionchanged:: v0.4.0-preview Replaces deprecated realm::App(...) with realm::App(const realm::App::configuration&) + .. literalinclude:: /examples/generated/cpp/flexible-sync.snippet.flexible-sync-prerequisites.cpp :language: cpp diff --git a/source/sdk/cpp/sync/write-to-synced-realm.txt b/source/sdk/cpp/sync/write-to-synced-realm.txt new file mode 100644 index 0000000000..c17f6cffe1 --- /dev/null +++ b/source/sdk/cpp/sync/write-to-synced-realm.txt @@ -0,0 +1,309 @@ +.. _cpp-write-synced-realm: + +============================================== +Write Data to a Synced Realm - C++ SDK Preview +============================================== + +.. meta:: + :keywords: code example + +.. facet:: + :name: genre + :values: tutorial + +.. contents:: On this page + :local: + :backlinks: none + :depth: 3 + :class: singlecol + +When writing data to a synced realm using :ref:`Flexible Sync `, +you can use the same APIs as when performing CRUD operations on a non-synced +realm. However, there are some differences in behavior to keep in mind as +you develop your application. + +When you write to a synced realm, your write operations must match *both* +of the following: + +- The sync subscription query +- The permissions in your App Services App + +If you try to write data that doesn't match either the query subscription or +the user's permissions, Realm reverts the write with a non-fatal error operation +called a :ref:`compensating write `. + +To learn more about configuring permissions for your app, see +:ref:`sync-rules` and the :ref:`flexible-sync-permissions-guide` in the +App Services documentation. + +To learn more about permission denied errors, compensating write errors, +and other Device Sync error types, refer to :ref:`sync-errors` in the App Services documentation. + +Determining What Data Syncs +--------------------------- + +The data that you can write to a synced realm is determined by the following: + +- Your Device Sync configuration +- Permissions in your App +- The Flexible Sync subscription query used when you open the realm + +The examples on this page use an Atlas App Services App with the following +Device Sync configuration and a client app with the following Realm SDK +data model and subscriptions. + +.. _cpp-sync-example-object-model: + +In this example, the client app uses the following object model: + +.. literalinclude:: /examples/generated/cpp/sync-errors.snippet.compensating-write-model.cpp + :language: cpp + +.. _cpp-app-services-configuration: + +App Services Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Based on the above example object model, Device Sync is configured with the +following queryable fields: + +- ``_id`` (always included) +- ``complexity`` +- ``ownerId`` + +The App Services App has permissions configured to let users read and write only +their own data: + +.. code-block:: json + + { + "roles": [ + { + "name": "readOwnWriteOwn", + "apply_when": {}, + "document_filters": { + "write": { + "ownerId": "%%user.id" + }, + "read": { + "ownerId": "%%user.id" + } + }, + "read": true, + "write": true, + "insert": true, + "delete": true, + "search": true + } + ] + } + +Any object in the Atlas collection where the ``ownerId`` does not match +the ``user.identifier()`` of the logged-in user cannot sync to this realm. + +.. _cpp-client-data-model-and-configuration: + +Client Data Model and Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The examples on this page use the following synced realm configuration with +this sync query and object model: + +.. tabs:: + + .. tab:: Sync Query + :tabid: sync-query + + .. literalinclude:: /examples/generated/cpp/sync-errors.snippet.compensating-write-setup.cpp + :language: cpp + + .. tab:: Object Model + :tabid: object-model + + .. literalinclude:: /examples/generated/cpp/sync-errors.snippet.compensating-write-model.cpp + :language: cpp + +With this Sync query, any object in the Atlas collection where the +``complexity`` property's value is greater than ``4`` cannot sync to this +realm. + +Write to a Synced Realm +----------------------- + +Writes to Flexible Sync realms may broadly fall into one of two categories, depending on +whether the write matches the :ref:`permissions ` and the +:ref:`Flexible Sync subscription query `: + +- Successful writes: The written object matches both the query subscription + and the user's permissions. The object writes successfully to the realm, + and syncs successfully to the App Services backend and other devices. +- Compensating writes: The written object does not match + the subscription query or the user does not have sufficient + permissions to perform the write. Realm reverts the illegal write with a + compensating write operation. + +.. tip:: + + If you want to write an object that does not match the query subscription, + you can open a different realm where the object matches the query + subscription. Alternately, you could write the object to a non-synced + realm that does not enforce permissions or subscription queries. + +.. _cpp-successful-writes: + +Successful Writes +~~~~~~~~~~~~~~~~~ + +When the write matches both user permissions *and* the query subscription in the client, +the Realm C++ SDK can successfully write the object to the synced realm. +This object syncs with the App Services backend when the device has a network connection. + +.. literalinclude:: /examples/generated/cpp/sync-errors.snippet.successful-write-example.cpp + :language: cpp + +.. _cpp-compensating-writes: + +Compensating Writes +~~~~~~~~~~~~~~~~~~~ + +When the write doesn't match *either* the query subscription or user +permissions, Realm reverts the write and provides an array of +:cpp-sdk:`compensating_write_error_info ` +objects. + +In more detail, when you write data that is outside the bounds of a query +subscription or does not match the user's permissions, the following occurs: + +#. Because the client realm has no concept of "illegal" writes, the write initially succeeds until Realm resolves the changeset with the App Services backend. +#. Upon sync, the server applies the rules and permissions. The server determines that the user does not have authorization to perform the write. +#. The server sends a revert operation, called a "compensating write", back to the client. +#. The client's realm reverts the illegal write operation. + +Any client-side writes to a given object between an illegal write to that object +and the corresponding compensating write will be lost. +In practice, this may look like the write succeeding, but then the +object "disappears" when Realm syncs with the App Services backend and +performs the compensating write. + +When this occurs, you can refer to the :ref:`App Services logs ` +or use the ``compensating_writes_info()`` function in the client to get +additional information on the error. For more information, refer +to the :ref:`cpp-compensating-write-info` section on this page. + +.. _cpp-writes-outside-subscription: + +Write Doesn't Match the Query Subscription +`````````````````````````````````````````` + +Given the :ref:`configuration ` for the Flexible +Sync realm detailed above, attempting to write this object results in a compensating write error because the object does not match the query +subscription: + +.. io-code-block:: + + .. input:: /examples/generated/cpp/sync-errors.snippet.compensating-write-example.cpp + :language: cpp + + .. output:: + :language: console + + Connection[2]: Session[10]: Received: ERROR "Client attempted a write that is not allowed; it has been reverted" (error_code=231, is_fatal=false, error_action=Warning) + +You will see the following error message in the App Services logs: + +.. code-block:: console + + Error: + + Client attempted a write that is not allowed; it has been reverted (ProtocolErrorCode=231) + + Details: + { + "Item": { + "ObjectID(\"6557ddb0bf050934870ca0f5\")": "write to ObjectID(\"6557ddb0bf050934870ca0f5\") + in table \"Item\" not allowed; object is outside of + the current query view" + } + } + +.. _cpp-writes-outside-permissions: + +Write Doesn't Match Permissions +``````````````````````````````` + +Given the :ref:`permissions ` in the Device Sync configuration detailed above, +attempting to write this object results in a compensating write error because the ``ownerId`` property does not match +the ``user.identifier()`` of the logged-in user: + +.. io-code-block:: + + .. input:: /examples/generated/cpp/sync-errors.snippet.write-not-matching-permissions.cpp + :language: cpp + + .. output:: + :language: console + + Connection[2]: Session[11]: Received: ERROR "Client attempted a write that is not allowed; it has been reverted" (error_code=231, is_fatal=false, error_action=Warning) + +You will see the following error message in the App Services logs: + +.. code-block:: console + + Error: + + Client attempted a write that is outside of permissions or query filters; it has been reverted (ProtocolErrorCode=231) + + Details: + { + "Item": { + "ObjectID(\"6557ddbabf050934870ca0f8\")": "write to ObjectID(\"6557ddbabf050934870ca0f8\") + in table \"Item\" was denied by write filter in role \"readOwnWriteOwn\"" + } + } + +.. _cpp-compensating-write-info: + +Compensating Write Error Information +```````````````````````````````````` + +.. versionadded:: v0.3.0-preview + +You can get additional information in the client about why a compensating +write occurs using the :cpp-sdk:`compensating_writes_info() +` +function, which provides an array of ``compensating_write_error_info`` +structs that contain: + +- The ``object_name`` of the object the client attempted to write +- The ``primary_key`` of the specific object +- The ``reason`` for the compensating write error + +This information is the same information you can find in the :ref:`App Services logs `. +The C++ SDK exposes this object on the client for convenience and +debugging purposes. + +The following shows an example of how you might log information +about compensating write errors: + +.. io-code-block:: + + .. input:: /examples/generated/cpp/sync-errors.snippet.get-compensating-write-error-info.cpp + :language: cpp + + .. output:: + :language: console + + A write was rejected with a compensating write error. + An object of type Item + was rejected because write to ObjectID("6557ddb0bf050934870ca0f5") in + table "Item" not allowed; object is outside of the current query view. + +- The ``Item`` in this message is ``Item`` object used in the :ref:`object model ` on this page. +- The ``table "Item"`` refers to the Atlas collection where this object would sync. +- The reason ``object is outside of the current query view`` in this message is because the query subscription was set to require the object's ``complexity`` property to be less than or equal to ``4``. The client attempted to write an object outside of this boundary. +- The primary key is the ``objectId`` of the specific object the client attempted to write. + +Group Writes for Improved Performance +------------------------------------- + +.. include:: /includes/sync-memory-performance.rst diff --git a/source/sdk/cpp/users/authenticate-users.txt b/source/sdk/cpp/users/authenticate-users.txt index 399b9731ca..7f9a414391 100644 --- a/source/sdk/cpp/users/authenticate-users.txt +++ b/source/sdk/cpp/users/authenticate-users.txt @@ -4,6 +4,13 @@ Authenticate Users - C++ SDK Preview ==================================== +.. meta:: + :keywords: code example + +.. facet:: + :name: genre + :values: reference + .. contents:: On this page :local: :backlinks: none @@ -14,6 +21,7 @@ Log In ------ .. versionchanged:: 0.2.0 Returns std::future instead of std::promise +.. versionchanged:: v0.4.0-preview Replaces deprecated realm::App(...) with realm::App(const realm::App::configuration&) .. _cpp-login-anonymous: