Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RNET-1109: Add App.UpdateBaseUri #3587

Merged
merged 4 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
## vNext (TBD)

### Enhancements
* None
* Added an experimental API to update the base url for an application at runtime - `App.UpdateBaseUriAsync()`. This intended to be used for roaming between edge server and cloud. (Issue [#3521](https://github.com/realm/realm-dotnet/issues/3521))

### Fixed
* The returned value from `MongoClient.Collection.FindOneAsync` is now a nullable document to more explicitly convey that `null` may be returned in case no object matched the filter. ([PR #3586](https://github.com/realm/realm-dotnet/pull/3586))
* The returned value from `MongoClient.Collection.FindOneAsync` is now a nullable document to more explicitly convey that `null` may be returned in case no object matched the filter. (PR [#3586](https://github.com/realm/realm-dotnet/pull/3586))

### Compatibility
* Realm Studio: 15.0.0 or later.
Expand Down
23 changes: 23 additions & 0 deletions Realm/Realm/Handles/AppHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ public static extern IntPtr get_user_for_testing(

[DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_get_default_url", CallingConvention = CallingConvention.Cdecl)]
public static extern StringValue get_default_url(out NativeException ex);

[DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_update_base_url", CallingConvention = CallingConvention.Cdecl)]
public static extern void update_base_uri(AppHandle appHandle,
[MarshalAs(UnmanagedType.LPWStr)] string url_buf, IntPtr url_len,
IntPtr tcs_ptr,
out NativeException ex);
}

static AppHandle()
Expand Down Expand Up @@ -343,6 +349,23 @@ public Uri GetBaseUri()
return new Uri(uriString);
}

public async Task UpdateBaseUriAsync(Uri? newUri)
{
var tcs = new TaskCompletionSource();
var tcsHandle = GCHandle.Alloc(tcs);
try
{
var url = newUri?.ToString().TrimEnd('/') ?? string.Empty;
NativeMethods.update_base_uri(this, url, (IntPtr)url.Length, GCHandle.ToIntPtr(tcsHandle), out var ex);
ex.ThrowIfNecessary();
await tcs.Task;
}
finally
{
tcsHandle.Free();
}
}

public string GetId()
{
var value = NativeMethods.get_id(this, out var ex);
Expand Down
64 changes: 64 additions & 0 deletions Realm/Realm/Helpers/ExperimentalAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#if !NET8_0_OR_GREATER

using System.ComponentModel;

namespace System.Diagnostics.CodeAnalysis;

/// <summary>
/// Indicates that an API is experimental and it may change in the future.
/// </summary>
/// <remarks>
/// This attribute allows call sites to be flagged with a diagnostic that indicates that an experimental
/// feature is used. Authors can use this attribute to ship preview features in their assemblies.
/// <br/>
/// This is a polyfill of the ExperimentalAttribute added in .NET 8.
/// </remarks>
[EditorBrowsable(EditorBrowsableState.Never)]
[AttributeUsage(
AttributeTargets.Assembly
| AttributeTargets.Module
| AttributeTargets.Class
| AttributeTargets.Struct
| AttributeTargets.Enum
| AttributeTargets.Constructor
| AttributeTargets.Method
| AttributeTargets.Property
| AttributeTargets.Field
| AttributeTargets.Event
| AttributeTargets.Interface
| AttributeTargets.Delegate,
Inherited = false)]
public sealed class ExperimentalAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="ExperimentalAttribute"/> class, specifying the ID that the compiler will use
/// when reporting a use of the API the attribute applies to.
/// </summary>
/// <param name="diagnosticId">The ID that the compiler will use when reporting a use of the API the attribute applies to.</param>
public ExperimentalAttribute(string diagnosticId)
{
DiagnosticId = diagnosticId;
}

/// <summary>
/// Gets the ID that the compiler will use when reporting a use of the API the attribute applies to.
/// </summary>
/// <value>The unique diagnostic ID.</value>
/// <remarks>
/// <para>The diagnostic ID is shown in build output for warnings and errors.</para>
/// <para>This property represents the unique ID that can be used to suppress the warnings or errors, if needed.</para>
/// </remarks>
public string DiagnosticId { get; }

/// <summary>
/// <para>Gets or sets the URL for corresponding documentation.</para>
/// <para>The API accepts a format string instead of an actual URL, creating a generic URL that includes the diagnostic ID.</para>
/// </summary>
/// <value>The format string that represents a URL to corresponding documentation.</value>
/// <remarks>
/// <para>An example format string is <c>https://contoso.com/obsoletion-warnings/{0}</c>.</para>
/// </remarks>
public string? UrlFormat { get; set; }
}

#endif
22 changes: 22 additions & 0 deletions Realm/Realm/Sync/App.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
////////////////////////////////////////////////////////////////////////////

using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -270,6 +271,27 @@ public Task DeleteUserFromServerAsync(User user)
return Handle.DeleteUserAsync(user.Handle);
}

/// <summary>
/// Temporarily overrides the <see cref="AppConfiguration.BaseUri"/> value from <see cref="AppConfiguration"/>
/// with a new <paramref name="newUri"/> value used for communicating with the server.
/// </summary>
/// <param name="newUri">
/// The new uri that will be used for communicating with the server. If set to <c>null</c>, the base uri will
/// be reset to its default value.
/// </param>
/// <returns>An awaitable <see cref="Task"/> that represents the asynchronous operation.</returns>
/// <remarks>
/// The App will revert to using the value in [AppConfiguration] when it is restarted.
/// <br/>
/// This API must be called after sync sessions have been manually stopped and at a point
/// where the server at <paramref name="newUri"/> is reachable. Once the base uri has been
/// updated, sync sessions should be resumed and the user needs to reauthenticate.
/// <br/>
/// This API is experimental and subject to change without a major version increase.
/// </remarks>
[Experimental("Rlm001", UrlFormat = "www.mongodb.com/docs/atlas/app-services/edge-server/connect/#roaming-between-edge-servers")]
public Task UpdateBaseUriAsync(Uri? newUri) => Handle.UpdateBaseUriAsync(newUri);

/// <inheritdoc />
public override bool Equals(object? obj)
{
Expand Down
40 changes: 40 additions & 0 deletions Tests/Realm.Tests/Sync/AppTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
using Baas;
using NUnit.Framework;
using Realms.Logging;
using Realms.PlatformHelpers;
Expand Down Expand Up @@ -398,5 +399,44 @@ public void RealmConfigurationBaseUrl_ReturnsExpectedValue()
var config = new AppConfiguration("abc");
Assert.That(config.BaseUri, Is.EqualTo(new Uri("https://services.cloud.mongodb.com")));
}

[Test]
public void App_UpdateBaseUri_UpdatesBaseUri()
{
SyncTestHelpers.RunBaasTestAsync(async () =>
{
var appConfig = SyncTestHelpers.GetAppConfig(AppConfigType.FlexibleSync);
appConfig.BaseUri = new Uri("https://services.mongodb.com");
var app = CreateApp(appConfig);

Assert.That(app.BaseUri, Is.EqualTo(new Uri("https://services.mongodb.com")));

#pragma warning disable Rlm001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
await app.UpdateBaseUriAsync(SyncTestHelpers.BaasUri!);
#pragma warning restore Rlm001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

Assert.That(app.BaseUri, Is.EqualTo(SyncTestHelpers.BaasUri));
});
}

[Test]
public void App_UpdateBaseUri_WhenUnreachable_Throws()
{
SyncTestHelpers.RunBaasTestAsync(async () =>
{
var appConfig = SyncTestHelpers.GetAppConfig(AppConfigType.FlexibleSync);
appConfig.BaseUri = new Uri("https://services.mongodb.com");
var app = CreateApp(appConfig);

Assert.That(app.BaseUri, Is.EqualTo(new Uri("https://services.mongodb.com")));

#pragma warning disable Rlm001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var ex = await TestHelpers.AssertThrows<AppException>(() => app.UpdateBaseUriAsync(new Uri("https://google.com")));
#pragma warning restore Rlm001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

Assert.That(ex.Message, Does.Contain("404"));
Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.NotFound));
});
}
}
}
19 changes: 19 additions & 0 deletions wrappers/src/app_cs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,25 @@ extern "C" {
});
}

REALM_EXPORT void shared_app_update_base_url(SharedApp& app, uint16_t* url_buf, size_t url_len, void* tcs_ptr, NativeException::Marshallable& ex)
{
return handle_errors(ex, [&]() {
std::string url(Utf16StringAccessor(url_buf, url_len));

app->update_base_url(url, [tcs_ptr](util::Optional<AppError> err) {
if (err) {
auto& err_copy = *err;
MarshaledAppError app_error(err_copy);

s_void_callback(tcs_ptr, app_error);
}
else {
s_void_callback(tcs_ptr, MarshaledAppError());
}
});
});
}

#pragma region EmailPassword

REALM_EXPORT void shared_app_email_register_user(SharedApp& app, uint16_t* username_buf, size_t username_len, uint16_t* password_buf, size_t password_len, void* tcs_ptr, NativeException::Marshallable& ex)
Expand Down
Loading