diff --git a/Realm/Realm/Handles/SyncUserHandle.cs b/Realm/Realm/Handles/SyncUserHandle.cs index 3c5de6bf54..11dcbb7cfd 100644 --- a/Realm/Realm/Handles/SyncUserHandle.cs +++ b/Realm/Realm/Handles/SyncUserHandle.cs @@ -241,7 +241,7 @@ public async Task CallFunctionAsync(AppHandle app, string name, string a NativeMethods.call_function(this, app, name, name.IntPtrLength(), args, args.IntPtrLength(), service, service.IntPtrLength(), GCHandle.ToIntPtr(tcsHandle), out var ex); ex.ThrowIfNecessary(); - return await tcs.Task; //.NET Host locks the dll when there's an exception here (coming from native) + return await tcs.Task; // .NET Host locks the dll when there's an exception here (coming from native) } finally { diff --git a/Realm/Realm/Sync/FlexibleSync/WaitForSyncMode.cs b/Realm/Realm/Sync/FlexibleSync/WaitForSyncMode.cs index 09b1e00b24..e7e453354c 100644 --- a/Realm/Realm/Sync/FlexibleSync/WaitForSyncMode.cs +++ b/Realm/Realm/Sync/FlexibleSync/WaitForSyncMode.cs @@ -61,7 +61,7 @@ public enum WaitForSyncMode /// With this mode enabled, Realm will always return as soon as the the subscription is created /// while any server data is being downloaded in the background. This update is not atomic, which /// means that if you subscribe to notifications using - /// + /// /// or /// you might see multiple events being fired as the server sends objects matching the subscription. /// diff --git a/Tests/Realm.Tests/Realm.Tests.csproj b/Tests/Realm.Tests/Realm.Tests.csproj index 1f0bfc53c9..f0874b632d 100644 --- a/Tests/Realm.Tests/Realm.Tests.csproj +++ b/Tests/Realm.Tests/Realm.Tests.csproj @@ -153,9 +153,9 @@ - Sync\BaasClient.cs + Sync\Baas\BaasClient.cs - + diff --git a/Tests/Realm.Tests/Sync/StaticQueriesTests.cs b/Tests/Realm.Tests/Sync/StaticQueriesTests.cs index 9b99056fcb..6e8fd2cb7c 100644 --- a/Tests/Realm.Tests/Sync/StaticQueriesTests.cs +++ b/Tests/Realm.Tests/Sync/StaticQueriesTests.cs @@ -183,7 +183,7 @@ public void RealmObjectAPI_Primitive_RealmToAtlas(TestCaseData testFunc, int timeout = 30000) { - if (BaasUri == null) + if (BaasUri == null && _baaSaasApiKey == null) { Assert.Ignore("Atlas App Services are not setup."); } @@ -132,7 +127,7 @@ public static async Task ExtractBaasSettingsAsync(string[] args) public static (string[] RemainingArgs, IDisposable? Logger) SetLoggerFromArgs(string[] args) { - var (extracted, remaining) = ArgumentHelper.ExtractArguments(args, "realmloglevel", "realmlogfile"); + var (extracted, remaining) = BaasClient.ExtractArguments(args, "realmloglevel", "realmlogfile"); if (extracted.TryGetValue("realmloglevel", out var logLevelStr) && Enum.TryParse(logLevelStr, out var logLevel)) { @@ -167,28 +162,33 @@ public static (string[] RemainingArgs, IDisposable? Logger) SetLoggerFromArgs(st private static async Task CreateBaasAppsAsync() { - if (_apps[AppConfigType.Default].AppId != string.Empty || BaasUri == null) + if (_apps[AppConfigType.Default].AppId != string.Empty || (BaasUri == null && _baaSaasApiKey == null)) { return; } -#if !UNITY - try - { - var cluster = ConfigHelpers.GetSetting("Cluster")!; - var apiKey = ConfigHelpers.GetSetting("ApiKey")!; - var privateApiKey = ConfigHelpers.GetSetting("PrivateApiKey")!; - var groupId = ConfigHelpers.GetSetting("GroupId")!; - var differentiator = ConfigHelpers.GetSetting("Differentiator") ?? "local"; + var cluster = ConfigHelpers.GetSetting("Cluster")!; + var apiKey = ConfigHelpers.GetSetting("ApiKey")!; + var privateApiKey = ConfigHelpers.GetSetting("PrivateApiKey")!; + var groupId = ConfigHelpers.GetSetting("GroupId")!; + var differentiator = ConfigHelpers.GetSetting("Differentiator") ?? "local"; - _baasClient ??= await BaasClient.Atlas(BaasUri, differentiator, TestHelpers.Output, cluster, apiKey, privateApiKey, groupId); + if (_baaSaasApiKey != null) + { + BaasUri = await BaasClient.GetOrDeployContainer(_baaSaasApiKey, differentiator, TestHelpers.Output); + _baasClient = await BaasClient.Docker(BaasUri, differentiator, TestHelpers.Output); } - catch + else if (!string.IsNullOrEmpty(cluster) && + !string.IsNullOrEmpty(apiKey) && + !string.IsNullOrEmpty(privateApiKey) && + !string.IsNullOrEmpty(groupId)) { + _baasClient = await BaasClient.Atlas(BaasUri!, differentiator, TestHelpers.Output, cluster, apiKey, privateApiKey, groupId); + } + else + { + _baasClient = await BaasClient.Docker(BaasUri!, "local", TestHelpers.Output); } -#endif - - _baasClient ??= await BaasClient.Docker(BaasUri, "local", TestHelpers.Output); _apps = await _baasClient.GetOrCreateApps(); } diff --git a/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/AsymmetricTestClass_generated.cs b/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/AsymmetricTestClass_generated.cs index f2e0d9dbd3..1badb02fe7 100644 --- a/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/AsymmetricTestClass_generated.cs +++ b/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/AsymmetricTestClass_generated.cs @@ -1,6 +1,7 @@ // #nullable enable +using MongoDB.Bson.Serialization; using Realms; using Realms.Schema; using Realms.Sync; @@ -23,6 +24,13 @@ [Woven(typeof(AsymmetricTestClassObjectHelper)), Realms.Preserve(AllMembers = true)] public partial class AsymmetricTestClass : IAsymmetricObject, INotifyPropertyChanged, IReflectableType { + + [Realms.Preserve] + static AsymmetricTestClass() + { + Realms.Serialization.RealmObjectSerializer.Register(new AsymmetricTestClassSerializer()); + } + /// /// Defines the schema for the class. /// @@ -37,7 +45,7 @@ public partial class AsymmetricTestClass : IAsymmetricObject, INotifyPropertyCha Realms.IRealmAccessor Realms.IRealmObjectBase.Accessor => Accessor; - internal IAsymmetricTestClassAccessor Accessor => _accessor ??= new AsymmetricTestClassUnmanagedAccessor(typeof(AsymmetricTestClass)); + private IAsymmetricTestClassAccessor Accessor => _accessor ??= new AsymmetricTestClassUnmanagedAccessor(typeof(AsymmetricTestClass)); /// [IgnoreDataMember, XmlIgnore] @@ -215,7 +223,7 @@ public override bool Equals(object? obj) return !IsValid; } - if (obj is not Realms.IRealmObjectBase iro) + if (!(obj is Realms.IRealmObjectBase iro)) { return false; } @@ -255,7 +263,7 @@ internal interface IAsymmetricTestClassAccessor : Realms.IRealmAccessor } [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] - internal class AsymmetricTestClassManagedAccessor : Realms.ManagedAccessor, IAsymmetricTestClassAccessor + private class AsymmetricTestClassManagedAccessor : Realms.ManagedAccessor, IAsymmetricTestClassAccessor { public int Int32Property { @@ -265,7 +273,7 @@ public int Int32Property } [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] - internal class AsymmetricTestClassUnmanagedAccessor : Realms.UnmanagedAccessor, IAsymmetricTestClassAccessor + private class AsymmetricTestClassUnmanagedAccessor : Realms.UnmanagedAccessor, IAsymmetricTestClassAccessor { public override ObjectSchema ObjectSchema => AsymmetricTestClass.RealmSchema; @@ -325,4 +333,44 @@ public override IDictionary GetDictionaryValue(string pr throw new MissingMemberException($"The object does not have a Realm dictionary property with name {propertyName}"); } } + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + private class AsymmetricTestClassSerializer : Realms.Serialization.RealmObjectSerializerBase + { + public override string SchemaName => "AsymmetricTestClass"; + + protected override void SerializeValue(MongoDB.Bson.Serialization.BsonSerializationContext context, BsonSerializationArgs args, AsymmetricTestClass value) + { + context.Writer.WriteStartDocument(); + + WriteValue(context, args, "Int32Property", value.Int32Property); + + context.Writer.WriteEndDocument(); + } + + protected override AsymmetricTestClass CreateInstance() => new AsymmetricTestClass(); + + protected override void ReadValue(AsymmetricTestClass instance, string name, BsonDeserializationContext context) + { + switch (name) + { + case "Int32Property": + instance.Int32Property = BsonSerializer.LookupSerializer().Deserialize(context); + break; + default: + context.Reader.SkipValue(); + break; + } + } + + protected override void ReadArrayElement(AsymmetricTestClass instance, string name, BsonDeserializationContext context) + { + // No persisted list/set properties to deserialize + } + + protected override void ReadDocumentField(AsymmetricTestClass instance, string name, string fieldName, BsonDeserializationContext context) + { + // No persisted dictionary properties to deserialize + } + } } diff --git a/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/EmbeddedTestClass_generated.cs b/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/EmbeddedTestClass_generated.cs index d194d39073..88a375227b 100644 --- a/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/EmbeddedTestClass_generated.cs +++ b/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/EmbeddedTestClass_generated.cs @@ -1,6 +1,7 @@ // #nullable enable +using MongoDB.Bson.Serialization; using Realms; using Realms.Schema; using Realms.Sync; @@ -23,6 +24,13 @@ [Woven(typeof(EmbeddedTestClassObjectHelper)), Realms.Preserve(AllMembers = true)] public partial class EmbeddedTestClass : IEmbeddedObject, INotifyPropertyChanged, IReflectableType { + + [Realms.Preserve] + static EmbeddedTestClass() + { + Realms.Serialization.RealmObjectSerializer.Register(new EmbeddedTestClassSerializer()); + } + /// /// Defines the schema for the class. /// @@ -37,7 +45,7 @@ public partial class EmbeddedTestClass : IEmbeddedObject, INotifyPropertyChanged Realms.IRealmAccessor Realms.IRealmObjectBase.Accessor => Accessor; - internal IEmbeddedTestClassAccessor Accessor => _accessor ??= new EmbeddedTestClassUnmanagedAccessor(typeof(EmbeddedTestClass)); + private IEmbeddedTestClassAccessor Accessor => _accessor ??= new EmbeddedTestClassUnmanagedAccessor(typeof(EmbeddedTestClass)); /// [IgnoreDataMember, XmlIgnore] @@ -219,7 +227,7 @@ public override bool Equals(object? obj) return !IsValid; } - if (obj is not Realms.IRealmObjectBase iro) + if (!(obj is Realms.IRealmObjectBase iro)) { return false; } @@ -259,7 +267,7 @@ internal interface IEmbeddedTestClassAccessor : Realms.IRealmAccessor } [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] - internal class EmbeddedTestClassManagedAccessor : Realms.ManagedAccessor, IEmbeddedTestClassAccessor + private class EmbeddedTestClassManagedAccessor : Realms.ManagedAccessor, IEmbeddedTestClassAccessor { public int Int32Property { @@ -269,7 +277,7 @@ public int Int32Property } [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] - internal class EmbeddedTestClassUnmanagedAccessor : Realms.UnmanagedAccessor, IEmbeddedTestClassAccessor + private class EmbeddedTestClassUnmanagedAccessor : Realms.UnmanagedAccessor, IEmbeddedTestClassAccessor { public override ObjectSchema ObjectSchema => EmbeddedTestClass.RealmSchema; @@ -329,4 +337,44 @@ public override IDictionary GetDictionaryValue(string pr throw new MissingMemberException($"The object does not have a Realm dictionary property with name {propertyName}"); } } + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + private class EmbeddedTestClassSerializer : Realms.Serialization.RealmObjectSerializerBase + { + public override string SchemaName => "EmbeddedTestClass"; + + protected override void SerializeValue(MongoDB.Bson.Serialization.BsonSerializationContext context, BsonSerializationArgs args, EmbeddedTestClass value) + { + context.Writer.WriteStartDocument(); + + WriteValue(context, args, "Int32Property", value.Int32Property); + + context.Writer.WriteEndDocument(); + } + + protected override EmbeddedTestClass CreateInstance() => new EmbeddedTestClass(); + + protected override void ReadValue(EmbeddedTestClass instance, string name, BsonDeserializationContext context) + { + switch (name) + { + case "Int32Property": + instance.Int32Property = BsonSerializer.LookupSerializer().Deserialize(context); + break; + default: + context.Reader.SkipValue(); + break; + } + } + + protected override void ReadArrayElement(EmbeddedTestClass instance, string name, BsonDeserializationContext context) + { + // No persisted list/set properties to deserialize + } + + protected override void ReadDocumentField(EmbeddedTestClass instance, string name, string fieldName, BsonDeserializationContext context) + { + // No persisted dictionary properties to deserialize + } + } } diff --git a/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/JustForObjectReference_generated.cs b/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/JustForObjectReference_generated.cs index 7443657274..154f5829c6 100644 --- a/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/JustForObjectReference_generated.cs +++ b/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/JustForObjectReference_generated.cs @@ -1,6 +1,7 @@ // #nullable enable +using MongoDB.Bson.Serialization; using Realms; using Realms.Schema; using Realms.Sync; @@ -23,6 +24,13 @@ [Woven(typeof(JustForObjectReferenceObjectHelper)), Realms.Preserve(AllMembers = true)] public partial class JustForObjectReference : IRealmObject, INotifyPropertyChanged, IReflectableType { + + [Realms.Preserve] + static JustForObjectReference() + { + Realms.Serialization.RealmObjectSerializer.Register(new JustForObjectReferenceSerializer()); + } + /// /// Defines the schema for the class. /// @@ -37,7 +45,7 @@ public partial class JustForObjectReference : IRealmObject, INotifyPropertyChang Realms.IRealmAccessor Realms.IRealmObjectBase.Accessor => Accessor; - internal IJustForObjectReferenceAccessor Accessor => _accessor ??= new JustForObjectReferenceUnmanagedAccessor(typeof(JustForObjectReference)); + private IJustForObjectReferenceAccessor Accessor => _accessor ??= new JustForObjectReferenceUnmanagedAccessor(typeof(JustForObjectReference)); /// [IgnoreDataMember, XmlIgnore] @@ -216,7 +224,7 @@ public override bool Equals(object? obj) return !IsValid; } - if (obj is not Realms.IRealmObjectBase iro) + if (!(obj is Realms.IRealmObjectBase iro)) { return false; } @@ -256,7 +264,7 @@ internal interface IJustForObjectReferenceAccessor : Realms.IRealmAccessor } [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] - internal class JustForObjectReferenceManagedAccessor : Realms.ManagedAccessor, IJustForObjectReferenceAccessor + private class JustForObjectReferenceManagedAccessor : Realms.ManagedAccessor, IJustForObjectReferenceAccessor { public RootRealmClass? UseAsBacklink { @@ -266,7 +274,7 @@ public RootRealmClass? UseAsBacklink } [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] - internal class JustForObjectReferenceUnmanagedAccessor : Realms.UnmanagedAccessor, IJustForObjectReferenceAccessor + private class JustForObjectReferenceUnmanagedAccessor : Realms.UnmanagedAccessor, IJustForObjectReferenceAccessor { public override ObjectSchema ObjectSchema => JustForObjectReference.RealmSchema; @@ -326,4 +334,44 @@ public override IDictionary GetDictionaryValue(string pr throw new MissingMemberException($"The object does not have a Realm dictionary property with name {propertyName}"); } } + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + private class JustForObjectReferenceSerializer : Realms.Serialization.RealmObjectSerializerBase + { + public override string SchemaName => "JustForObjectReference"; + + protected override void SerializeValue(MongoDB.Bson.Serialization.BsonSerializationContext context, BsonSerializationArgs args, JustForObjectReference value) + { + context.Writer.WriteStartDocument(); + + WriteValue(context, args, "UseAsBacklink", value.UseAsBacklink); + + context.Writer.WriteEndDocument(); + } + + protected override JustForObjectReference CreateInstance() => new JustForObjectReference(); + + protected override void ReadValue(JustForObjectReference instance, string name, BsonDeserializationContext context) + { + switch (name) + { + case "UseAsBacklink": + instance.UseAsBacklink = Realms.Serialization.RealmObjectSerializer.LookupSerializer()!.DeserializeById(context); + break; + default: + context.Reader.SkipValue(); + break; + } + } + + protected override void ReadArrayElement(JustForObjectReference instance, string name, BsonDeserializationContext context) + { + // No persisted list/set properties to deserialize + } + + protected override void ReadDocumentField(JustForObjectReference instance, string name, string fieldName, BsonDeserializationContext context) + { + // No persisted dictionary properties to deserialize + } + } } diff --git a/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/RootRealmClass_generated.cs b/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/RootRealmClass_generated.cs index 3b15193961..b78d7fb1a3 100644 --- a/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/RootRealmClass_generated.cs +++ b/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/RootRealmClass_generated.cs @@ -1,6 +1,7 @@ // #nullable enable +using MongoDB.Bson.Serialization; using Realms; using Realms.Schema; using Realms.Sync; @@ -23,6 +24,13 @@ [Woven(typeof(RootRealmClassObjectHelper)), Realms.Preserve(AllMembers = true)] public partial class RootRealmClass : IRealmObject, INotifyPropertyChanged, IReflectableType { + + [Realms.Preserve] + static RootRealmClass() + { + Realms.Serialization.RealmObjectSerializer.Register(new RootRealmClassSerializer()); + } + /// /// Defines the schema for the class. /// @@ -46,7 +54,7 @@ public partial class RootRealmClass : IRealmObject, INotifyPropertyChanged, IRef Realms.IRealmAccessor Realms.IRealmObjectBase.Accessor => Accessor; - internal IRootRealmClassAccessor Accessor => _accessor ??= new RootRealmClassUnmanagedAccessor(typeof(RootRealmClass)); + private IRootRealmClassAccessor Accessor => _accessor ??= new RootRealmClassUnmanagedAccessor(typeof(RootRealmClass)); /// [IgnoreDataMember, XmlIgnore] @@ -246,7 +254,7 @@ public override bool Equals(object? obj) return !IsValid; } - if (obj is not Realms.IRealmObjectBase iro) + if (!(obj is Realms.IRealmObjectBase iro)) { return false; } @@ -304,7 +312,7 @@ internal interface IRootRealmClassAccessor : Realms.IRealmAccessor } [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] - internal class RootRealmClassManagedAccessor : Realms.ManagedAccessor, IRootRealmClassAccessor + private class RootRealmClassManagedAccessor : Realms.ManagedAccessor, IRootRealmClassAccessor { public JustForObjectReference? JustForRef { @@ -424,7 +432,7 @@ public System.Linq.IQueryable JustBackLink } [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] - internal class RootRealmClassUnmanagedAccessor : Realms.UnmanagedAccessor, IRootRealmClassAccessor + private class RootRealmClassUnmanagedAccessor : Realms.UnmanagedAccessor, IRootRealmClassAccessor { public override ObjectSchema ObjectSchema => RootRealmClass.RealmSchema; @@ -544,4 +552,90 @@ public override IDictionary GetDictionaryValue(string pr }; } } + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + private class RootRealmClassSerializer : Realms.Serialization.RealmObjectSerializerBase + { + public override string SchemaName => "RootRealmClass"; + + protected override void SerializeValue(MongoDB.Bson.Serialization.BsonSerializationContext context, BsonSerializationArgs args, RootRealmClass value) + { + context.Writer.WriteStartDocument(); + + WriteValue(context, args, "JustForRef", value.JustForRef); + WriteList(context, args, "ReferenceList", value.ReferenceList); + WriteList(context, args, "PrimitiveList", value.PrimitiveList); + WriteDictionary(context, args, "ReferenceDictionary", value.ReferenceDictionary); + WriteDictionary(context, args, "PrimitiveDictionary", value.PrimitiveDictionary); + WriteSet(context, args, "ReferenceSet", value.ReferenceSet); + WriteSet(context, args, "PrimitiveSet", value.PrimitiveSet); + WriteValue(context, args, "Counter", value.Counter); + WriteValue(context, args, "RealmValue", value.RealmValue); + + context.Writer.WriteEndDocument(); + } + + protected override RootRealmClass CreateInstance() => new RootRealmClass(); + + protected override void ReadValue(RootRealmClass instance, string name, BsonDeserializationContext context) + { + switch (name) + { + case "JustForRef": + instance.JustForRef = Realms.Serialization.RealmObjectSerializer.LookupSerializer()!.DeserializeById(context); + break; + case "Counter": + instance.Counter = BsonSerializer.LookupSerializer>().Deserialize(context); + break; + case "RealmValue": + instance.RealmValue = BsonSerializer.LookupSerializer().Deserialize(context); + break; + case "ReferenceList": + case "PrimitiveList": + case "ReferenceSet": + case "PrimitiveSet": + ReadArray(instance, name, context); + break; + case "ReferenceDictionary": + case "PrimitiveDictionary": + ReadDictionary(instance, name, context); + break; + default: + context.Reader.SkipValue(); + break; + } + } + + protected override void ReadArrayElement(RootRealmClass instance, string name, BsonDeserializationContext context) + { + switch (name) + { + case "ReferenceList": + instance.ReferenceList.Add(Realms.Serialization.RealmObjectSerializer.LookupSerializer()!.DeserializeById(context)!); + break; + case "PrimitiveList": + instance.PrimitiveList.Add(BsonSerializer.LookupSerializer().Deserialize(context)); + break; + case "ReferenceSet": + instance.ReferenceSet.Add(Realms.Serialization.RealmObjectSerializer.LookupSerializer()!.DeserializeById(context)!); + break; + case "PrimitiveSet": + instance.PrimitiveSet.Add(BsonSerializer.LookupSerializer().Deserialize(context)); + break; + } + } + + protected override void ReadDocumentField(RootRealmClass instance, string name, string fieldName, BsonDeserializationContext context) + { + switch (name) + { + case "ReferenceDictionary": + instance.ReferenceDictionary[fieldName] = Realms.Serialization.RealmObjectSerializer.LookupSerializer()!.DeserializeById(context)!; + break; + case "PrimitiveDictionary": + instance.PrimitiveDictionary[fieldName] = BsonSerializer.LookupSerializer().Deserialize(context); + break; + } + } + } } diff --git a/Tools/DeployApps/BaasClient.cs b/Tools/DeployApps/BaasClient.cs index 3f785f900a..2fb9e63f23 100644 --- a/Tools/DeployApps/BaasClient.cs +++ b/Tools/DeployApps/BaasClient.cs @@ -18,7 +18,6 @@ using System; using System.Collections.Generic; -using System.Data; using System.IO; using System.Linq; using System.Net; @@ -46,41 +45,6 @@ public static class AppConfigType public const string FlexibleSync = "flx"; } - public static class ArgumentHelper - { - public static (Dictionary Extracted, string[] RemainingArgs) ExtractArguments(string[] args, params string[] toExtract) - { - if (args == null) - { - throw new ArgumentNullException(nameof(args)); - } - - var extracted = new Dictionary(); - var remainingArgs = new List(); - for (var i = 0; i < args.Length; i++) - { - if (!toExtract.Any(name => ExtractArg(i, name))) - { - remainingArgs.Add(args[i]); - } - } - - return (extracted, remainingArgs.ToArray()); - - bool ExtractArg(int index, string name) - { - var arg = args[index]; - if (arg.StartsWith($"--{name}=")) - { - extracted[name] = arg.Replace($"--{name}=", string.Empty); - return true; - } - - return false; - } - } - } - public class BaasClient { public class FunctionReturn @@ -143,7 +107,7 @@ public class FunctionReturn return { status: 'pending' }; } - return { status: 'success' }; + return { status: 'success' }; } // will not reset the password @@ -271,7 +235,7 @@ public static async Task Atlas(Uri baseUri, string differentiator, T throw new ArgumentNullException(nameof(args)); } - var (extracted, remaining) = ArgumentHelper.ExtractArguments(args, "baasurl", "baascluster", "baasapikey", "baasprivateapikey", "baasprojectid", "baasdifferentiator"); + var (extracted, remaining) = ExtractArguments(args, "baasurl", "baascluster", "baasapikey", "baasprivateapikey", "baasprojectid", "baasdifferentiator"); if (!extracted.TryGetValue("baasurl", out var baseUrl) || string.IsNullOrEmpty(baseUrl)) { @@ -299,6 +263,13 @@ private async Task Authenticate(string provider, object credentials) _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authDoc["access_token"].AsString); } + public static async Task GetOrDeployContainer(string baasaasApiKey, string differentiator, TextWriter output) + { + var baaSaasClient = new BaasaasClient(baasaasApiKey); + var uriString = await baaSaasClient.GetOrDeployContainer(differentiator, output); + return new Uri(uriString); + } + public async Task> GetOrCreateApps() { var apps = await GetApps(); @@ -626,16 +597,6 @@ private async Task CreateService(BaasApp app, string name, string type, return response!["_id"].AsString; } - private static HttpContent GetJsonContent(object obj) - { - var json = obj is BsonDocument doc ? doc.ToJson() : obj.ToJson(); - var content = new StringContent(json); - - content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - - return content; - } - private async Task CreateMongodbService(BaasApp app, object syncConfig) { var serviceName = _clusterName == null ? "mongodb" : "mongodb-atlas"; @@ -728,7 +689,6 @@ private async Task RefreshAccessTokenAsync() throw new Exception($"An error ({response.StatusCode}) occurred while executing {method} {relativePath}: {content}"); } - response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); if (!string.IsNullOrWhiteSpace(json)) @@ -739,6 +699,64 @@ private async Task RefreshAccessTokenAsync() return default; } + public static HttpContent GetJsonContent(object obj) + { + string jsonContent; + + if (obj is Array arr) + { + var bsonArray = new BsonArray(); + foreach (var elem in arr) + { + bsonArray.Add(elem.ToBsonDocument()); + } + + jsonContent = bsonArray.ToJson(); + } + else + { + jsonContent = obj is BsonDocument doc ? doc.ToJson() : obj.ToJson(); + } + + var content = new StringContent(jsonContent); + + content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + return content; + } + + public static (Dictionary Extracted, string[] RemainingArgs) ExtractArguments(string[] args, params string[] toExtract) + { + if (args == null) + { + throw new ArgumentNullException(nameof(args)); + } + + var extracted = new Dictionary(); + var remainingArgs = new List(); + for (var i = 0; i < args.Length; i++) + { + if (!toExtract.Any(name => ExtractArg(i, name))) + { + remainingArgs.Add(args[i]); + } + } + + return (extracted, remainingArgs.ToArray()); + + bool ExtractArg(int index, string name) + { + var arg = args[index]; + if (arg.StartsWith($"--{name}=")) + { + extracted[name] = arg.Replace($"--{name}=", string.Empty); + return true; + } + + return false; + } + } + public class BaasApp { public string AppId { get; } @@ -863,6 +881,180 @@ public static (object Schema, object Rules) Foos(string partitionKeyType, string }, GenericBaasRule(differentiator, "foos")); } + + private class BaasaasClient + { + private const string _baseUrl = "https://us-east-1.aws.data.mongodb-api.com/app/baas-container-service-autzb/endpoint/"; + private readonly HttpClient _client; + + public BaasaasClient(string apiKey) + { + _client = new(); + _client.BaseAddress = new Uri(_baseUrl); + _client.DefaultRequestHeaders.TryAddWithoutValidation("apiKey", apiKey); + } + + public async Task GetOrDeployContainer(string differentiator, TextWriter output) + { + output.WriteLine("Looking for existing containers on BaaSaas."); + var containers = await GetContainers(); + + if (containers?.Length > 0) + { + var userId = await GetCurrentUserId(); + var existingContainer = containers + .FirstOrDefault(c => c.CreatorId == userId && c.Tags.Any(t => t.Key == "DIFFERENTIATOR" && t.Value == differentiator)); + + if (existingContainer is not null) + { + output.WriteLine($"Container with id {existingContainer.ContainerId} found."); + + if (!existingContainer.IsRunning) + { + output.WriteLine($"Waiting for container with id {existingContainer.ContainerId} to be running."); + await WaitForContainer(existingContainer.ContainerId); + } + + return existingContainer.HttpUrl; + } + } + + output.WriteLine($"No container found, starting a new one."); + var containerId = await StartContainer(differentiator); + + output.WriteLine($"Container with id {containerId} started, waiting for it to be running."); + var container = await WaitForContainer(containerId); + + return container.HttpUrl; + } + + private Task GetContainers() + { + return CallEndpointAsync(HttpMethod.Get, "listContainers"); + } + + // Useful for debugging purposes + private async Task StopAllContainers() + { + var containers = await GetContainers(); + var userId = await GetCurrentUserId(); + + var existingContainers = containers! + .Where(c => c.CreatorId == userId); + + foreach (var container in existingContainers) + { + await StopContainer(container.ContainerId); + } + } + + private Task StopContainer(string id) + { + return CallEndpointAsync(HttpMethod.Post, $"stopContainer?id={id}"); + } + + private async Task GetCurrentUserId() + { + return (await CallEndpointAsync(HttpMethod.Get, "userinfo"))!["id"].AsString; + } + + private async Task StartContainer(string differentiator) + { + var response = await CallEndpointAsync(HttpMethod.Post, "startContainer", new[] + { + new + { + key = "DIFFERENTIATOR", + value = differentiator, + } + }); + + return response?["id"].AsString!; + } + + private async Task WaitForContainer(string containerId, int maxRetries = 100) + { + while (maxRetries > 0) + { + maxRetries -= 1; + + try + { + var containers = await GetContainers(); + var container = containers!.FirstOrDefault(c => c.ContainerId == containerId); + + if (container?.IsRunning == true) + { + // Checking that Baas started correctly, and not only the container + var response = await _client.GetAsync($"{container.HttpUrl}/api/private/v1.0/version"); + if (response.IsSuccessStatusCode) + { + return container; + } + } + } + catch + { + } + + await Task.Delay(2000); + } + + throw new Exception($"Container with id={containerId} was not found or ready after {maxRetries} retrues"); + } + + private async Task CallEndpointAsync(HttpMethod method, string relativePath, object? payload = null) + { + using var message = new HttpRequestMessage(method, new Uri(relativePath, UriKind.Relative)); + + if (payload is not null) + { + message.Content = GetJsonContent(payload); + } + + var response = await _client.SendAsync(message); + response.EnsureSuccessStatusCode(); + + var json = await response.Content.ReadAsStringAsync(); + + if (!string.IsNullOrWhiteSpace(json)) + { + return BsonSerializer.Deserialize(json); + } + + return default!; + } + + [BsonIgnoreExtraElements] + public class ContainerInfo + { + [BsonElement("id")] + public string ContainerId { get; set; } = null!; + + [BsonElement("httpUrl")] + public string HttpUrl { get; set; } = null!; + + [BsonElement("lastStatus")] + public string LastStatus { get; set; } = null!; + + [BsonElement("tags")] + public List Tags { get; set; } = null!; + + [BsonElement("creatorId")] + public string CreatorId { get; set; } = null!; + + public bool IsRunning => LastStatus == "RUNNING"; + } + + public class Tag + { + [BsonElement("key")] + public string Key { get; set; } = null!; + + [BsonElement("value")] + public string Value { get; set; } = null!; + } + } } #if !NETCOREAPP2_1_OR_GREATER @@ -880,4 +1072,5 @@ internal static class DictionaryExtensions } } #endif + }