From 1ad9187feb8fbb4f74e0e70c39a67a794499b3bf Mon Sep 17 00:00:00 2001 From: LittleGitPhoenix <57481506+LittleGitPhoenix@users.noreply.github.com> Date: Tue, 28 Mar 2023 16:32:37 +0200 Subject: [PATCH] Added new SettingsUnavailableException. --- README.md | 51 +--- .../Settings.Builder.Test.csproj | 8 +- .../EncryptSettingsManagerTest.cs | 48 ++-- .../EncryptionHelperTest.cs | 35 ++- .../Settings.Encryption.Test.csproj | 8 +- .../EnumConverterTest.cs | 223 ++++++++++++------ .../JsonSettingsSerializerTest.cs | 4 +- .../Settings.Serializers.Json.Net.Test.csproj | 8 +- .../CustomConverters/EnumConverter.cs | 201 ++++++++-------- .../JsonSettingsSerializer.cs | 1 - .../Settings.Serializers.Json.Net.csproj | 4 +- .../\342\254\231/CHANGELOG.md" | 13 + .../Settings.Sinks.File.Test.csproj | 8 +- src/Settings.Test/Settings.Test.csproj | 8 +- src/Settings.Test/SettingsManagerTest.cs | 22 ++ src/Settings/ISettingsManager.cs | 7 +- src/Settings/Settings.csproj | 4 +- src/Settings/SettingsException.cs | 9 + src/Settings/SettingsManager.cs | 2 +- "src/Settings/\342\254\231/CHANGELOG.md" | 10 + 20 files changed, 388 insertions(+), 286 deletions(-) diff --git a/README.md b/README.md index e3b1dcd..5fe83c1 100644 --- a/README.md +++ b/README.md @@ -175,6 +175,8 @@ var settingsManager = SettingsManager .WithIpAddressConverter() .WithRegexConverter() .WithTimeSpanConverter() + .WithVersionConverter() + .WithEnumConverter(WriteOutValues.AsSuffix(start: "[", separator: ";", end: "]")) .WithDefaultSerializerOptions() // <-- .AddCache(...) @@ -241,55 +243,6 @@ class MySettings : ISettings "second": "Entry2 [Default;Entry1;Entry2]" } ``` - -- Write-out as comment - - Will add the enumeration values with configurable separator as a single comment below the serialized property. Due to limitations of the **JsonStringEnumConverter** the comment cannot be added above the property. - - ```c# - var converter = new EnumConverter(WriteOutValues.AsComment(separator: ";")); - var serializer = new JsonSettingsSerializer(converter); - var settingsData = serializer.Serialize(settings); - ``` - - ```json - { - "first": "Entry1" - /*Default;Entry1;Entry2*/, - "second": "Entry2" - /*Default;Entry1;Entry2*/ - } - ``` - -- Write out as separate property - - Will add the enumeration values as an additional property below the serialized property. Due to limitations of the **JsonStringEnumConverter** the name of the additional property cannot correspond to the name of the serialized property and is a generic one based on the enumerations type appended by a random number to keep the JSON valid. - - ```c# - var converter = new EnumConverter(WriteOutValues.AsProperty()); - var serializer = new JsonSettingsSerializer(converter); - var settingsData = serializer.Serialize(settings); - ``` - - ```json - { - "first": "Entry1", - "Values_for_MyEnum_45319": [ - "Default", - "Entry1", - "Entry2" - ], - "second": "Entry2", - "Values_for_MyEnum_13705": [ - "Default", - "Entry1", - "Entry2" - ] - } - ``` - - - ___ # Implementations of `ISettingsCache` diff --git a/src/Settings.Builder.Test/Settings.Builder.Test.csproj b/src/Settings.Builder.Test/Settings.Builder.Test.csproj index 8135b32..790ff6f 100644 --- a/src/Settings.Builder.Test/Settings.Builder.Test.csproj +++ b/src/Settings.Builder.Test/Settings.Builder.Test.csproj @@ -9,11 +9,11 @@ - - + + - - + + diff --git a/src/Settings.Encryption.Test/EncryptSettingsManagerTest.cs b/src/Settings.Encryption.Test/EncryptSettingsManagerTest.cs index 90d46d2..6958b44 100644 --- a/src/Settings.Encryption.Test/EncryptSettingsManagerTest.cs +++ b/src/Settings.Encryption.Test/EncryptSettingsManagerTest.cs @@ -159,7 +159,7 @@ public EncryptedCollection() : base(new List() { "1", "2", "3" }) { } [TestCase(typeof(object[]), false)] [TestCase(typeof(IPAddress), false)] [TestCase(typeof(NestedSettings.Inner), true)] - public void Check_Is_Treated_As_Nested(Type nestedType, bool shouldBeTreatedAsCollection) + public void Is_Treated_As_Nested(Type nestedType, bool shouldBeTreatedAsCollection) { // Act var isNested = EncryptSettingsManager.IsNested(nestedType); @@ -173,7 +173,7 @@ public void Check_Is_Treated_As_Nested(Type nestedType, bool shouldBeTreatedAsCo [TestCase(typeof(Dictionary), false)] [TestCase(typeof(List), true)] [TestCase(typeof(object[]), true)] - public void Check_Is_Treated_As_Collection(Type collectionType, bool shouldBeTreatedAsCollection) + public void Is_Treated_As_Collection(Type collectionType, bool shouldBeTreatedAsCollection) { // Act var isCollection = EncryptSettingsManager.IsCollection(collectionType); @@ -183,7 +183,7 @@ public void Check_Is_Treated_As_Collection(Type collectionType, bool shouldBeTre } [Test] - public void Check_System_Namespace_Is_Ignored() + public void System_Namespace_Is_Ignored() { Assert.Ignore("This would need a custom assembly name 'System.[...].dll' which is currently not implemented."); //// Arrange @@ -197,7 +197,7 @@ public void Check_System_Namespace_Is_Ignored() } [Test] - public void Check_EncryptDoNotFollowAttribute_Overrules() + public void EncryptDoNotFollowAttribute_Overrules() { // Arrange var settings = new DontFollowSettings(); @@ -210,7 +210,7 @@ public void Check_EncryptDoNotFollowAttribute_Overrules() } [Test] - public void Check_EncryptForceFollowAttribute_Overrules() + public void EncryptForceFollowAttribute_Overrules() { Assert.Ignore("This would need a custom assembly name 'System.[...].dll' which is currently not implemented."); //// Arrange @@ -225,7 +225,7 @@ public void Check_EncryptForceFollowAttribute_Overrules() } [Test] - public void Check_Relevant_Properties_Are_Filtered() + public void Relevant_Properties_Are_Filtered() { // Arrange var settings = new MultiplePropertiesSettings(); @@ -245,7 +245,7 @@ public void Check_Relevant_Properties_Are_Filtered() } [Test] - public void Check_Nested_Properties_Are_Filtered() + public void Nested_Properties_Are_Filtered() { // Arrange var settings = new NestedSettings(); @@ -259,7 +259,7 @@ public void Check_Nested_Properties_Are_Filtered() } [Test] - public void Check_Settings_With_Properties_That_Throw_Can_Be_Handled() + public void Settings_With_Properties_That_Throw_Can_Be_Handled() { // Arrange var settings = new ThrowingSettings(); @@ -270,19 +270,19 @@ public void Check_Settings_With_Properties_That_Throw_Can_Be_Handled() [Test] [Category("Encrypted Collection Test")] - public void Check_Simple_Array_Handling() => this.CheckCollectionHandling(2); + public void Simple_Array_Handling() => this.CheckCollectionHandling(2); [Test] [Category("Encrypted Collection Test")] - public void Check_Simple_List_Handling() => this.CheckCollectionHandling(2); + public void Simple_List_Handling() => this.CheckCollectionHandling(2); [Test] [Category("Encrypted Collection Test")] - public void Check_Stacked_List_Handling() => this.CheckCollectionHandling(6); + public void Stacked_List_Handling() => this.CheckCollectionHandling(6); [Test] [Category("Encrypted Collection Test")] - public void Check_Nested_List_Handling() => this.CheckCollectionHandling(3); + public void Nested_List_Handling() => this.CheckCollectionHandling(3); private void CheckCollectionHandling(int amountOfProperties) where TSettings : new() { @@ -301,7 +301,7 @@ public void Check_Settings_With_Properties_That_Throw_Can_Be_Handled() #region En-/Decryption [Test] - public void Check_Unencrypted_Property_Is_Same() + public void Unencrypted_Property_Is_Same() { // Arrange var underlyingSettingsManager = _fixture.Create>().Object; @@ -324,7 +324,7 @@ public void Check_Unencrypted_Property_Is_Same() } [Test] - public void Check_Null_Property_Is_Same() + public void Null_Property_Is_Same() { // Arrange var underlyingSettingsManager = _fixture.Create>().Object; @@ -347,7 +347,7 @@ public void Check_Null_Property_Is_Same() } [Test] - public void Check_Null_Is_Not_Encrypted() + public void Null_Is_Not_Encrypted() { // Arrange string? unencrypted = null; @@ -376,7 +376,7 @@ public void Check_Null_Is_Not_Encrypted() } [Test] - public void Check_Value_Is_Encrypted_Upon_Save() + public void Value_Is_Encrypted_Upon_Save() { // Arrange string? unencrypted = "unencrypted"; @@ -412,7 +412,7 @@ public void Check_Value_Is_Encrypted_Upon_Save() } [Test] - public void Check_Value_Is_Still_Decrypted_After_Save() + public void Value_Is_Still_Decrypted_After_Save() { // Arrange string? unencrypted = "unencrypted"; @@ -441,7 +441,7 @@ public void Check_Value_Is_Still_Decrypted_After_Save() } [Test] - public void Check_Nested_Property_Is_Decrypted() + public void Nested_Property_Is_Decrypted() { // Arrange var unencrypted = "unencrypted"; @@ -458,7 +458,7 @@ public void Check_Nested_Property_Is_Decrypted() } [Test] - public void Check_Nested_Property_Is_Encrypted() + public void Nested_Property_Is_Encrypted() { // Arrange var unencrypted = "unencrypted"; @@ -474,7 +474,7 @@ public void Check_Nested_Property_Is_Encrypted() } [Test] - public void Check_Simple_Array_Property_Is_Encrypted() + public void Simple_Array_Property_Is_Encrypted() { // Arrange var unencrypted = "unencrypted"; @@ -491,7 +491,7 @@ public void Check_Simple_Array_Property_Is_Encrypted() } [Test] - public void Check_Simple_List_Property_Is_Encrypted() + public void Simple_List_Property_Is_Encrypted() { // Arrange var unencrypted = "unencrypted"; @@ -508,7 +508,7 @@ public void Check_Simple_List_Property_Is_Encrypted() } [Test] - public void Check_Stacked_List_Property_Is_Encrypted() + public void Stacked_List_Property_Is_Encrypted() { // Arrange var unencrypted = "unencrypted"; @@ -534,7 +534,7 @@ public void Check_Stacked_List_Property_Is_Encrypted() } [Test] - public void Check_True_Is_Returned_If_Not_All_Properties_Are_Encrypted() + public void True_Is_Returned_If_Not_All_Properties_Are_Encrypted() { // Arrange var unencrypted = "unencrypted"; @@ -550,7 +550,7 @@ public void Check_True_Is_Returned_If_Not_All_Properties_Are_Encrypted() } [Test] - public void Check_False_Is_Returned_If_All_Properties_Are_Encrypted() + public void False_Is_Returned_If_All_Properties_Are_Encrypted() { // Arrange var encryptedText = "3DX19APzlJKi5kK0YlUjp1ZDNeQF9fDeN7bfsddBDKhX7w+jhw=="; diff --git a/src/Settings.Encryption.Test/EncryptionHelperTest.cs b/src/Settings.Encryption.Test/EncryptionHelperTest.cs index dff953d..56689ae 100644 --- a/src/Settings.Encryption.Test/EncryptionHelperTest.cs +++ b/src/Settings.Encryption.Test/EncryptionHelperTest.cs @@ -35,7 +35,7 @@ public void AfterAllTest() { } #region Tests [Test] - public void Check_Encryption_And_Decrypt_Are_Identical() + public void Encryption_And_Decrypt_Are_Identical() { // Arrange var unencrypted = "unencrypted"; @@ -52,10 +52,10 @@ public void Check_Encryption_And_Decrypt_Are_Identical() /// Checks that encrypting the same value yields different results each time. This is because the is placed randomly. /// [Test] - public void Check_Encryption_Yields_Different_Results() + public void Encryption_Yields_Different_Results() { // Arrange - var unencrypted = String.Join("_", Enumerable.Repeat("unencrypted", 100)); //! Make this string a little bit longer, so that the random position of the encryption marker is not the same for both encryption attempts. + var unencrypted = String.Join("_", Enumerable.Repeat("unencrypted", 100)); //! Make this string a little bit longer, so that the random position of the encryption marker is accidentally not the same for both encryption attempts. // Act var encrypted1 = _encryptionHelper.Encrypt(unencrypted); @@ -66,7 +66,7 @@ public void Check_Encryption_Yields_Different_Results() } [Test] - public void Check_Encryption_Adds_Marker() + public void Encryption_Adds_Marker() { // Arrange var unencrypted = "unencrypted"; @@ -79,7 +79,7 @@ public void Check_Encryption_Adds_Marker() } [Test] - public void Check_Unencrypted_Value_Is_Not_Decrypted() + public void Unencrypted_Value_Is_Not_Decrypted() { // Arrange var pseudoEncrypted = "unencrypted"; @@ -92,7 +92,7 @@ public void Check_Unencrypted_Value_Is_Not_Decrypted() } [Test] - public void Check_Null_Value_Is_Not_Encrypted() + public void Null_Value_Is_Not_Encrypted() { // Act var encrypted = _encryptionHelper.Encrypt(null); @@ -102,7 +102,7 @@ public void Check_Null_Value_Is_Not_Encrypted() } [Test] - public void Check_Null_Value_Is_Not_Decrypted() + public void Null_Value_Is_Not_Decrypted() { // Act var decrypted = _encryptionHelper.Decrypt(null); @@ -111,5 +111,26 @@ public void Check_Null_Value_Is_Not_Decrypted() Assert.IsNull(decrypted); } + /// + /// Checks that an decrypted value is always properly decrypted, no matter how often the encrypted value changes (due to the random ). + /// + [Test] + public void Encrypting_Does_Not_Degrade() + { + // Arrange + var initialDecrypted = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. \\!?#~äöü|.:,;@"; + var decrypted = initialDecrypted; + + for (var iteration = 0; iteration < 10000; iteration++) + { + // Act + var encrypted = _encryptionHelper.Encrypt(decrypted); + decrypted = _encryptionHelper.Decrypt(encrypted); + + // Assert + Assert.That(decrypted, Is.EqualTo(initialDecrypted)); + } + } + #endregion } \ No newline at end of file diff --git a/src/Settings.Encryption.Test/Settings.Encryption.Test.csproj b/src/Settings.Encryption.Test/Settings.Encryption.Test.csproj index b986ce9..f1048e2 100644 --- a/src/Settings.Encryption.Test/Settings.Encryption.Test.csproj +++ b/src/Settings.Encryption.Test/Settings.Encryption.Test.csproj @@ -9,11 +9,11 @@ - - + + - - + + diff --git a/src/Settings.Serializers.Json.Net.Test/EnumConverterTest.cs b/src/Settings.Serializers.Json.Net.Test/EnumConverterTest.cs index 403bd88..bfecda2 100644 --- a/src/Settings.Serializers.Json.Net.Test/EnumConverterTest.cs +++ b/src/Settings.Serializers.Json.Net.Test/EnumConverterTest.cs @@ -40,6 +40,11 @@ class MySettings : ISettings public MyEnum Second { get; init; } = MyEnum.Default; } + class MyNullableSettings : ISettings + { + public MyEnum? First { get; init; } = null; + } + enum MyEnum { Default, @@ -80,18 +85,28 @@ enum MyFlags #region Tests #region Helper + + [Test] + [TestCase(MyEnum.Default)] + [TestCase(MyEnumWithoutDefault.First)] + [TestCase(MyEnumWithDefaultAttribute.Default)] + public void GetDefaultEnumerationValue(TEnum target) + { + // Act + var defaultValue = EnumConverter.InternalEnumConverter.GetDefaultValue(); + + // Assert + Assert.That(defaultValue, Is.EqualTo(target)); + } [Test] - [TestCase(typeof(MyEnum), MyEnum.Default)] - [TestCase(typeof(MyEnumWithoutDefault), MyEnumWithoutDefault.First)] - [TestCase(typeof(MyEnumWithDefaultAttribute), MyEnumWithDefaultAttribute.Default)] - public void GetDefaultEnumerationValue(Type enumerationType, object target) + public void GetDefaultValueForNullableEnumeration() { // Act - var defaultValue = EnumConverter.InternalEnumConverter.GetDefaultValue(enumerationType); + var defaultValue = EnumConverter.InternalEnumConverter.GetDefaultValue(); // Assert - Assert.AreEqual(target, defaultValue); + Assert.That(defaultValue, Is.Null); } #endregion @@ -102,102 +117,111 @@ public void GetDefaultEnumerationValue(Type enumerationType, object target) public void DeserializeEnumeration() { // Arrange - var converter = new EnumConverter.InternalEnumConverter(); + var converter = new EnumConverter.InternalEnumConverter(); var value = nameof(MyEnum.Default); var target = MyEnum.Default; - var type = target.GetType(); - + // Act - var actual = converter.Deserialize(value, type); - + var actual = converter.Deserialize(value); + // Assert Assert.NotNull(actual); - Assert.AreEqual(target, actual); + Assert.That(actual, Is.EqualTo(target)); } [Test] public void DeserializeEnumerationCleansWriteOut() { // Arrange - var converter = new EnumConverter.InternalEnumConverter(); + var converter = new EnumConverter.InternalEnumConverter(); var value = $"{nameof(MyEnum.Entry2)}{MyEnumSuffix}"; var target = MyEnum.Entry2; - var type = target.GetType(); // Act - var actual = converter.Deserialize(value, type); + var actual = converter.Deserialize(value); // Assert Assert.NotNull(actual); - Assert.AreEqual(target, actual); + Assert.That(actual, Is.EqualTo(target)); } [Test] public void DeserializeFlagsEnumeration() { // Arrange - var converter = new EnumConverter.InternalEnumConverter(); + var converter = new EnumConverter.InternalEnumConverter(); var value = $"{nameof(MyFlags.Me)}, {nameof(MyFlags.Myself)}, {nameof(MyFlags.I)}"; var target = MyFlags.Me | MyFlags.Myself | MyFlags.I; - var type = target.GetType(); - + // Act - var actual = converter.Deserialize(value, type); - + var actual = converter.Deserialize(value); + // Assert Assert.NotNull(actual); - Assert.AreEqual(target, actual); + Assert.That(actual, Is.EqualTo(target)); } [Test] public void DeserializeFlagsEnumerationCleansWriteOut() { // Arrange - var converter = new EnumConverter.InternalEnumConverter(); + var converter = new EnumConverter.InternalEnumConverter(); var value = $"{nameof(MyFlags.Me)}, {nameof(MyFlags.Myself)}, {nameof(MyFlags.I)}{MyFlagsSuffix}"; var target = MyFlags.Me | MyFlags.Myself | MyFlags.I; - var type = target.GetType(); - + // Act - var actual = converter.Deserialize(value, type); + var actual = converter.Deserialize(value); // Assert Assert.NotNull(actual); - Assert.AreEqual(target, actual); + Assert.That(actual, Is.EqualTo(target)); } [Test] public void DeserializeEnumerationIsCaseInsensitive() { // Arrange - var converter = new EnumConverter.InternalEnumConverter(); + var converter = new EnumConverter.InternalEnumConverter(); var value = nameof(MyEnum.Default).ToLower(); var target = MyEnum.Default; - var type = target.GetType(); - + // Act - var actual = converter.Deserialize(value, type); - + var actual = converter.Deserialize(value); + // Assert Assert.NotNull(actual); - Assert.AreEqual(target, actual); + Assert.That(actual, Is.EqualTo(target)); } [Test] public void DeserializeNullableEnumeration() { // Arrange - var converter = new EnumConverter.InternalEnumConverter(); + var converter = new EnumConverter.InternalEnumConverter(); //! Nullable var value = nameof(MyEnum.Default); var target = (MyEnum?) MyEnum.Default; - var type = typeof(MyEnum?); //! nullable - + // Act - var actual = converter.Deserialize(value, type); - + var actual = converter.Deserialize(value); + // Assert Assert.NotNull(actual); - Assert.AreEqual(target, actual); + Assert.That(actual, Is.EqualTo(target)); + } + + /// Checks that converting a null-string into a succeeds. + [Test] + public void DeserializeNullableEnumProperty() + { + // Arrange + var converter = new EnumConverter.InternalEnumConverter(); //! Nullable + var value = (string?) null; + + // Act + var actual = converter.Deserialize(value); + + //Assert + Assert.That(actual, Is.Null); } /// Checks that converting null or empty strings into an will return null, if the target type is nullable. @@ -207,16 +231,15 @@ public void DeserializeNullableEnumeration() public void DeserializeEnumerationReturnsNull(string? value) { // Arrange - var converter = new EnumConverter.InternalEnumConverter(); + var converter = new EnumConverter.InternalEnumConverter(); //! Nullable var target = (MyEnum?) null; - var type = typeof(MyEnum?); //! nullable - + // Act - var actual = converter.Deserialize(value, type); - + var actual = converter.Deserialize(value); + // Assert Assert.Null(actual); - Assert.AreEqual(target, actual); + Assert.That(actual, Is.EqualTo(target)); } /// Checks that converting null or empty strings into an will return the enumerations default value, if the target type is not nullable. @@ -226,16 +249,15 @@ public void DeserializeEnumerationReturnsNull(string? value) public void DeserializeEnumerationReturnsDefault(string? value) { // Arrange - var converter = new EnumConverter.InternalEnumConverter(); + var converter = new EnumConverter.InternalEnumConverter(); var target = MyEnum.Default; - var type = typeof(MyEnum); //! not-nullable - + // Act - var actual = converter.Deserialize(value, type); - + var actual = converter.Deserialize(value); + // Assert Assert.NotNull(actual); - Assert.AreEqual(target, actual); + Assert.That(actual, Is.EqualTo(target)); } /// Checks that converting null or empty strings into an will return the enumerations first value, if the target type is not nullable and the enum does not provide a default value. @@ -245,16 +267,15 @@ public void DeserializeEnumerationReturnsDefault(string? value) public void DeserializeEnumerationThrowsBecauseNoDefault(string? value) { // Arrange - var converter = new EnumConverter.InternalEnumConverter(); + var converter = new EnumConverter.InternalEnumConverter(); var target = MyEnumWithoutDefault.First; - var type = typeof(MyEnumWithoutDefault); //! not-nullable - + // Act + Assert - var actual = converter.Deserialize(value, type); - + var actual = converter.Deserialize(value); + // Assert Assert.NotNull(actual); - Assert.AreEqual(target, actual); + Assert.That(actual, Is.EqualTo(target)); } /// Checks that converting a string that is not defined within an will throw. @@ -262,24 +283,59 @@ public void DeserializeEnumerationThrowsBecauseNoDefault(string? value) public void DeserializeEnumerationThrowsIfNotDefined() { // Arrange - var converter = new EnumConverter.InternalEnumConverter(); + var converter = new EnumConverter.InternalEnumConverter(); var value = "not_defined"; var target = MyEnum.Default; - var type = typeof(MyEnum); - + // Act + Assert - Assert.Catch(() => converter.Deserialize(value, type)); + Assert.Catch(() => converter.Deserialize(value)); + } + + [Test] + public void DeserializeNullableEnumPropertyInSettings() + { + // Arrange + //var options = new EnumConverterOptions() {WriteOutOptions = WriteOutValues.AsSuffix()}; + var converter = new EnumConverter(/*options*/); + var serializer = new JsonSettingsSerializer(converter); + + //var settingsData = @"{""First"": null, ""Second"": ""Entry2""}"; + var settingsData = @"{""First"": null}"; + + // Act + var settings = serializer.Deserialize(settingsData); + + // Assert + Assert.That(settings, Is.Not.Null); + Assert.That(settings!.First, Is.Null); + } + + [Test] + public void DeserializeEnumPropertyInSettings() + { + // Arrange + var converter = new EnumConverter(); + var serializer = new JsonSettingsSerializer(converter); + var settingsData = @"{""First"": ""Entry1"", ""Second"": ""Entry2""}"; + + // Act + var settings = serializer.Deserialize(settingsData); + + // Assert + Assert.That(settings, Is.Not.Null); + Assert.That(settings!.First, Is.EqualTo(MyEnum.Entry1)); + Assert.That(settings!.Second, Is.EqualTo(MyEnum.Entry2)); } #endregion #region Serialize - + [Test] public void SerializeEnumeration() { // Arrange - var converter = new EnumConverter.InternalEnumConverter(); + var converter = new EnumConverter.InternalEnumConverter(); var value = MyEnum.Entry1; var target = nameof(MyEnum.Entry1); @@ -288,14 +344,14 @@ public void SerializeEnumeration() // Assert Assert.NotNull(actual); - Assert.AreEqual(target, actual); + Assert.That(actual, Is.EqualTo(target)); } [Test] public void SerializeEnumerationWithWriteOut() { // Arrange - var converter = new EnumConverter.InternalEnumConverter(new EnumConverterOptions() {WriteOutOptions = WriteOutValues.AsSuffix()}); + var converter = new EnumConverter.InternalEnumConverter(new EnumConverterOptions() { WriteOutOptions = WriteOutValues.AsSuffix() }); var value = MyEnum.Entry1; var target = $"{nameof(MyEnum.Entry1)}{MyEnumSuffix}"; @@ -303,15 +359,14 @@ public void SerializeEnumerationWithWriteOut() var actual = converter.Serialize(value); // Assert - Assert.NotNull(actual); - Assert.AreEqual(target, actual); + Assert.That(actual, Is.EqualTo(target)); } [Test] public void SerializeNullEnumeration() { // Arrange - var converter = new EnumConverter.InternalEnumConverter(); + var converter = new EnumConverter.InternalEnumConverter(); var value = (MyEnum?) null; var target = (string?) null; @@ -320,7 +375,7 @@ public void SerializeNullEnumeration() // Assert Assert.Null(actual); - Assert.AreEqual(target, actual); + Assert.That(actual, Is.EqualTo(target)); } [Test] @@ -335,46 +390,58 @@ public void SerializeAndDeserializeWithWriteOutAsSuffix() var settingsData = serializer.Serialize(settings); Console.WriteLine(settingsData); Assert.That(settingsData, Is.Not.Empty); + Assert.That(settingsData, Does.Contain($"\"{nameof(MySettings.First)}\"").IgnoreCase); + Assert.That(settingsData, Does.Contain($"\"{nameof(MyEnum.Entry1)} [").IgnoreCase); + Assert.That(settingsData, Does.Contain($"\"{nameof(MySettings.Second)}\"").IgnoreCase); + Assert.That(settingsData, Does.Contain($"\"{nameof(MyEnum.Entry2)} [").IgnoreCase); // Assert + Assert var deserializedSettings = serializer.Deserialize(settingsData); Assert.NotNull(deserializedSettings); + Assert.That(deserializedSettings!.First, Is.EqualTo(settings.First)); + Assert.That(deserializedSettings!.Second, Is.EqualTo(settings.Second)); } [Test] - public void SerializeAndDeserializeWithWriteOutAsComment() + public void SerializeAndDeserializeWithWriteOutAsSuffixForNullableEnumeration() { // Arrange - var converter = new EnumConverter(WriteOutValues.AsComment(separator: ";")); + var converter = new EnumConverter(WriteOutValues.AsSuffix(start: "[", separator: ";", end: "]")); var serializer = new JsonSettingsSerializer(converter); - var settings = new MySettings() { First = MyEnum.Entry1, Second = MyEnum.Entry2 }; + var settings = new MyNullableSettings() { First = null }; // Act + Assert var settingsData = serializer.Serialize(settings); Console.WriteLine(settingsData); Assert.That(settingsData, Is.Not.Empty); + Assert.That(settingsData, Does.Contain(nameof(MyNullableSettings.First)).IgnoreCase); + Assert.That(settingsData, Does.Contain("null").IgnoreCase); // Assert + Assert - var deserializedSettings = serializer.Deserialize(settingsData); + var deserializedSettings = serializer.Deserialize(settingsData); Assert.NotNull(deserializedSettings); + Assert.That(deserializedSettings!.First, Is.EqualTo(settings.First)); } - + [Test] - public void SerializeAndDeserializeWithWriteOutAsProperty() + public void SerializedNullableEnumPropertyDoesNotHaveQuatationMarks() { // Arrange - var converter = new EnumConverter(WriteOutValues.AsProperty()); + var converter = new EnumConverter(); var serializer = new JsonSettingsSerializer(converter); - var settings = new MySettings() { First = MyEnum.Entry1, Second = MyEnum.Entry2 }; + var settings = new MyNullableSettings() { First = null }; // Act + Assert var settingsData = serializer.Serialize(settings); Console.WriteLine(settingsData); Assert.That(settingsData, Is.Not.Empty); + Assert.That(settingsData, Does.Contain("null")); //! Must contain a pure null without quotation marks. + Assert.That(settingsData, Does.Not.Contain("\"null\"")); // Assert + Assert - var deserializedSettings = serializer.Deserialize(settingsData); + var deserializedSettings = serializer.Deserialize(settingsData); Assert.NotNull(deserializedSettings); + Assert.That(deserializedSettings!.First, Is.EqualTo(settings.First)); } #endregion diff --git a/src/Settings.Serializers.Json.Net.Test/JsonSettingsSerializerTest.cs b/src/Settings.Serializers.Json.Net.Test/JsonSettingsSerializerTest.cs index 43f3541..ffb1d7c 100644 --- a/src/Settings.Serializers.Json.Net.Test/JsonSettingsSerializerTest.cs +++ b/src/Settings.Serializers.Json.Net.Test/JsonSettingsSerializerTest.cs @@ -212,9 +212,9 @@ public void Check_Default_Json_Converter_Is_Added() /// Checks that the is not added, if another enum converter is available. /// [Test] - [TestCase(typeof(EnumConverter.InternalEnumConverter))] + [TestCase(typeof(EnumConverter))] [TestCase(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))] - public void Check_Default_Json_Converter_Is_Not_Added(Type enumConverterType) /*where T : class, new()*/ + public void Check_Default_Json_Converter_Is_Not_Added(Type enumConverterType) { // Arrange var enumConverter = (System.Text.Json.Serialization.JsonConverter) _fixture.Create(enumConverterType, new AutoFixture.Kernel.SpecimenContext(_fixture)); diff --git a/src/Settings.Serializers.Json.Net.Test/Settings.Serializers.Json.Net.Test.csproj b/src/Settings.Serializers.Json.Net.Test/Settings.Serializers.Json.Net.Test.csproj index f3d7e19..d5245ee 100644 --- a/src/Settings.Serializers.Json.Net.Test/Settings.Serializers.Json.Net.Test.csproj +++ b/src/Settings.Serializers.Json.Net.Test/Settings.Serializers.Json.Net.Test.csproj @@ -9,11 +9,11 @@ - - + + - - + + diff --git a/src/Settings.Serializers.Json.Net/CustomConverters/EnumConverter.cs b/src/Settings.Serializers.Json.Net/CustomConverters/EnumConverter.cs index 41a92b2..4b51720 100644 --- a/src/Settings.Serializers.Json.Net/CustomConverters/EnumConverter.cs +++ b/src/Settings.Serializers.Json.Net/CustomConverters/EnumConverter.cs @@ -25,12 +25,6 @@ internal record NoValueWriteOut : IWriteOutOptions; /// internal record ValueWriteOutAsSuffix(string Start, string Separator, string End) : IWriteOutOptions; -/// -public record ValueWriteOutAsComment(string Separator) : IWriteOutOptions; - -/// -public record ValueWriteOutAsProperty : IWriteOutOptions; - /// /// Helper for creating different . /// @@ -45,24 +39,6 @@ public static class WriteOutValues /// A new instance. public static IWriteOutOptions AsSuffix(string? start = "[", string? separator = ", ", string? end = "]") => new ValueWriteOutAsSuffix(start ?? "[", separator ?? ", ", end ?? "]"); - - /// - /// Enumeration values will be serialized as comment after the property value. - /// - /// The string used as separator for the values. Default is , - [Obsolete("It is currently not advised to use this method of enumeration value write-out, because comments are not part of the JSON specification and due limits imposed by System.Text.Json that prevent from writing comments above a property.")] - public static IWriteOutOptions AsComment(string? separator = ", ") - => new ValueWriteOutAsComment(separator ?? ", "); - - /// Enumeration values will be serialized in a separate property as an array below the property of the enumeration. - /// - /// It is currently not advised to use this method of enumeration value write-out. - /// Due to limitations of serializer, the additional property will be named after the enumeration type, rather then the original property name. - /// Additionally the property will be suffixed by a random number, to prevent duplicate keys in the final JSON. - /// - [Obsolete("It is currently not advised to use this method of enumeration value write-out, because System.Text.Json does not provide access to the original property name during serialization.")] - public static IWriteOutOptions AsProperty() - => new ValueWriteOutAsProperty(); } /// @@ -70,16 +46,14 @@ public static IWriteOutOptions AsProperty() /// public class EnumConverter : JsonConverterFactory { - private readonly Lazy _converter; + private readonly EnumConverterOptions? _options; /// /// Constructor /// /// The used for writing out enumeration values. public EnumConverter(IWriteOutOptions writeOutOptions) - { - _converter = new Lazy(() => new InternalEnumConverter(new EnumConverterOptions() { WriteOutOptions = writeOutOptions }), LazyThreadSafetyMode.ExecutionAndPublication); - } + : this(new EnumConverterOptions() { WriteOutOptions = writeOutOptions }) { } /// /// Constructor @@ -87,88 +61,133 @@ public EnumConverter(IWriteOutOptions writeOutOptions) /// The used for (de)serialization. public EnumConverter(EnumConverterOptions? options = null) { - _converter = new Lazy(() => new InternalEnumConverter(options), LazyThreadSafetyMode.ExecutionAndPublication); + _options = options; } /// public override bool CanConvert(Type typeToConvert) { if (typeToConvert.IsEnum) return true; - if (typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(Nullable<>)) return typeToConvert.GenericTypeArguments.First().IsEnum; - return false; + return Nullable.GetUnderlyingType(typeToConvert)?.IsEnum ?? false; } /// - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions _) => CreateConverter(typeToConvert, _options); + + internal static JsonConverter CreateConverter(Type typeToConvert, EnumConverterOptions? options) { - return _converter.Value; + //? Use a cache per enum type? + //* Depending on the amount of same enumeration types, this may be beneficial. On the other hand it may keep converter instances alive, that are only used one time. + var converter = Activator.CreateInstance(typeof(InternalEnumConverter<>).MakeGenericType(typeToConvert), args: options); + return (JsonConverter) converter; } #region Nested Types - + /// - /// Custom json converter for s. + /// Provides static properties for the generic . /// - internal class InternalEnumConverter : JsonConverter + internal class InternalEnumConverterHelper { - private static readonly Random Random; - - private static readonly Regex ValueCleanRegEx; - - private readonly EnumConverterOptions _options; + internal static readonly Random Random; + + /// + /// Matches the first whitespace not preceded by a comma and everything afterwards. + /// + internal static readonly Regex ValueCleanRegEx; - static InternalEnumConverter() + static InternalEnumConverterHelper() { Random = new Random(); - ValueCleanRegEx = new Regex(@"(? + /// Custom json converter for s. + /// + internal class InternalEnumConverter : JsonConverter + { + #region Delegates / Events + #endregion + + #region Constants + #endregion + + #region Fields + + private readonly EnumConverterOptions _options; + + private readonly Type _enumType; + + private readonly bool _isNullable; + + private readonly object? _defaultValue; + #endregion + + #region Properties + + /// + /// https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/converters-how-to?pivots=dotnet-6-0#handle-null-values + public override bool HandleNull => true; + + #endregion + + #region (De)Constructors + /// /// Constructor /// /// The used for (de)serialization. public InternalEnumConverter(EnumConverterOptions? options = null) { + // Save parameters. _options = options ?? new EnumConverterOptions(); + + // Initialize fields. + _enumType = Nullable.GetUnderlyingType(typeof(TEnum)) ?? typeof(TEnum); + _defaultValue = GetDefaultValue(); + _isNullable = _defaultValue is null; } + #endregion + + #region Methods + #region Deserialization /// - public override Enum? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - => this.Deserialize(reader.GetString(), typeToConvert); + public override TEnum? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => this.Deserialize(reader.GetString()); - internal Enum? Deserialize(string? value, Type enumerationType) + internal TEnum? Deserialize(string? value) { value = CleanEnumValue(value); - var isNullable = enumerationType.IsGenericType && enumerationType.GetGenericTypeDefinition() == typeof(Nullable<>); if (String.IsNullOrWhiteSpace(value)) { - if (isNullable) return null; + if (_isNullable) return default; else { - var defaultEnumerationValue = GetDefaultValue(enumerationType); - if (defaultEnumerationValue is null) throw new JsonException($"Cannot convert the value '{value}' into a {enumerationType.Name}."); - return defaultEnumerationValue as Enum; + //var defaultEnumerationValue = GetDefaultValue(enumerationType); + var defaultEnumerationValue = _defaultValue; + if (defaultEnumerationValue is null) throw new JsonException($"Cannot convert the value '{value}' into a {_enumType.Name}."); + return (TEnum?) defaultEnumerationValue; } } - - // If the target type is nullable, but the value is not null, than change the nullable for the underlying enumeration type. - if (isNullable) enumerationType = enumerationType.GenericTypeArguments.First(); - + #if NET5_0_OR_GREATER - if (Enum.TryParse(enumerationType, value, true, out var enumeration)) + if (Enum.TryParse(_enumType, value, true, out var enumeration)) { #else - if (TryParseEnum(value!, enumerationType, out var enumeration)) + if (TryParseEnum(value!, _enumType, out var enumeration)) { #endif - - return enumeration as Enum; + return (TEnum?) enumeration; } else { - throw new JsonException($"Cannot convert the value '{value}' into {enumerationType.Name}."); + throw new JsonException($"Cannot convert the value '{value}' into {_enumType.Name}."); } } @@ -177,31 +196,24 @@ public InternalEnumConverter(EnumConverterOptions? options = null) #region Serialization /// - public override void Write(Utf8JsonWriter writer, Enum? enumeration, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, TEnum? enumeration, JsonSerializerOptions _) { - if (enumeration is null) return; - writer.WriteStringValue(this.Serialize(enumeration)); - if (_options.WriteOutOptions is ValueWriteOutAsComment commentOptions) - { - writer.WriteCommentValue(String.Join(commentOptions.Separator, GetEnumerationValues(enumeration.GetType()))); - } - else if (_options.WriteOutOptions is ValueWriteOutAsProperty) - { - writer.WriteStartArray($"Values_for_{enumeration.GetType().Name}_{Random.Next(ushort.MinValue, ushort.MaxValue)}"); - foreach (var enumerationValue in GetEnumerationValues(enumeration.GetType())) - { - writer.WriteStringValue(enumerationValue); - } - writer.WriteEndArray(); - } + var enumString = this.Serialize(enumeration); + writer.WriteStringValue(enumString); } - internal string? Serialize(Enum? enumeration) + internal string? Serialize(TEnum? enumeration) { - if (enumeration is null) return null; - var suffix = String.Empty; - if (_options.WriteOutOptions is ValueWriteOutAsSuffix suffixOptions) suffix = $" {suffixOptions.Start}{String.Join(suffixOptions.Separator, GetEnumerationValues(enumeration.GetType()))}{suffixOptions.End}"; - return $"{enumeration}{suffix}"; + var enumString = enumeration?.ToString(); + var enumValues = GetEnumerationValues(_enumType, _isNullable); + + if (_options.WriteOutOptions is ValueWriteOutAsSuffix suffixOptions) + { + var suffix = $" {suffixOptions.Start}{String.Join(suffixOptions.Separator, enumValues)}{suffixOptions.End}"; + return $"{enumString ?? "null"}{suffix}"; + } + + return enumString; } #endregion @@ -224,35 +236,28 @@ private static bool TryParseEnum(string value, Type enumerationType, out object? } #endif - private static IReadOnlyCollection GetEnumerationValues(Type enumerationType) + private static IEnumerable GetEnumerationValues(Type enumerationType, bool isNullable) { + if (isNullable) yield return "null"; var enumValues = enumerationType.GetEnumValues(); - var stringValues = new List(enumValues.Length); foreach (var enumValue in enumValues) { - stringValues.Add(enumValue.ToString()!); + yield return enumValue.ToString()!; } - return stringValues.ToArray(); } private static string? CleanEnumValue(string? value) { if (value == null) return null; - - // Since whitespaces are not allowed withing identifiers, this can be used to remove value write-out as suffix. - //! Flags enumerations are serialized as a comma separated list containing whitespaces. Therefore a RegEx approach has been chosen. - // var separatorPosition = value.IndexOf(" ", StringComparison.OrdinalIgnoreCase); - // if (separatorPosition < 0) return value; - //#if NET5_0_OR_GREATER - // return value[..separatorPosition]; - //#else - // return value.Substring(0, separatorPosition); - //#endif - return ValueCleanRegEx.Replace(value, String.Empty); + var cleanedValue = InternalEnumConverterHelper.ValueCleanRegEx.Replace(value, String.Empty); + return cleanedValue == "null" ? null : cleanedValue; // This is necessary because the WriteOutOption 'AsSuffix' needs to wrap null values inside a string. } - internal static object? GetDefaultValue(Type enumerationType) + internal static object? GetDefaultValue(/*Type enumerationType*/) { + if (Nullable.GetUnderlyingType(typeof(TEnum)) is not null) return null; + var enumerationType = typeof(TEnum); + // Check for default attribute and return its value if available. var attribute = enumerationType.GetCustomAttribute(inherit: false); if (attribute?.Value != null) return attribute.Value; @@ -279,6 +284,8 @@ private static IReadOnlyCollection GetEnumerationValues(Type enumeration } #endregion + + #endregion } #endregion diff --git a/src/Settings.Serializers.Json.Net/JsonSettingsSerializer.cs b/src/Settings.Serializers.Json.Net/JsonSettingsSerializer.cs index 3ec9c09..11249e0 100644 --- a/src/Settings.Serializers.Json.Net/JsonSettingsSerializer.cs +++ b/src/Settings.Serializers.Json.Net/JsonSettingsSerializer.cs @@ -67,7 +67,6 @@ public JsonSettingsSerializer(JsonSerializerOptions? jsonSerializerOptions = nul if ( !ContainsConverter(customJsonConverters) - && !ContainsConverter(customJsonConverters) && !ContainsConverter(customJsonConverters) ) this.AddDefaultJsonConverter(); diff --git a/src/Settings.Serializers.Json.Net/Settings.Serializers.Json.Net.csproj b/src/Settings.Serializers.Json.Net/Settings.Serializers.Json.Net.csproj index 84acb7b..130eb4e 100644 --- a/src/Settings.Serializers.Json.Net/Settings.Serializers.Json.Net.csproj +++ b/src/Settings.Serializers.Json.Net/Settings.Serializers.Json.Net.csproj @@ -7,10 +7,10 @@ enable Phoenix.Functionality.Settings.Serializers.Json.Net Phoenix.Functionality.Settings.Serializers.Json.Net - 3.2.0 + 3.3.0 Felix Leistner Little Phoenix - 2022 + 2023 JSON based implementation for the common settings infastructure using JsonSerializer. Phoenix.Functionality.Settings.Serializers.Json.Net Phoenix.Functionality.Settings.Serializers.Json.Net diff --git "a/src/Settings.Serializers.Json.Net/\342\254\231/CHANGELOG.md" "b/src/Settings.Serializers.Json.Net/\342\254\231/CHANGELOG.md" index 9c40a88..168f451 100644 --- "a/src/Settings.Serializers.Json.Net/\342\254\231/CHANGELOG.md" +++ "b/src/Settings.Serializers.Json.Net/\342\254\231/CHANGELOG.md" @@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ___ +## 3.3.0 + +:calendar: _2023-03-28_ + +### Fixed + +- The custom `EnumConverter` did not work for **Nullable** enumerations. Using such lead to an **InvalidOperationException**. This was due to the **System.Text.Json.JsonSerializer** not passing null values to the custom converter at all (see [here](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/converters-how-to?pivots=dotnet-6-0#handle-null-values)). + +### Removed + +- The already obsolete enumeration `IWriteOutOptions` **as comment** and **as property** have been removed. +___ + ## 3.2.0 :calendar: _2022-12-30_ diff --git a/src/Settings.Sinks.File.Test/Settings.Sinks.File.Test.csproj b/src/Settings.Sinks.File.Test/Settings.Sinks.File.Test.csproj index 955d9e2..55f16f4 100644 --- a/src/Settings.Sinks.File.Test/Settings.Sinks.File.Test.csproj +++ b/src/Settings.Sinks.File.Test/Settings.Sinks.File.Test.csproj @@ -9,11 +9,11 @@ - - + + - - + + diff --git a/src/Settings.Test/Settings.Test.csproj b/src/Settings.Test/Settings.Test.csproj index 0755aea..7ca0a70 100644 --- a/src/Settings.Test/Settings.Test.csproj +++ b/src/Settings.Test/Settings.Test.csproj @@ -9,11 +9,11 @@ - - + + - - + + diff --git a/src/Settings.Test/SettingsManagerTest.cs b/src/Settings.Test/SettingsManagerTest.cs index 7b83379..aca5c8a 100644 --- a/src/Settings.Test/SettingsManagerTest.cs +++ b/src/Settings.Test/SettingsManagerTest.cs @@ -280,6 +280,28 @@ public void Check_Load_Throws_If_Deserialization_Fails() Mock.Get(manager).Verify(mock => mock.Save(It.IsAny(), It.IsAny()), Times.Never); } + /// + /// Checks that loading unavailable settings with the preventCreation parameter set to true throws the . + /// + [Test] + public void Check_Load_Throws_If_Settings_Data_Is_Unavailable() + { + // Arrange + var sink = _fixture.Create>>().Object; + Mock.Get(sink) + .Setup(mock => mock.Retrieve()) + .Returns((string?) null) //! This needs to return null to simulate unavailable settings. + .Verifiable() + ; + _fixture.Inject(sink); + var serializer = _fixture.Create>>().Object; + _fixture.Inject(serializer); + var manager = _fixture.Create>>().Object; + + // Act + Assert + Assert.Catch(() => manager.Load(preventCreation: true)); + } + [Test] public void Check_Load_Uses_Existing_Data() { diff --git a/src/Settings/ISettingsManager.cs b/src/Settings/ISettingsManager.cs index a88c041..1ed76f7 100644 --- a/src/Settings/ISettingsManager.cs +++ b/src/Settings/ISettingsManager.cs @@ -31,11 +31,12 @@ public interface ISettingsManager /// Loads the settings of type . /// /// The concrete settings type. - /// In prevents the to use its internal when loading settings. Default is false. - /// This prevents the from creating and saving a default instance of if no underlying settings data is available. Default is false. - /// This prevents the from updating the underlying data source in case the settings instance differs from it. Default is false. + /// Prevents the from using its internal when loading settings. Default is false. + /// Prevents the from creating and saving a default instance of if no underlying settings data is available. Default is false. + /// Prevents the from updating the underlying data source in case the settings instance differs from it. Default is false. /// A new instance of . /// May be thrown if the settings data could not be loaded. + /// May be thrown if the settings data could not be loaded and is true. TSettings Load(bool bypassCache = false, bool preventCreation = false, bool preventUpdate = false) where TSettings : class, ISettings, new(); /// diff --git a/src/Settings/Settings.csproj b/src/Settings/Settings.csproj index c06ce1c..9570ef4 100644 --- a/src/Settings/Settings.csproj +++ b/src/Settings/Settings.csproj @@ -7,10 +7,10 @@ enable Phoenix.Functionality.Settings Phoenix.Functionality.Settings - 3.1.0 + 3.2.0 Felix Leistner Little Phoenix - 2022 + 2023 Common settings infastructure for assemblies. Phoenix.Functionality.Settings Phoenix.Functionality.Settings diff --git a/src/Settings/SettingsException.cs b/src/Settings/SettingsException.cs index 170dcfb..f0c8dcc 100644 --- a/src/Settings/SettingsException.cs +++ b/src/Settings/SettingsException.cs @@ -26,6 +26,15 @@ public class SettingsLoadException : SettingsException public SettingsLoadException(string? message = null, Exception? innerException = null) : base(message, innerException) { } } +/// +/// Special used if no settings data could be loaded and creation of default data is explicitly disabled when loading settings. +/// +public class SettingsUnavailableException : SettingsLoadException +{ + /// + public SettingsUnavailableException() { } +} + /// /// Base exception used when saving settings fails (e.g. ). /// diff --git a/src/Settings/SettingsManager.cs b/src/Settings/SettingsManager.cs index 661cb43..beb9153 100644 --- a/src/Settings/SettingsManager.cs +++ b/src/Settings/SettingsManager.cs @@ -85,7 +85,7 @@ public TSettings Load(bool bypassCache = false, bool preventCreation if (settingsData is null) { // Create a new settings instance if possible. - settings = preventCreation ? throw new SettingsLoadException() : this.GetAndSaveDefaultInstance(preventUpdate); + settings = preventCreation ? throw new SettingsUnavailableException() : this.GetAndSaveDefaultInstance(preventUpdate); // Setup usage of the special extension methods. settings.InitializeExtensionMethods(this); diff --git "a/src/Settings/\342\254\231/CHANGELOG.md" "b/src/Settings/\342\254\231/CHANGELOG.md" index 78b60b3..d40df5c 100644 --- "a/src/Settings/\342\254\231/CHANGELOG.md" +++ "b/src/Settings/\342\254\231/CHANGELOG.md" @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ___ +## 3.2.0 + +:calendar: _2023-03-28_ + +### Changed + +- When loading `ISettings` with the `ISettingsManager.Load` function having the `preventCreation` parameter set to **true**, the new `SettingsUnavailableException` may be thrown if no settings data is available. This can be used for settings migrations. The new exception `SettingsUnavailableException` inherits from `SettingsLoadException` and therefore does not break existing implementations. + +___ + ## 3.1.0 :calendar: _2022-05-10_