Skip to content

Commit

Permalink
Type mismatch behavior (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
imurashka authored Jun 22, 2024
1 parent 258965d commit d7bce60
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 16 deletions.
3 changes: 2 additions & 1 deletion .BinaryPrefs/ProjectSettings/ProjectSettings.asset
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions Runtime/BinaryStorage.Builder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ public class Builder
private readonly string _filePath;
private readonly List<BinarySection> _serializers = new();
private bool _autoSave;
private MissingKeyBehavior _missingKeyBehavior = MissingKeyBehavior.InitializeWithDefaultValue;
private TypeMismatchBehaviour _typeMismatchBehaviour = TypeMismatchBehaviour.ThrowException;

internal Builder(string filePath)
{
Expand All @@ -72,6 +74,26 @@ public Builder EnableAutoSaveOnChange()
return this;
}

/// <summary>
/// Specifies the behavior when a requested key is not found in the storage.
/// </summary>
/// <returns>The current <see cref="Builder"/> instance for method chaining.</returns>
public Builder SetMissingKeyBehaviour(MissingKeyBehavior behavior)
{
_missingKeyBehavior = behavior;
return this;
}
/// <summary>
/// Specifies the behavior when the type of value associated with a key does not match the expected type.
/// </summary>
/// <param name="behavior">The type mismatch behavior.</param>
/// <returns>The current <see cref="Builder"/> instance for method chaining.</returns>
public Builder SetTypeMismatchBehaviour(TypeMismatchBehaviour behavior)
{
_typeMismatchBehaviour = behavior;
return this;
}

/// <summary>
/// Adds serializers for primitive types to the storage configuration.
/// </summary>
Expand Down Expand Up @@ -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;
}
Expand Down
41 changes: 26 additions & 15 deletions Runtime/BinaryStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public partial class BinaryStorage : IDisposable
/// </summary>
public MissingKeyBehavior MissingKeyBehavior { get; set; } = MissingKeyBehavior.ReturnDefaultValueOnly;

/// <summary>
/// Gets or sets the behavior when the type of a value associated with a key does not match the expected type.
/// </summary>
public TypeMismatchBehaviour TypeMismatchBehaviour { get; set; } = TypeMismatchBehaviour.OverrideValueAndType;

/// <summary>
/// Gets a value indicating whether there are unsaved changes.
/// </summary>
Expand Down Expand Up @@ -92,23 +97,24 @@ public virtual bool Supports<T>()
/// <param name="key">The key to get the value for.</param>
/// <param name="defaultValue">The default value to use if the key does not exist.</param>
/// <returns>The value associated with the key.</returns>
public virtual T Get<T>(string key, T defaultValue = default)
public virtual T Get<T>(string key, T defaultValue = default, MissingKeyBehavior? overrideMissingKeyBehavior = null)
{
ThrowIfDisposed();
ThrowIfCollection<T>();
var record = GetRecord(key);
var missingKeyBehavior = overrideMissingKeyBehavior ?? MissingKeyBehavior;
switch (record)
{
case Record<T> typedRecord:
return typedRecord.Value;
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)
};
}
}
Expand All @@ -119,9 +125,9 @@ public virtual T Get<T>(string key, T defaultValue = default)
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="key">The key to set the value for.</param>
/// <param name="value">The value to set.</param>
/// <param name="overrideTypeMismatch">Whether to override the value if the key already exists but with another type.</param>
/// <param name="overrideTypeMismatchBehaviour">Whether to override the value if the key already exists but with another type.</param>
/// <returns>True if the value was set; otherwise, false.</returns>
public virtual bool Set<T>(string key, T value, bool overrideTypeMismatch = false)
public virtual bool Set<T>(string key, T value, TypeMismatchBehaviour? overrideTypeMismatchBehaviour = null)
{
ThrowIfDisposed();
ThrowIfCollection<T>();
Expand All @@ -138,18 +144,23 @@ public virtual bool Set<T>(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;
}

/// <summary>
Expand Down
23 changes: 23 additions & 0 deletions Runtime/Settings/TypeMismatchBehaviour.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Appegy.Storage
{
/// <summary>
/// Specifies the behavior when the type of a value associated with a key does not match the expected type.
/// </summary>
public enum TypeMismatchBehaviour
{
/// <summary>
/// Throws an exception if there is a type mismatch.
/// </summary>
ThrowException,

/// <summary>
/// Overrides the existing value and type with the new value and type.
/// </summary>
OverrideValueAndType,

/// <summary>
/// Ignores the new value and type if there is a type mismatch.
/// </summary>
Ignore
}
}
3 changes: 3 additions & 0 deletions Runtime/Settings/TypeMismatchBehaviour.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

141 changes: 141 additions & 0 deletions Tests/BinaryStorageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<UnexpectedTypeException>();
}

[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<string>("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<int>("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<string>("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<int>("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<int>("key").Should().Be(10);
}

#endregion
}
}

0 comments on commit d7bce60

Please sign in to comment.