diff --git a/.BinaryPrefs/ProjectSettings/ProjectSettings.asset b/.BinaryPrefs/ProjectSettings/ProjectSettings.asset index 388a9fa..0730eae 100644 --- a/.BinaryPrefs/ProjectSettings/ProjectSettings.asset +++ b/.BinaryPrefs/ProjectSettings/ProjectSettings.asset @@ -159,7 +159,8 @@ PlayerSettings: androidMaxAspectRatio: 2.1 applicationIdentifier: Android: org.appegy.binaryprefs - iPhone: org.appegy.tools.ulog + Standalone: org.appegy.binaryprefs + iPhone: org.appegy.binaryprefs buildNumber: Standalone: 0 iPhone: 0 diff --git a/Runtime/BinaryStorage.Builder.cs b/Runtime/BinaryStorage.Builder.cs index 6557777..a361241 100644 --- a/Runtime/BinaryStorage.Builder.cs +++ b/Runtime/BinaryStorage.Builder.cs @@ -56,6 +56,8 @@ public class Builder private readonly string _filePath; private readonly List _serializers = new(); private bool _autoSave; + private MissingKeyBehavior _missingKeyBehavior = MissingKeyBehavior.InitializeWithDefaultValue; + private TypeMismatchBehaviour _typeMismatchBehaviour = TypeMismatchBehaviour.ThrowException; internal Builder(string filePath) { @@ -72,6 +74,26 @@ public Builder EnableAutoSaveOnChange() return this; } + /// + /// Specifies the behavior when a requested key is not found in the storage. + /// + /// The current instance for method chaining. + public Builder SetMissingKeyBehaviour(MissingKeyBehavior behavior) + { + _missingKeyBehavior = behavior; + return this; + } + /// + /// Specifies the behavior when the type of value associated with a key does not match the expected type. + /// + /// The type mismatch behavior. + /// The current instance for method chaining. + public Builder SetTypeMismatchBehaviour(TypeMismatchBehaviour behavior) + { + _typeMismatchBehaviour = behavior; + return this; + } + /// /// Adds serializers for primitive types to the storage configuration. /// @@ -225,6 +247,8 @@ public BinaryStorage Build() { var storage = new BinaryStorage(_filePath, _serializers); storage.AutoSave = _autoSave; + storage.MissingKeyBehavior = _missingKeyBehavior; + storage.TypeMismatchBehaviour = _typeMismatchBehaviour; storage.LoadDataFromDisk(); return storage; } diff --git a/Runtime/BinaryStorage.cs b/Runtime/BinaryStorage.cs index a721de9..7be8053 100644 --- a/Runtime/BinaryStorage.cs +++ b/Runtime/BinaryStorage.cs @@ -27,6 +27,11 @@ public partial class BinaryStorage : IDisposable /// public MissingKeyBehavior MissingKeyBehavior { get; set; } = MissingKeyBehavior.ReturnDefaultValueOnly; + /// + /// Gets or sets the behavior when the type of a value associated with a key does not match the expected type. + /// + public TypeMismatchBehaviour TypeMismatchBehaviour { get; set; } = TypeMismatchBehaviour.OverrideValueAndType; + /// /// Gets a value indicating whether there are unsaved changes. /// @@ -92,11 +97,12 @@ public virtual bool Supports() /// The key to get the value for. /// The default value to use if the key does not exist. /// The value associated with the key. - public virtual T Get(string key, T defaultValue = default) + public virtual T Get(string key, T defaultValue = default, MissingKeyBehavior? overrideMissingKeyBehavior = null) { ThrowIfDisposed(); ThrowIfCollection(); var record = GetRecord(key); + var missingKeyBehavior = overrideMissingKeyBehavior ?? MissingKeyBehavior; switch (record) { case Record typedRecord: @@ -104,11 +110,11 @@ public virtual T Get(string key, T defaultValue = default) case not null: throw new UnexpectedTypeException(key, nameof(Get), record.Type, typeof(T)); case null: - return MissingKeyBehavior switch + return missingKeyBehavior switch { MissingKeyBehavior.InitializeWithDefaultValue => AddRecord(key, defaultValue).Value, MissingKeyBehavior.ReturnDefaultValueOnly => defaultValue, - _ => throw new UnexpectedEnumException(typeof(MissingKeyBehavior), MissingKeyBehavior) + _ => throw new UnexpectedEnumException(typeof(MissingKeyBehavior), missingKeyBehavior) }; } } @@ -119,9 +125,9 @@ public virtual T Get(string key, T defaultValue = default) /// The type of the value. /// The key to set the value for. /// The value to set. - /// Whether to override the value if the key already exists but with another type. + /// Whether to override the value if the key already exists but with another type. /// True if the value was set; otherwise, false. - public virtual bool Set(string key, T value, bool overrideTypeMismatch = false) + public virtual bool Set(string key, T value, TypeMismatchBehaviour? overrideTypeMismatchBehaviour = null) { ThrowIfDisposed(); ThrowIfCollection(); @@ -138,18 +144,23 @@ public virtual bool Set(string key, T value, bool overrideTypeMismatch = fals return ChangeRecord(typedRecord, value); } - if (!overrideTypeMismatch) + var mismatchBehaviour = overrideTypeMismatchBehaviour ?? TypeMismatchBehaviour; + switch (mismatchBehaviour) { - throw new UnexpectedTypeException(key, nameof(Set), record.Type, typeof(T)); - } - - using (MultipleChangeScope()) - { - RemoveRecord(key); - AddRecord(key, value); + case TypeMismatchBehaviour.OverrideValueAndType: + using (MultipleChangeScope()) + { + RemoveRecord(key); + AddRecord(key, value); + } + return true; + case TypeMismatchBehaviour.ThrowException: + throw new UnexpectedTypeException(key, nameof(Set), record.Type, typeof(T)); + case TypeMismatchBehaviour.Ignore: + return false; + default: + throw new UnexpectedEnumException(typeof(TypeMismatchBehaviour), mismatchBehaviour); } - - return true; } /// diff --git a/Runtime/Settings/TypeMismatchBehaviour.cs b/Runtime/Settings/TypeMismatchBehaviour.cs new file mode 100644 index 0000000..bfef555 --- /dev/null +++ b/Runtime/Settings/TypeMismatchBehaviour.cs @@ -0,0 +1,23 @@ +namespace Appegy.Storage +{ + /// + /// Specifies the behavior when the type of a value associated with a key does not match the expected type. + /// + public enum TypeMismatchBehaviour + { + /// + /// Throws an exception if there is a type mismatch. + /// + ThrowException, + + /// + /// Overrides the existing value and type with the new value and type. + /// + OverrideValueAndType, + + /// + /// Ignores the new value and type if there is a type mismatch. + /// + Ignore + } +} \ No newline at end of file diff --git a/Runtime/Settings/TypeMismatchBehaviour.cs.meta b/Runtime/Settings/TypeMismatchBehaviour.cs.meta new file mode 100644 index 0000000..39021ca --- /dev/null +++ b/Runtime/Settings/TypeMismatchBehaviour.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a2462c117fb34b0185b5336fe57b69bd +timeCreated: 1719058726 \ No newline at end of file diff --git a/Tests/BinaryStorageTests.cs b/Tests/BinaryStorageTests.cs index 9374be4..a2dcefa 100644 --- a/Tests/BinaryStorageTests.cs +++ b/Tests/BinaryStorageTests.cs @@ -388,5 +388,146 @@ public void WhenReactiveDictionaryChanged_AndStorageReloaded_ThenValuesInStorage } #endregion + + #region TypeMismatchBehaviour Tests + + [Test] + public void WhenTypeMismatchBehaviorIsThrowException_ThenExceptionIsThrown() + { + // Arrange + using var storage = BinaryStorage.Construct(StoragePath) + .AddPrimitiveTypes() + .SetTypeMismatchBehaviour(TypeMismatchBehaviour.ThrowException) + .Build(); + + storage.Set("key", 123); + + // Act + // ReSharper disable once AccessToDisposedClosure + Action action = () => storage.Set("key", "value"); + + // Assert + action.Should().Throw(); + } + + [Test] + public void WhenTypeMismatchBehaviorIsOverrideValueAndType_ThenValueAndTypeAreOverridden() + { + // Arrange + using var storage = BinaryStorage.Construct(StoragePath) + .AddPrimitiveTypes() + .SetTypeMismatchBehaviour(TypeMismatchBehaviour.OverrideValueAndType) + .Build(); + + storage.Set("key", 123); + + // Act + var result = storage.Set("key", "value"); + + // Assert + result.Should().BeTrue(); + storage.TypeOf("key").Should().Be(typeof(string)); + storage.Get("key").Should().Be("value"); + } + + [Test] + public void WhenTypeMismatchBehaviorIsIgnore_ThenValueAndTypeAreIgnored() + { + // Arrange + using var storage = BinaryStorage.Construct(StoragePath) + .AddPrimitiveTypes() + .SetTypeMismatchBehaviour(TypeMismatchBehaviour.Ignore) + .Build(); + + storage.Set("key", 123); + + // Act + var result = storage.Set("key", "value"); + + // Assert + result.Should().BeFalse(); + storage.TypeOf("key").Should().Be(typeof(int)); + storage.Get("key").Should().Be(123); + } + + [Test] + public void WhenTypeMismatchBehaviorOverride_ThenBehaviorIsOverridden() + { + // Arrange + using var storage = BinaryStorage.Construct(StoragePath) + .AddPrimitiveTypes() + .SetTypeMismatchBehaviour(TypeMismatchBehaviour.ThrowException) + .Build(); + + storage.Set("key", 123); + + // Act + var result = storage.Set("key", "value", TypeMismatchBehaviour.OverrideValueAndType); + + // Assert + result.Should().BeTrue(); + storage.Has("key").Should().BeTrue(); + storage.TypeOf("key").Should().Be(typeof(string)); + storage.Get("key").Should().Be("value"); + } + + #endregion + + #region MissingKeyBehavior Tests + + [Test] + public void WhenMissingKeyBehaviorIsInitializeWithDefaultValue_ThenKeyIsInitialized() + { + // Arrange + using var storage = BinaryStorage.Construct(StoragePath) + .AddPrimitiveTypes() + .SetMissingKeyBehaviour(MissingKeyBehavior.InitializeWithDefaultValue) + .Build(); + + // Act + var value = storage.Get("key", 10); + + // Assert + value.Should().Be(10); + storage.Has("key").Should().BeTrue(); + storage.Get("key").Should().Be(10); + } + + [Test] + public void WhenMissingKeyBehaviorIsReturnDefaultValueOnly_ThenDefaultValueIsReturned() + { + // Arrange + using var storage = BinaryStorage.Construct(StoragePath) + .AddPrimitiveTypes() + .SetMissingKeyBehaviour(MissingKeyBehavior.ReturnDefaultValueOnly) + .Build(); + + // Act + var value = storage.Get("key", 10); + + // Assert + value.Should().Be(10); + storage.Has("key").Should().BeFalse(); + } + + [Test] + public void WhenMissingKeyBehaviorOverrideIsSetInGetMethod_ThenBehaviorIsOverridden() + { + // Arrange + using var storage = BinaryStorage.Construct(StoragePath) + .AddPrimitiveTypes() + .SetMissingKeyBehaviour(MissingKeyBehavior.ReturnDefaultValueOnly) + .Build(); + + // Act + var value = storage.Get("key", 10, MissingKeyBehavior.InitializeWithDefaultValue); + + // Assert + value.Should().Be(10); + storage.Has("key").Should().BeTrue(); + storage.Get("key").Should().Be(10); + } + + #endregion } } \ No newline at end of file