From 46bea2beb4f805b74af0c675e311b7de687cf0b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 15 Dec 2022 11:11:41 +0100 Subject: [PATCH] Disallow embedded objects in RealmValue (capture in type system) --- common/lib/src/realm_types.dart | 13 +++++++++-- lib/src/realm_object.dart | 4 ++-- test/realm_value_test.dart | 11 +++++++++- test/realm_value_test.g.dart | 39 +++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 5 deletions(-) diff --git a/common/lib/src/realm_types.dart b/common/lib/src/realm_types.dart index 84d1a96052..1025ed97f7 100644 --- a/common/lib/src/realm_types.dart +++ b/common/lib/src/realm_types.dart @@ -105,6 +105,15 @@ class Decimal128 {} // TODO Support decimal128 datatype https://github.com/realm /// @nodoc abstract class RealmObjectMarker {} +/// @nodoc +abstract class TopLevelObjectMarker implements RealmObjectMarker {} + +/// @nodoc +abstract class EmbeddedObjectMarker implements RealmObjectMarker {} + +/// @nodoc +abstract class AsymmetricObjectMarker implements RealmObjectMarker {} + /// A type that can represent any valid realm data type, except collections and embedded objects. /// /// You can use [RealmValue] to declare fields on realm models, in which case it must be non-nullable, @@ -148,7 +157,7 @@ class RealmValue { const RealmValue.int(int i) : this._(i); const RealmValue.double(double d) : this._(d); // TODO: RealmObjectMarker introduced to avoid dependency inversion. It would be better if we could use RealmObject directly. https://github.com/realm/realm-dart/issues/701 - const RealmValue.realmObject(RealmObjectMarker o) : this._(o); + const RealmValue.realmObject(TopLevelObjectMarker o) : this._(o); const RealmValue.dateTime(DateTime timestamp) : this._(timestamp); const RealmValue.objectId(ObjectId id) : this._(id); // const RealmValue.decimal128(Decimal128 decimal) : this._(decimal); // not supported yet @@ -162,7 +171,7 @@ class RealmValue { o is int || o is Float || o is double || - o is RealmObjectMarker || + o is TopLevelObjectMarker || o is DateTime || o is ObjectId || // o is Decimal128 || // not supported yet diff --git a/lib/src/realm_object.dart b/lib/src/realm_object.dart index 798fb64bce..152bb8cfec 100644 --- a/lib/src/realm_object.dart +++ b/lib/src/realm_object.dart @@ -432,10 +432,10 @@ mixin RealmObjectBase on RealmEntity implements RealmObjectMarker, Finalizable { } /// @nodoc -mixin RealmObject on RealmObjectBase {} +mixin RealmObject on RealmObjectBase implements TopLevelObjectMarker {} /// @nodoc -mixin EmbeddedObject on RealmObjectBase {} +mixin EmbeddedObject on RealmObjectBase implements EmbeddedObjectMarker {} extension EmbeddedObjectExtension on EmbeddedObject { /// Retrieve the [parent] object of this embedded object. diff --git a/test/realm_value_test.dart b/test/realm_value_test.dart index 8456e1291f..8da5472583 100644 --- a/test/realm_value_test.dart +++ b/test/realm_value_test.dart @@ -25,6 +25,11 @@ import 'test.dart'; part 'realm_value_test.g.dart'; +@RealmModel(ObjectType.embeddedObject) +class _TuckedIn { + int x = 42; +} + @RealmModel() class _AnythingGoes { @Indexed() @@ -47,7 +52,7 @@ void main() { Uuid.v4(), ]; - final config = Configuration.inMemory([AnythingGoes.schema]); + final config = Configuration.inMemory([AnythingGoes.schema, TuckedIn.schema]); final realm = getRealm(config); for (final x in values) { @@ -62,6 +67,10 @@ void main() { expect(() => realm.write(() => realm.add(AnythingGoes(oneAny: RealmValue.from([1, 2])))), throwsArgumentError); }); + test('Embedded object not allowed in RealmValue', () { + expect(() => realm.write(() => realm.add(AnythingGoes(oneAny: RealmValue.from(TuckedIn())))), throwsArgumentError); + }); + for (final x in values) { test('Switch $x', () { final something = AnythingGoes(oneAny: RealmValue.from(x)); diff --git a/test/realm_value_test.g.dart b/test/realm_value_test.g.dart index 52c0b2ca65..a1e5e479c9 100644 --- a/test/realm_value_test.g.dart +++ b/test/realm_value_test.g.dart @@ -6,6 +6,45 @@ part of 'realm_value_test.dart'; // RealmObjectGenerator // ************************************************************************** +class TuckedIn extends _TuckedIn + with RealmEntity, RealmObjectBase, EmbeddedObject { + static var _defaultsSet = false; + + TuckedIn({ + int x = 42, + }) { + if (!_defaultsSet) { + _defaultsSet = RealmObjectBase.setDefaults({ + 'x': 42, + }); + } + RealmObjectBase.set(this, 'x', x); + } + + TuckedIn._(); + + @override + int get x => RealmObjectBase.get(this, 'x') as int; + @override + set x(int value) => RealmObjectBase.set(this, 'x', value); + + @override + Stream> get changes => + RealmObjectBase.getChanges(this); + + @override + TuckedIn freeze() => RealmObjectBase.freezeObject(this); + + static SchemaObject get schema => _schema ??= _initSchema(); + static SchemaObject? _schema; + static SchemaObject _initSchema() { + RealmObjectBase.registerFactory(TuckedIn._); + return const SchemaObject(ObjectType.embeddedObject, TuckedIn, 'TuckedIn', [ + SchemaProperty('x', RealmPropertyType.int), + ]); + } +} + class AnythingGoes extends _AnythingGoes with RealmEntity, RealmObjectBase, RealmObject { AnythingGoes({