diff --git a/src/SIL.Harmony/Adapters/CustomAdapterProvider.cs b/src/SIL.Harmony/Adapters/CustomAdapterProvider.cs index e2b918a..7de660f 100644 --- a/src/SIL.Harmony/Adapters/CustomAdapterProvider.cs +++ b/src/SIL.Harmony/Adapters/CustomAdapterProvider.cs @@ -6,18 +6,21 @@ namespace SIL.Harmony.Adapters; -public class CustomAdapterProvider : IObjectAdapter +public class CustomAdapterProvider : IObjectAdapterProvider where TCommonInterface : class where TCustomAdapter : class, ICustomAdapter, IPolyType { - public CustomAdapterProvider() + private readonly ObjectTypeListBuilder _objectTypeListBuilder; + private readonly List _objectTypes = new(); + private Dictionary> JsonTypes { get; } = []; + Dictionary> IObjectAdapterProvider.JsonTypes => JsonTypes; + + public CustomAdapterProvider(ObjectTypeListBuilder objectTypeListBuilder) { + _objectTypeListBuilder = objectTypeListBuilder; JsonTypes.AddDerivedType(typeof(IObjectBase), typeof(TCustomAdapter), TCustomAdapter.TypeName); } - - private readonly List _objectTypes = new(); - public Dictionary> JsonTypes { get; } = []; - + public CustomAdapterProvider AddWithCustomPolymorphicMapping(string typeName, Action>? configureEntry = null ) where T : class, TCommonInterface @@ -25,10 +28,12 @@ public CustomAdapterProvider AddWithCustomPoly JsonTypes.AddDerivedType(typeof(TCommonInterface), typeof(T), typeName); return Add(configureEntry); } + public CustomAdapterProvider Add( Action>? configureEntry = null ) where T : class, TCommonInterface { + _objectTypeListBuilder.CheckFrozen(); _objectTypes.Add( new AdapterRegistration(typeof(T), builder => @@ -41,17 +46,18 @@ public CustomAdapterProvider Add( return this; } - public IEnumerable GetRegistrations() + IEnumerable IObjectAdapterProvider.GetRegistrations() { return _objectTypes; } - public IObjectBase Adapt(object obj) + IObjectBase IObjectAdapterProvider.Adapt(object obj) { return TCustomAdapter.Create((TCommonInterface)obj); } } +// it's possible to implement this without a Common interface, but it would require the adapter to have 1 property for each object type public interface ICustomAdapter : IObjectBase, IPolyType where TSelf : class, ICustomAdapter diff --git a/src/SIL.Harmony/Adapters/DefaultAdapter.cs b/src/SIL.Harmony/Adapters/DefaultAdapterProvider.cs similarity index 55% rename from src/SIL.Harmony/Adapters/DefaultAdapter.cs rename to src/SIL.Harmony/Adapters/DefaultAdapterProvider.cs index ee3d85c..53bea0f 100644 --- a/src/SIL.Harmony/Adapters/DefaultAdapter.cs +++ b/src/SIL.Harmony/Adapters/DefaultAdapterProvider.cs @@ -5,17 +5,18 @@ namespace SIL.Harmony.Adapters; -public class DefaultAdapter : IObjectAdapter +public class DefaultAdapterProvider(ObjectTypeListBuilder objectTypeListBuilder) : IObjectAdapterProvider { - private readonly List _objectTypes = new(); + private readonly List _objectTypes = []; - IEnumerable IObjectAdapter.GetRegistrations() + IEnumerable IObjectAdapterProvider.GetRegistrations() { return _objectTypes.AsReadOnly(); } - public DefaultAdapter Add(Action>? configureEntry = null) where T : class, IObjectBase + public DefaultAdapterProvider Add(Action>? configureEntry = null) where T : class, IObjectBase { + objectTypeListBuilder.CheckFrozen(); JsonTypes.AddDerivedType(typeof(IObjectBase), typeof(T), T.TypeName); _objectTypes.Add(new(typeof(T), builder => { @@ -26,7 +27,7 @@ public DefaultAdapter Add(Action>? configureEntry = null return this; } - IObjectBase IObjectAdapter.Adapt(object obj) + IObjectBase IObjectAdapterProvider.Adapt(object obj) { if (obj is IObjectBase objectBase) { @@ -37,5 +38,6 @@ IObjectBase IObjectAdapter.Adapt(object obj) $"Object is of type {obj.GetType().Name} which does not implement {nameof(IObjectBase)}"); } - public Dictionary> JsonTypes { get; } = []; + private Dictionary> JsonTypes { get; } = []; + Dictionary> IObjectAdapterProvider.JsonTypes => JsonTypes; } \ No newline at end of file diff --git a/src/SIL.Harmony/Adapters/IObjectAdapter.cs b/src/SIL.Harmony/Adapters/IObjectAdapterProvider.cs similarity index 70% rename from src/SIL.Harmony/Adapters/IObjectAdapter.cs rename to src/SIL.Harmony/Adapters/IObjectAdapterProvider.cs index 61a0430..6504c0a 100644 --- a/src/SIL.Harmony/Adapters/IObjectAdapter.cs +++ b/src/SIL.Harmony/Adapters/IObjectAdapterProvider.cs @@ -5,9 +5,9 @@ namespace SIL.Harmony.Adapters; -public record AdapterRegistration(Type ObjectDbType, Func EntityBuilder); +internal record AdapterRegistration(Type ObjectDbType, Func EntityBuilder); -public interface IObjectAdapter +internal interface IObjectAdapterProvider { IEnumerable GetRegistrations(); IObjectBase Adapt(object obj); diff --git a/src/SIL.Harmony/Changes/ChangeContext.cs b/src/SIL.Harmony/Changes/ChangeContext.cs index 42c97c4..72104c6 100644 --- a/src/SIL.Harmony/Changes/ChangeContext.cs +++ b/src/SIL.Harmony/Changes/ChangeContext.cs @@ -19,5 +19,5 @@ internal ChangeContext(Commit commit, SnapshotWorker worker, CrdtConfig crdtConf public async ValueTask GetSnapshot(Guid entityId) => await _worker.GetSnapshot(entityId); public async ValueTask IsObjectDeleted(Guid entityId) => (await GetSnapshot(entityId))?.EntityIsDeleted ?? true; - public IObjectBase Adapt(object obj) => _crdtConfig.ObjectTypeListBuilder.Adapter.Adapt(obj); + public IObjectBase Adapt(object obj) => _crdtConfig.ObjectTypeListBuilder.AdapterProvider.Adapt(obj); } diff --git a/src/SIL.Harmony/CrdtConfig.cs b/src/SIL.Harmony/CrdtConfig.cs index 996d564..1b9837a 100644 --- a/src/SIL.Harmony/CrdtConfig.cs +++ b/src/SIL.Harmony/CrdtConfig.cs @@ -107,8 +107,8 @@ public void Freeze() { if (_frozen) return; _frozen = true; - JsonTypes = Adapter.JsonTypes; - foreach (var registration in Adapter.GetRegistrations()) + JsonTypes = AdapterProvider.JsonTypes; + foreach (var registration in AdapterProvider.GetRegistrations()) { ModelConfigurations.Add((builder, config) => { @@ -122,7 +122,7 @@ public void Freeze() } } - private void CheckFrozen() + internal void CheckFrozen() { if (_frozen) throw new InvalidOperationException($"{nameof(ObjectTypeListBuilder)} is frozen"); } @@ -131,27 +131,40 @@ private void CheckFrozen() internal List> ModelConfigurations { get; } = []; - public ObjectTypeListBuilder AddDbModelConfig(Action modelConfiguration) - { - CheckFrozen(); - ModelConfigurations.Add((builder, _) => modelConfiguration(builder)); - return this; - } - internal IObjectAdapter Adapter => _adapter ?? throw new InvalidOperationException("No adapter has been added to the builder"); - private IObjectAdapter? _adapter; + internal IObjectAdapterProvider AdapterProvider => _adapterProvider ?? throw new InvalidOperationException("No adapter has been added to the builder"); + private IObjectAdapterProvider? _adapterProvider; - public DefaultAdapter DefaultAdapter() + public DefaultAdapterProvider DefaultAdapter() { - var adapter = new DefaultAdapter(); - _adapter = adapter; + CheckFrozen(); + if (_adapterProvider is not null) throw new InvalidOperationException("adapter has already been added"); + var adapter = new DefaultAdapterProvider(this); + _adapterProvider = adapter; return adapter; } - + + /// + /// add a custom adapter for a common interface + /// this is required as CRDT objects must express their references and have an Id property + /// using a custom adapter allows your model to not take a dependency on Harmony + /// + /// + /// A common interface that all objects in your application implement + /// which System.Text.Json will deserialize your objects to, they must support polymorphic deserialization + /// + /// + /// This adapter will be serialized and stored in the database, + /// it should include the object it is adapting otherwise Harmony will not work + /// + /// + /// when another adapter has already been added or the config has been frozen public CustomAdapterProvider CustomAdapter() where TCommonInterface : class where TAdapter : class, ICustomAdapter, IPolyType { - var adapter = new CustomAdapterProvider(); - _adapter = adapter; + CheckFrozen(); + if (_adapterProvider is not null) throw new InvalidOperationException("adapter has already been added"); + var adapter = new CustomAdapterProvider(this); + _adapterProvider = adapter; return adapter; } }