diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e9381e..258d944 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +# Future + +## Breaking +- `PhpTokenizer` class is now internal. +- Removed support for `net6.0` and `net7.0`. + +## Regular changes +- Integers and doubles without a value now give a better error message (`i:;` and `d:;`). + +## Performance +- Reduced time to decode / re-encode the input string. +- Reduced memory allocations both in the input re-encoding and the deserialization. +- Delay the materialization of strings when deserializing. This can avoid string allocations entirely for integers, + doubles and floats. + +## Internal +Split the deserialization into 3 phases: + 1. Validation of the input and counting of the data tokens. + 2. Parsing of the input into tokens + 3. Deserializations of the tokens into the target C# objects/structs. + +In version 1.4 and prior, this was a 2 step process. This is slightly slower on some inputs, but overall a little +neater because we're cleanly separating the tasks. + # 1.4.0 - Now targets .NET 6.0, 7.0 and 8.0 - Improved tokenization performance by allowing and forcing more aggresive inlining. diff --git a/PhpSerializerNET.Test/Deserialize/ArrayDeserialization.cs b/PhpSerializerNET.Test/Deserialize/ArrayDeserialization.cs index e14705c..31d259a 100644 --- a/PhpSerializerNET.Test/Deserialize/ArrayDeserialization.cs +++ b/PhpSerializerNET.Test/Deserialize/ArrayDeserialization.cs @@ -8,70 +8,70 @@ This Source Code Form is subject to the terms of the Mozilla Public using System.Collections; using System.Collections.Generic; using System.Linq; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; using PhpSerializerNET.Test.DataTypes; using static PhpSerializerNET.Test.DataTypes.DeserializeObjects; -namespace PhpSerializerNET.Test.Deserialize { - [TestClass] - public class DeserializeArraysTest { - [TestMethod] - public void ExplicitToClass() { - var deserializedObject = PhpSerialization.Deserialize( - "a:5:{s:7:\"AString\";s:22:\"this is a string value\";s:9:\"AnInteger\";i:10;s:7:\"ADouble\";d:1.2345;s:4:\"True\";b:1;s:5:\"False\";b:0;}" - ); - Assert.AreEqual("this is a string value", deserializedObject.AString); - Assert.AreEqual(10, deserializedObject.AnInteger); - Assert.AreEqual(1.2345, deserializedObject.ADouble); - Assert.AreEqual(true, deserializedObject.True); - Assert.AreEqual(false, deserializedObject.False); - } +namespace PhpSerializerNET.Test.Deserialize; + +public class DeserializeArraysTest { + [Fact] + public void ExplicitToClass() { + var deserializedObject = PhpSerialization.Deserialize( + "a:5:{s:7:\"AString\";s:22:\"this is a string value\";s:9:\"AnInteger\";i:10;s:7:\"ADouble\";d:1.2345;s:4:\"True\";b:1;s:5:\"False\";b:0;}" + ); + Assert.Equal("this is a string value", deserializedObject.AString); + Assert.Equal(10, deserializedObject.AnInteger); + Assert.Equal(1.2345, deserializedObject.ADouble); + Assert.True(deserializedObject.True); + Assert.False(deserializedObject.False); + } - [TestMethod] - public void ExplicitToClassFormatException() { - var ex = Assert.ThrowsException(() => - PhpSerialization.Deserialize("a:1:{s:9:\"AnInteger\";s:3:\"1b1\";}") - ); - Assert.AreEqual( - "Exception encountered while trying to assign '1b1' to SimpleClass.AnInteger. See inner exception for details.", - ex.Message - ); - } + [Fact] + public void ExplicitToClassFormatException() { + var ex = Assert.Throws(() => + PhpSerialization.Deserialize("a:1:{s:9:\"AnInteger\";s:3:\"1b1\";}") + ); + Assert.Equal( + "Exception encountered while trying to assign '1b1' to SimpleClass.AnInteger. See inner exception for details.", + ex.Message + ); + } - [TestMethod] - public void ExplicitToClassWrongProperty() { - var ex = Assert.ThrowsException(() => - PhpSerialization.Deserialize( - "a:1:{s:7:\"BString\";s:22:\"this is a string value\";}" - ) - ); - Assert.AreEqual("Could not bind the key \"BString\" to object of type SimpleClass: No such property.", ex.Message); - } + [Fact] + public void ExplicitToClassWrongProperty() { + var ex = Assert.Throws(() => + PhpSerialization.Deserialize( + "a:1:{s:7:\"BString\";s:22:\"this is a string value\";}" + ) + ); + Assert.Equal("Could not bind the key \"BString\" to object of type SimpleClass: No such property.", ex.Message); + } - [TestMethod] - public void ExplicitToDictionaryOfObject() { - var result = PhpSerialization.Deserialize>( - "a:5:{s:7:\"AString\";s:22:\"this is a string value\";s:9:\"AnInteger\";i:10;s:7:\"ADouble\";d:1.2345;s:4:\"True\";b:1;s:5:\"False\";b:0;}" - ); + [Fact] + public void ExplicitToDictionaryOfObject() { + var result = PhpSerialization.Deserialize>( + "a:5:{s:7:\"AString\";s:22:\"this is a string value\";s:9:\"AnInteger\";i:10;s:7:\"ADouble\";d:1.2345;s:4:\"True\";b:1;s:5:\"False\";b:0;}" + ); - Assert.IsInstanceOfType(result, typeof(Dictionary)); - Assert.AreEqual(5, result.Count); + Assert.IsType>(result); + Assert.Equal(5, result.Count); - Assert.AreEqual("this is a string value", result["AString"]); - Assert.AreEqual((long)10, result["AnInteger"]); - Assert.AreEqual(1.2345, result["ADouble"]); - Assert.AreEqual(true, result["True"]); - Assert.AreEqual(false, result["False"]); - } + Assert.Equal("this is a string value", result["AString"]); + Assert.Equal((long)10, result["AnInteger"]); + Assert.Equal(1.2345, result["ADouble"]); + Assert.Equal(true, result["True"]); + Assert.Equal(false, result["False"]); + } - [TestMethod] - public void ExplicitToDictionaryOfComplexType() { - var result = PhpSerialization.Deserialize>( - "a:1:{s:4:\"AKey\";a:5:{s:7:\"AString\";s:22:\"this is a string value\";s:9:\"AnInteger\";i:10;s:7:\"ADouble\";d:1.2345;s:4:\"True\";b:1;s:5:\"False\";b:0;}}" - ); + [Fact] + public void ExplicitToDictionaryOfComplexType() { + var result = PhpSerialization.Deserialize>( + "a:1:{s:4:\"AKey\";a:5:{s:7:\"AString\";s:22:\"this is a string value\";s:9:\"AnInteger\";i:10;s:7:\"ADouble\";d:1.2345;s:4:\"True\";b:1;s:5:\"False\";b:0;}}" + ); - var expected = new Dictionary - { + var expected = new Dictionary + { { "AKey", new SimpleClass @@ -86,165 +86,157 @@ public void ExplicitToDictionaryOfComplexType() { } }; - // No easy way to assert dicts in MsTest :/ + // No easy way to assert dicts in MsTest :/ - Assert.AreEqual(expected.Count, result.Count); + Assert.Equal(expected.Count, result.Count); - foreach (var ((expectedKey, expectedValue), (actualKey, actualValue)) in expected.Zip(result)) { - Assert.AreEqual(expectedKey, actualKey); - Assert.AreEqual(expectedValue.ADouble, actualValue.ADouble); - Assert.AreEqual(expectedValue.AString, actualValue.AString); - Assert.AreEqual(expectedValue.AnInteger, actualValue.AnInteger); - Assert.AreEqual(expectedValue.False, actualValue.False); - Assert.AreEqual(expectedValue.True, actualValue.True); - } + foreach (var ((expectedKey, expectedValue), (actualKey, actualValue)) in expected.Zip(result)) { + Assert.Equal(expectedKey, actualKey); + Assert.Equal(expectedValue.ADouble, actualValue.ADouble); + Assert.Equal(expectedValue.AString, actualValue.AString); + Assert.Equal(expectedValue.AnInteger, actualValue.AnInteger); + Assert.Equal(expectedValue.False, actualValue.False); + Assert.Equal(expectedValue.True, actualValue.True); } + } - [TestMethod] - public void ExplicitToHashtable() { - var result = PhpSerialization.Deserialize( - "a:5:{i:0;s:22:\"this is a string value\";i:1;i:10;i:2;d:1.2345;s:4:\"True\";b:1;s:5:\"False\";b:0;}" - ); - - Assert.IsInstanceOfType(result, typeof(Hashtable)); - Assert.AreEqual(5, result.Count); - // the cast to long on the keys is because of the hashtable and C# intrinsics. - // (int)0 and (long)0 aren't identical enough for the hashtable - Assert.AreEqual("this is a string value", result[(long)0]); - Assert.AreEqual((long)10, result[(long)1]); - Assert.AreEqual(1.2345, result[(long)2]); - Assert.AreEqual(true, result["True"]); - Assert.AreEqual(false, result["False"]); - } + [Fact] + public void ExplicitToHashtable() { + var result = PhpSerialization.Deserialize( + "a:5:{i:0;s:22:\"this is a string value\";i:1;i:10;i:2;d:1.2345;s:4:\"True\";b:1;s:5:\"False\";b:0;}" + ); + + Assert.IsType(result); + Assert.Equal(5, result.Count); + // the cast to long on the keys is because of the hashtable and C# intrinsics. + // (int)0 and (long)0 aren't identical enough for the hashtable + Assert.Equal("this is a string value", result[(long)0]); + Assert.Equal((long)10, result[(long)1]); + Assert.Equal(1.2345, result[(long)2]); + Assert.Equal(true, result["True"]); + Assert.Equal(false, result["False"]); + } - [TestMethod] - public void ExplicitToClass_MappingInfo() { - var deserializedObject = PhpSerialization.Deserialize( - "a:3:{s:2:\"en\";s:12:\"Hello World!\";s:2:\"de\";s:11:\"Hallo Welt!\";s:2:\"It\";s:11:\"Ciao mondo!\";}" - ); - - // en and de mapped to differently named property: - Assert.AreEqual("Hello World!", deserializedObject.English); - Assert.AreEqual("Hallo Welt!", deserializedObject.German); - // "it" correctly ignored: - Assert.AreEqual(null, deserializedObject.It); - } + [Fact] + public void ExplicitToClass_MappingInfo() { + var deserializedObject = PhpSerialization.Deserialize( + "a:3:{s:2:\"en\";s:12:\"Hello World!\";s:2:\"de\";s:11:\"Hallo Welt!\";s:2:\"It\";s:11:\"Ciao mondo!\";}" + ); + + // en and de mapped to differently named property: + Assert.Equal("Hello World!", deserializedObject.English); + Assert.Equal("Hallo Welt!", deserializedObject.German); + // "it" correctly ignored: + Assert.Null(deserializedObject.It); + } - [TestMethod] - public void ExplicitToStruct() { - var value = PhpSerialization.Deserialize( - "a:2:{s:3:\"foo\";s:3:\"Foo\";s:3:\"bar\";s:3:\"Bar\";}" - ); - - Assert.AreEqual( - "Foo", - value.foo - ); - Assert.AreEqual( - "Bar", - value.bar - ); - } + [Fact] + public void ExplicitToStruct() { + var value = PhpSerialization.Deserialize( + "a:2:{s:3:\"foo\";s:3:\"Foo\";s:3:\"bar\";s:3:\"Bar\";}" + ); + + Assert.Equal( + "Foo", + value.foo + ); + Assert.Equal( + "Bar", + value.bar + ); + } - [TestMethod] - public void ExplicitToStructWrongField() { - var ex = Assert.ThrowsException(() => - PhpSerialization.Deserialize( - "a:1:{s:7:\"BString\";s:22:\"this is a string value\";}" - ) - ); - Assert.AreEqual("Could not bind the key \"BString\" to struct of type AStruct: No such field.", ex.Message); - } + [Fact] + public void ExplicitToStructWrongField() { + var ex = Assert.Throws(() => + PhpSerialization.Deserialize( + "a:1:{s:7:\"BString\";s:22:\"this is a string value\";}" + ) + ); + Assert.Equal("Could not bind the key \"BString\" to struct of type AStruct: No such field.", ex.Message); + } - [TestMethod] - public void ExplicitToList() { - var result = PhpSerialization.Deserialize>("a:3:{i:0;s:5:\"Hello\";i:1;s:5:\"World\";i:2;i:12345;}"); + [Fact] + public void ExplicitToList() { + var result = PhpSerialization.Deserialize>("a:3:{i:0;s:5:\"Hello\";i:1;s:5:\"World\";i:2;i:12345;}"); - CollectionAssert.AreEqual(new List() { "Hello", "World", "12345" }, result); - } + Assert.Equal(3, result.Count); + Assert.Equal(new List() { "Hello", "World", "12345" }, result); + } - [TestMethod] - public void ExplicitToArray() { - var result = PhpSerialization.Deserialize("a:3:{i:0;s:5:\"Hello\";i:1;s:5:\"World\";i:2;i:12345;}"); + [Fact] + public void ExplicitToArray() { + var result = PhpSerialization.Deserialize("a:3:{i:0;s:5:\"Hello\";i:1;s:5:\"World\";i:2;i:12345;}"); - CollectionAssert.AreEqual(new string[] { "Hello", "World", "12345" }, result); - } + Assert.Equal(new string[] { "Hello", "World", "12345" }, result); + } - [TestMethod] - public void ExplicitToListNonIntegerKey() { - var ex = Assert.ThrowsException(() => - PhpSerialization.Deserialize>("a:3:{i:0;s:5:\"Hello\";s:1:\"a\";s:5:\"World\";i:2;i:12345;}") - ); + [Fact] + public void ExplicitToListNonIntegerKey() { + var ex = Assert.Throws(() => + PhpSerialization.Deserialize>("a:3:{i:0;s:5:\"Hello\";s:1:\"a\";s:5:\"World\";i:2;i:12345;}") + ); - Assert.AreEqual("Can not deserialize array at position 0 to list: It has a non-integer key 'a' at element 2 (position 21).", ex.Message); - } + Assert.Equal("Can not deserialize array at position 0 to list: It has a non-integer key 'a' at element 2 (position 21).", ex.Message); + } - [TestMethod] - public void ExplicitToEmptyList() { - var result = PhpSerialization.Deserialize>("a:0:{}"); - CollectionAssert.AreEqual(new List(), result); - } + [Fact] + public void ExplicitToEmptyList() { + var result = PhpSerialization.Deserialize>("a:0:{}"); + Assert.Equal(new List(), result); + } - [TestMethod] - public void ImplicitToDictionary() { - var result = PhpSerialization.Deserialize( - "a:5:{s:7:\"AString\";s:22:\"this is a string value\";s:9:\"AnInteger\";i:10;s:7:\"ADouble\";d:1.2345;s:4:\"True\";b:1;s:5:\"False\";b:0;}" - ); - - Assert.IsInstanceOfType(result, typeof(Dictionary)); - var dictionary = result as Dictionary; - Assert.AreEqual(5, dictionary.Count); - - Assert.AreEqual("this is a string value", dictionary["AString"]); - Assert.AreEqual((long)10, dictionary["AnInteger"]); - Assert.AreEqual(1.2345, dictionary["ADouble"]); - Assert.AreEqual(true, dictionary["True"]); - Assert.AreEqual(false, dictionary["False"]); - } + [Fact] + public void ImplicitToDictionary() { + var result = PhpSerialization.Deserialize( + "a:5:{s:7:\"AString\";s:22:\"this is a string value\";s:9:\"AnInteger\";i:10;s:7:\"ADouble\";d:1.2345;s:4:\"True\";b:1;s:5:\"False\";b:0;}" + ); + + Assert.IsType>(result); + var dictionary = result as Dictionary; + Assert.Equal(5, dictionary.Count); + + Assert.Equal("this is a string value", dictionary["AString"]); + Assert.Equal((long)10, dictionary["AnInteger"]); + Assert.Equal(1.2345, dictionary["ADouble"]); + Assert.Equal(true, dictionary["True"]); + Assert.Equal(false, dictionary["False"]); + } - [TestMethod] - public void ExcplicitToNestedObject() { - var result = PhpSerialization.Deserialize("a:2:{s:3:\"Foo\";s:5:\"First\";s:3:\"Bar\";a:2:{s:3:\"Foo\";s:6:\"Second\";s:3:\"Bar\";N;}}"); - - Assert.AreEqual( - "First", - result.Foo - ); - Assert.IsNotNull( - result.Bar - ); - Assert.AreEqual( - "Second", - result.Bar.Foo - ); - } + [Fact] + public void ExcplicitToNestedObject() { + var result = PhpSerialization.Deserialize("a:2:{s:3:\"Foo\";s:5:\"First\";s:3:\"Bar\";a:2:{s:3:\"Foo\";s:6:\"Second\";s:3:\"Bar\";N;}}"); - [TestMethod] - public void Test_Issue11() { - // See https://github.com/StringEpsilon/PhpSerializerNET/issues/11 - var deserializedObject = PhpSerialization.Deserialize( - "a:1:{i:0;a:7:{s:1:\"A\";N;s:1:\"B\";N;s:1:\"C\";s:1:\"C\";s:5:\"odSdr\";i:1;s:1:\"D\";d:1;s:1:\"E\";N;s:1:\"F\";a:3:{s:1:\"X\";i:8;s:1:\"Y\";N;s:1:\"Z\";N;}}}" - ); - Assert.IsNotNull(deserializedObject); - } + Assert.Equal("First", result.Foo); + Assert.NotNull(result.Bar); + Assert.Equal("Second", result.Bar.Foo); + } - [TestMethod] - public void Test_Issue12() { - // See https://github.com/StringEpsilon/PhpSerializerNET/issues/12 - var result = PhpSerialization.Deserialize("a:1:{i:0;a:4:{s:1:\"A\";s:2:\"63\";s:1:\"B\";a:2:{i:558710;s:1:\"2\";i:558709;s:1:\"2\";}s:1:\"C\";s:2:\"71\";s:1:\"G\";a:3:{s:1:\"x\";s:6:\"446368\";s:1:\"y\";s:1:\"0\";s:1:\"z\";s:5:\"1.029\";}}}"); - Assert.IsNotNull(result); - } + [Fact] + public void Test_Issue11() { + // See https://github.com/StringEpsilon/PhpSerializerNET/issues/11 + var deserializedObject = PhpSerialization.Deserialize( + "a:1:{i:0;a:7:{s:1:\"A\";N;s:1:\"B\";N;s:1:\"C\";s:1:\"C\";s:5:\"odSdr\";i:1;s:1:\"D\";d:1;s:1:\"E\";N;s:1:\"F\";a:3:{s:1:\"X\";i:8;s:1:\"Y\";N;s:1:\"Z\";N;}}}" + ); + Assert.NotNull(deserializedObject); + } + + [Fact] + public void Test_Issue12() { + // See https://github.com/StringEpsilon/PhpSerializerNET/issues/12 + var result = PhpSerialization.Deserialize("a:1:{i:0;a:4:{s:1:\"A\";s:2:\"63\";s:1:\"B\";a:2:{i:558710;s:1:\"2\";i:558709;s:1:\"2\";}s:1:\"C\";s:2:\"71\";s:1:\"G\";a:3:{s:1:\"x\";s:6:\"446368\";s:1:\"y\";s:1:\"0\";s:1:\"z\";s:5:\"1.029\";}}}"); + Assert.NotNull(result); + } - [TestMethod] - public void MixedKeyArrayIntoObject() { - var result = PhpSerialization.Deserialize( - "a:4:{i:0;s:3:\"Foo\";i:1;s:3:\"Bar\";s:1:\"a\";s:1:\"A\";s:1:\"b\";s:1:\"B\";}" - ); + [Fact] + public void MixedKeyArrayIntoObject() { + var result = PhpSerialization.Deserialize( + "a:4:{i:0;s:3:\"Foo\";i:1;s:3:\"Bar\";s:1:\"a\";s:1:\"A\";s:1:\"b\";s:1:\"B\";}" + ); - Assert.AreEqual("Foo", result.Foo); - Assert.AreEqual("Bar", result.Bar); - Assert.AreEqual("A", result.Baz); - Assert.AreEqual("B", result.Dummy); - } + Assert.Equal("Foo", result.Foo); + Assert.Equal("Bar", result.Bar); + Assert.Equal("A", result.Baz); + Assert.Equal("B", result.Dummy); } } \ No newline at end of file diff --git a/PhpSerializerNET.Test/Deserialize/BooleanDeserialization.cs b/PhpSerializerNET.Test/Deserialize/BooleanDeserialization.cs index e72cc71..1726f89 100644 --- a/PhpSerializerNET.Test/Deserialize/BooleanDeserialization.cs +++ b/PhpSerializerNET.Test/Deserialize/BooleanDeserialization.cs @@ -5,72 +5,58 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace PhpSerializerNET.Test.Deserialize { - [TestClass] - public class DeserializeBooleansTest { - [TestMethod] - public void DeserializesTrue() { - Assert.AreEqual( - true, - PhpSerialization.Deserialize("b:1;") - ); - } - - [TestMethod] - public void DeserializesTrueExplicit() { - - Assert.AreEqual( - true, - PhpSerialization.Deserialize("b:1;") - ); - } - - [TestMethod] - public void DeserializesFalse() { - Assert.AreEqual( - false, - PhpSerialization.Deserialize("b:0;") - ); - } - - [TestMethod] - public void DeserializesFalseExplicit() { - Assert.AreEqual( - false, - PhpSerialization.Deserialize("b:0;") - ); - } - - [TestMethod] - public void DeserializesToLong() { - var result = PhpSerialization.Deserialize("b:0;"); - - Assert.AreEqual(0, result); - - result = PhpSerialization.Deserialize("b:1;"); - - Assert.AreEqual(1, result); - } - - [TestMethod] - public void DeserializesToString() { - var result = PhpSerialization.Deserialize("b:0;"); - - Assert.AreEqual("False", result); - - result = PhpSerialization.Deserialize("b:1;"); - - Assert.AreEqual("True", result); - } - - [TestMethod] - public void DeserializeToNullable() { - Assert.AreEqual( - false, - PhpSerialization.Deserialize("b:0;") - ); - } +using Xunit; + +namespace PhpSerializerNET.Test.Deserialize; + +public class DeserializeBooleansTest { + [Fact] + public void DeserializesTrue() { + Assert.Equal(true, PhpSerialization.Deserialize("b:1;")); + } + + [Fact] + public void DeserializesTrueExplicit() { + Assert.True(PhpSerialization.Deserialize("b:1;")); + } + + [Fact] + public void DeserializesFalse() { + Assert.Equal(false, PhpSerialization.Deserialize("b:0;")); + } + + [Fact] + public void DeserializesFalseExplicit() { + Assert.False(PhpSerialization.Deserialize("b:0;")); + } + + [Fact] + public void DeserializesToLong() { + var result = PhpSerialization.Deserialize("b:0;"); + + Assert.Equal(0, result); + + result = PhpSerialization.Deserialize("b:1;"); + + Assert.Equal(1, result); + } + + [Fact] + public void DeserializesToString() { + var result = PhpSerialization.Deserialize("b:0;"); + + Assert.Equal("False", result); + + result = PhpSerialization.Deserialize("b:1;"); + + Assert.Equal("True", result); + } + + [Fact] + public void DeserializeToNullable() { + Assert.Equal( + false, + PhpSerialization.Deserialize("b:0;") + ); } } \ No newline at end of file diff --git a/PhpSerializerNET.Test/Deserialize/DeserializeStructs.cs b/PhpSerializerNET.Test/Deserialize/DeserializeStructs.cs index 032dffb..01b84c8 100644 --- a/PhpSerializerNET.Test/Deserialize/DeserializeStructs.cs +++ b/PhpSerializerNET.Test/Deserialize/DeserializeStructs.cs @@ -4,86 +4,84 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; using PhpSerializerNET.Test.DataTypes; -namespace PhpSerializerNET.Test.Deserialize { +namespace PhpSerializerNET.Test.Deserialize; - [TestClass] - public class DeserializeStructsTest { - [TestMethod] - public void DeserializeArrayToStruct() { - var value = PhpSerialization.Deserialize( - "a:2:{s:3:\"foo\";s:3:\"Foo\";s:3:\"bar\";s:3:\"Bar\";}" - ); - Assert.AreEqual("Foo", value.foo); - Assert.AreEqual("Bar", value.bar); - } +public class DeserializeStructsTest { + [Fact] + public void DeserializeArrayToStruct() { + var value = PhpSerialization.Deserialize( + "a:2:{s:3:\"foo\";s:3:\"Foo\";s:3:\"bar\";s:3:\"Bar\";}" + ); + Assert.Equal("Foo", value.foo); + Assert.Equal("Bar", value.bar); + } - [TestMethod] - public void DeserializeObjectToStruct() { - var value = PhpSerialization.Deserialize( - "O:8:\"sdtClass\":2:{s:3:\"foo\";s:3:\"Foo\";s:3:\"bar\";s:3:\"Bar\";}" - ); - Assert.AreEqual("Foo", value.foo); - Assert.AreEqual("Bar", value.bar); - } + [Fact] + public void DeserializeObjectToStruct() { + var value = PhpSerialization.Deserialize( + "O:8:\"sdtClass\":2:{s:3:\"foo\";s:3:\"Foo\";s:3:\"bar\";s:3:\"Bar\";}" + ); + Assert.Equal("Foo", value.foo); + Assert.Equal("Bar", value.bar); + } - [TestMethod] - public void DeserializeWithIgnoredField() { - var value = PhpSerialization.Deserialize( - "a:2:{s:3:\"foo\";s:3:\"Foo\";s:3:\"bar\";s:3:\"Bar\";}" - ); - Assert.AreEqual("Foo", value.foo); - Assert.AreEqual(null, value.bar); - } + [Fact] + public void DeserializeWithIgnoredField() { + var value = PhpSerialization.Deserialize( + "a:2:{s:3:\"foo\";s:3:\"Foo\";s:3:\"bar\";s:3:\"Bar\";}" + ); + Assert.Equal("Foo", value.foo); + Assert.Null(value.bar); + } - [TestMethod] - public void DeserializePropertyName() { - var value = PhpSerialization.Deserialize( - "a:2:{s:3:\"foo\";s:3:\"Foo\";s:6:\"foobar\";s:3:\"Bar\";}" - ); - Assert.AreEqual("Foo", value.foo); - Assert.AreEqual("Bar", value.bar); - } + [Fact] + public void DeserializePropertyName() { + var value = PhpSerialization.Deserialize( + "a:2:{s:3:\"foo\";s:3:\"Foo\";s:6:\"foobar\";s:3:\"Bar\";}" + ); + Assert.Equal("Foo", value.foo); + Assert.Equal("Bar", value.bar); + } - [TestMethod] - public void DeserializeBoolToStruct() { - var ex = Assert.ThrowsException( - () => PhpSerialization.Deserialize( - "b:1;" - ) - ); + [Fact] + public void DeserializeBoolToStruct() { + var ex = Assert.Throws( + () => PhpSerialization.Deserialize( + "b:1;" + ) + ); - Assert.AreEqual( - "Can not assign value \"1\" (at position 0) to target type of AStruct.", - ex.Message - ); - } + Assert.Equal( + "Can not assign value \"1\" (at position 0) to target type of AStruct.", + ex.Message + ); + } - [TestMethod] - public void DeserializeStringToStruct() { - var ex = Assert.ThrowsException( - () => PhpSerialization.Deserialize( - "s:3:\"foo\";" - ) - ); + [Fact] + public void DeserializeStringToStruct() { + var ex = Assert.Throws( + () => PhpSerialization.Deserialize( + "s:3:\"foo\";" + ) + ); - Assert.AreEqual( - "Can not assign value \"foo\" (at position 0) to target type of AStruct.", - ex.Message - ); - } + Assert.Equal( + "Can not assign value \"foo\" (at position 0) to target type of AStruct.", + ex.Message + ); + } - [TestMethod] - public void DeserializeNullToStruct() { - Assert.AreEqual( - default, - PhpSerialization.Deserialize( - "N;" - ) - ); - } + [Fact] + public void DeserializeNullToStruct() { + Assert.Equal( + default, + PhpSerialization.Deserialize( + "N;" + ) + ); } } \ No newline at end of file diff --git a/PhpSerializerNET.Test/Deserialize/DoubleDeserialization.cs b/PhpSerializerNET.Test/Deserialize/DoubleDeserialization.cs index 933345d..5f8213d 100644 --- a/PhpSerializerNET.Test/Deserialize/DoubleDeserialization.cs +++ b/PhpSerializerNET.Test/Deserialize/DoubleDeserialization.cs @@ -6,133 +6,132 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace PhpSerializerNET.Test.Deserialize { - [TestClass] - public class DoubleDeserializationTest { - - [TestMethod] - public void DeserializesNormalValue() { - Assert.AreEqual( - 1.23456789, - PhpSerialization.Deserialize("d:1.23456789;") - ); - Assert.AreEqual( - 1.23456789, - PhpSerialization.Deserialize("d:1.23456789;") - ); - } - - [TestMethod] - public void DeserializesOne() { - Assert.AreEqual( - (double)1, - PhpSerialization.Deserialize("d:1;") - ); - } - - [TestMethod] - public void DeserializesMinValue() { - Assert.AreEqual( - double.MinValue, - PhpSerialization.Deserialize("d:-1.7976931348623157E+308;") - ); - } - - [TestMethod] - public void DeserializesMaxValue() { - Assert.AreEqual( - double.MaxValue, - PhpSerialization.Deserialize("d:1.7976931348623157E+308;") - ); - } - - [TestMethod] - public void DeserializesInfinity() { - Assert.AreEqual( - double.PositiveInfinity, - PhpSerialization.Deserialize("d:INF;") - ); - } - - [TestMethod] - public void DeserializesNegativeInfinity() { - Assert.AreEqual( - double.NegativeInfinity, - PhpSerialization.Deserialize("d:-INF;") - ); - } - - [TestMethod] - public void DeserializesNotANumber() { - Assert.AreEqual( - double.NaN, - PhpSerialization.Deserialize("d:NAN;") - ); - } - - [TestMethod] - public void Explicit_DeserializesInfinity() { - Assert.AreEqual( - double.PositiveInfinity, - PhpSerialization.Deserialize("d:INF;") - ); - } - - [TestMethod] - public void Explicit_DeserializesNegativeInfinity() { - Assert.AreEqual( - double.NegativeInfinity, - PhpSerialization.Deserialize("d:-INF;") - ); - } - - [TestMethod] - public void Explicit_DeserializesNotANumber() { - Assert.AreEqual( - double.NaN, - PhpSerialization.Deserialize("d:NAN;") - ); - } - - [TestMethod] - public void Explicit_Nullable_DeserializesInfinity() { - Assert.AreEqual( - double.PositiveInfinity, - PhpSerialization.Deserialize("d:INF;") - ); - } - - [TestMethod] - public void Explicit_Nullable_DeserializesNegativeInfinity() { - Assert.AreEqual( - double.NegativeInfinity, - PhpSerialization.Deserialize("d:-INF;") - ); - } - - [TestMethod] - public void Explicit_Nullable_DeserializesNotANumber() { - Assert.AreEqual( - double.NaN, - PhpSerialization.Deserialize("d:NAN;") - ); - } - - [TestMethod] - public void DeserializesToNullable() { - Assert.AreEqual( - 3.1415, - PhpSerialization.Deserialize("d:3.1415;") - ); - } - - [TestMethod] - public void DeserializeDoubleToInt() { - double number = PhpSerialization.Deserialize("d:10;"); - Assert.AreEqual((long)10, number); - } +using Xunit; + +namespace PhpSerializerNET.Test.Deserialize; + +public class DoubleDeserializationTest { + + [Fact] + public void DeserializesNormalValue() { + Assert.Equal( + 1.23456789, + PhpSerialization.Deserialize("d:1.23456789;") + ); + Assert.Equal( + 1.23456789, + PhpSerialization.Deserialize("d:1.23456789;") + ); + } + + [Fact] + public void DeserializesOne() { + Assert.Equal( + (double)1, + PhpSerialization.Deserialize("d:1;") + ); + } + + [Fact] + public void DeserializesMinValue() { + Assert.Equal( + double.MinValue, + PhpSerialization.Deserialize("d:-1.7976931348623157E+308;") + ); + } + + [Fact] + public void DeserializesMaxValue() { + Assert.Equal( + double.MaxValue, + PhpSerialization.Deserialize("d:1.7976931348623157E+308;") + ); + } + + [Fact] + public void DeserializesInfinity() { + Assert.Equal( + double.PositiveInfinity, + PhpSerialization.Deserialize("d:INF;") + ); + } + + [Fact] + public void DeserializesNegativeInfinity() { + Assert.Equal( + double.NegativeInfinity, + PhpSerialization.Deserialize("d:-INF;") + ); + } + + [Fact] + public void DeserializesNotANumber() { + Assert.Equal( + double.NaN, + PhpSerialization.Deserialize("d:NAN;") + ); + } + + [Fact] + public void Explicit_DeserializesInfinity() { + Assert.Equal( + double.PositiveInfinity, + PhpSerialization.Deserialize("d:INF;") + ); + } + + [Fact] + public void Explicit_DeserializesNegativeInfinity() { + Assert.Equal( + double.NegativeInfinity, + PhpSerialization.Deserialize("d:-INF;") + ); + } + + [Fact] + public void Explicit_DeserializesNotANumber() { + Assert.Equal( + double.NaN, + PhpSerialization.Deserialize("d:NAN;") + ); + } + + [Fact] + public void Explicit_Nullable_DeserializesInfinity() { + Assert.Equal( + double.PositiveInfinity, + PhpSerialization.Deserialize("d:INF;") + ); + } + [Fact] + public void Explicit_Nullable_DeserializesNegativeInfinity() { + Assert.Equal( + double.NegativeInfinity, + PhpSerialization.Deserialize("d:-INF;") + ); } + + [Fact] + public void Explicit_Nullable_DeserializesNotANumber() { + Assert.Equal( + double.NaN, + PhpSerialization.Deserialize("d:NAN;") + ); + } + + [Fact] + public void DeserializesToNullable() { + Assert.Equal( + 3.1415, + PhpSerialization.Deserialize("d:3.1415;") + ); + } + + [Fact] + public void DeserializeDoubleToInt() { + double number = PhpSerialization.Deserialize("d:10;"); + Assert.Equal(10, number); + } + } \ No newline at end of file diff --git a/PhpSerializerNET.Test/Deserialize/EnumDeserialization.cs b/PhpSerializerNET.Test/Deserialize/EnumDeserialization.cs index 4aaa5b6..c512138 100644 --- a/PhpSerializerNET.Test/Deserialize/EnumDeserialization.cs +++ b/PhpSerializerNET.Test/Deserialize/EnumDeserialization.cs @@ -5,63 +5,61 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; using PhpSerializerNET.Test.DataTypes; -namespace PhpSerializerNET.Test.Deserialize { +namespace PhpSerializerNET.Test.Deserialize; - [TestClass] - public class EnumDeserializationTest { +public class EnumDeserializationTest { - [TestMethod] - public void DeserializeLongBasedEnum() { - Assert.AreEqual( - IntEnum.A, - PhpSerialization.Deserialize("i:1;") - ); - } + [Fact] + public void DeserializeLongBasedEnum() { + Assert.Equal( + IntEnum.A, + PhpSerialization.Deserialize("i:1;") + ); + } - [TestMethod] - public void DeserializeIntBasedEnum() { - Assert.AreEqual( - LongEnum.A, - PhpSerialization.Deserialize("i:1;") - ); - } + [Fact] + public void DeserializeIntBasedEnum() { + Assert.Equal( + LongEnum.A, + PhpSerialization.Deserialize("i:1;") + ); + } - [TestMethod] - public void DeserializeFromString() { - Assert.AreEqual( - LongEnum.A, - PhpSerialization.Deserialize("s:1:\"A\";") - ); - } + [Fact] + public void DeserializeFromString() { + Assert.Equal( + LongEnum.A, + PhpSerialization.Deserialize("s:1:\"A\";") + ); + } - [TestMethod] - public void DeserializeFromStringWithPropertyName() { - Assert.AreEqual( - IntEnumWithPropertyName.A, - PhpSerialization.Deserialize("s:1:\"a\";") - ); + [Fact] + public void DeserializeFromStringWithPropertyName() { + Assert.Equal( + IntEnumWithPropertyName.A, + PhpSerialization.Deserialize("s:1:\"a\";") + ); - Assert.AreEqual( - IntEnumWithPropertyName.B, - PhpSerialization.Deserialize("s:1:\"c\";") - ); + Assert.Equal( + IntEnumWithPropertyName.B, + PhpSerialization.Deserialize("s:1:\"c\";") + ); - Assert.AreEqual( - IntEnumWithPropertyName.C, - PhpSerialization.Deserialize("s:1:\"C\";") - ); - } + Assert.Equal( + IntEnumWithPropertyName.C, + PhpSerialization.Deserialize("s:1:\"C\";") + ); + } - [TestMethod] - public void DeserializeToNullable() { - LongEnum? result = PhpSerialization.Deserialize("i:1;"); - Assert.AreEqual( - LongEnum.A, - result - ); - } + [Fact] + public void DeserializeToNullable() { + LongEnum? result = PhpSerialization.Deserialize("i:1;"); + Assert.Equal( + LongEnum.A, + result + ); } } \ No newline at end of file diff --git a/PhpSerializerNET.Test/Deserialize/IPhpObjectDeserialization.cs b/PhpSerializerNET.Test/Deserialize/IPhpObjectDeserialization.cs index b2e04bb..6003114 100644 --- a/PhpSerializerNET.Test/Deserialize/IPhpObjectDeserialization.cs +++ b/PhpSerializerNET.Test/Deserialize/IPhpObjectDeserialization.cs @@ -3,38 +3,37 @@ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; using PhpSerializerNET.Test.DataTypes; -namespace PhpSerializerNET.Test { - [TestClass] - public class IPhpObjectDeserializationTest { +namespace PhpSerializerNET.Test; - [TestMethod] - public void DeerializesIPhpObject() { // #Issue 25 - var result = PhpSerialization.Deserialize("O:11:\"MyPhpObject\":1:{s:3:\"Foo\";s:0:\"\";}"); - Assert.AreEqual( // strings: - "MyPhpObject", - result.GetClassName() - ); - } +public class IPhpObjectDeserializationTest { - [TestMethod] - public void DeerializesPhpObjectDictionary() { - var result = PhpSerialization.Deserialize("O:11:\"MyPhpObject\":1:{s:3:\"Foo\";s:0:\"\";}"); - Assert.AreEqual( // strings: - "MyPhpObject", - result.GetClassName() - ); - } + [Fact] + public void DeerializesIPhpObject() { // #Issue 25 + var result = PhpSerialization.Deserialize("O:11:\"MyPhpObject\":1:{s:3:\"Foo\";s:0:\"\";}"); + Assert.Equal( // strings: + "MyPhpObject", + result.GetClassName() + ); + } + + [Fact] + public void DeerializesPhpObjectDictionary() { + var result = PhpSerialization.Deserialize("O:11:\"MyPhpObject\":1:{s:3:\"Foo\";s:0:\"\";}"); + Assert.Equal( // strings: + "MyPhpObject", + result.GetClassName() + ); + } - [TestMethod] - public void DeerializesIPhpObjectStruct() { - var result = PhpSerialization.Deserialize("O:11:\"MyPhpObject\":1:{s:3:\"Foo\";s:0:\"\";}"); - Assert.AreEqual( // strings: - "MyPhpObject", - result.GetClassName() - ); - } + [Fact] + public void DeerializesIPhpObjectStruct() { + var result = PhpSerialization.Deserialize("O:11:\"MyPhpObject\":1:{s:3:\"Foo\";s:0:\"\";}"); + Assert.Equal( // strings: + "MyPhpObject", + result.GetClassName() + ); } } \ No newline at end of file diff --git a/PhpSerializerNET.Test/Deserialize/IntegerDeserialization.cs b/PhpSerializerNET.Test/Deserialize/IntegerDeserialization.cs index 5ba7446..7f40520 100644 --- a/PhpSerializerNET.Test/Deserialize/IntegerDeserialization.cs +++ b/PhpSerializerNET.Test/Deserialize/IntegerDeserialization.cs @@ -5,69 +5,40 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace PhpSerializerNET.Test.Deserialize { - [TestClass] - public class IntegerDeserializationTest { - [TestMethod] - public void DeserializeZero() { - Assert.AreEqual( - 0, - PhpSerialization.Deserialize("i:0;") - ); - } - - [TestMethod] - public void DeserializeOne() { - Assert.AreEqual( - 1, - PhpSerialization.Deserialize("i:1;") - ); - } - - [TestMethod] - public void DeserializeToNullable() { - var result = PhpSerialization.Deserialize("i:1;"); - Assert.IsInstanceOfType(result, typeof(int?)); - Assert.AreEqual( - 1, - result.Value - ); - } - - [TestMethod] - public void DeserializeIntMaxValue() { - Assert.AreEqual( - int.MaxValue, - PhpSerialization.Deserialize("i:2147483647;") - ); - } - - [TestMethod] - public void DeserializeIntMinValue() { - Assert.AreEqual( - int.MinValue, - PhpSerialization.Deserialize("i:-2147483648;") - ); - } +using Xunit; + +namespace PhpSerializerNET.Test.Deserialize; + +public class IntegerDeserializationTest { + [Theory] + [InlineData("i:0;", 0)] + [InlineData("i:1;", 1)] + [InlineData("i:2147483647;", int.MaxValue)] + [InlineData("i:-2147483648;", int.MinValue)] + public void DeserializeZero(string input, int expected) { + Assert.Equal(expected, PhpSerialization.Deserialize(input)); + } - [TestMethod] - public void DeserializeIntToDouble() { - double number = PhpSerialization.Deserialize("i:10;"); - Assert.AreEqual(10.00, number); - } + [Fact] + public void DeserializeToNullable() { + var result = PhpSerialization.Deserialize("i:1;"); + Assert.Equal(1, result.Value); + } - [TestMethod] - public void ExplictCastFormatException() { - var ex = Assert.ThrowsException(() => - PhpSerialization.Deserialize( - "s:3:\"1b1\";" - ) - ); - Assert.IsInstanceOfType(ex.InnerException, typeof(System.FormatException)); - Assert.AreEqual("Exception encountered while trying to assign '1b1' to type Int32. See inner exception for details.", ex.Message); - } + [Fact] + public void DeserializeIntToDouble() { + double number = PhpSerialization.Deserialize("i:10;"); + Assert.Equal(10.00, number); + } + [Fact] + public void ExplictCastFormatException() { + var ex = Assert.Throws(() => + PhpSerialization.Deserialize( + "s:3:\"1b1\";" + ) + ); + Assert.IsType(ex.InnerException); + Assert.Equal("Exception encountered while trying to assign '1b1' to type Int32. See inner exception for details.", ex.Message); } -} \ No newline at end of file +} diff --git a/PhpSerializerNET.Test/Deserialize/LongDeserialization.cs b/PhpSerializerNET.Test/Deserialize/LongDeserialization.cs index 811a127..3b427fa 100644 --- a/PhpSerializerNET.Test/Deserialize/LongDeserialization.cs +++ b/PhpSerializerNET.Test/Deserialize/LongDeserialization.cs @@ -5,109 +5,39 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; -namespace PhpSerializerNET.Test.Deserialize { - [TestClass] - public class LongDeserializationTest { +namespace PhpSerializerNET.Test.Deserialize; - [TestMethod] - public void DeserializeZero() { - Assert.AreEqual( - (long)0, - PhpSerialization.Deserialize("i:0;") - ); - } - - [TestMethod] - public void DeserializeOne() { - Assert.AreEqual( - (long)1, - PhpSerialization.Deserialize("i:1;") - ); - } - - - [TestMethod] - public void DeserializeMaxValue() { - Assert.AreEqual( - long.MaxValue, - PhpSerialization.Deserialize("i:9223372036854775807;") - ); - } - - [TestMethod] - public void DeserializeMinValue() { - Assert.AreEqual( - long.MinValue, - PhpSerialization.Deserialize("i:-9223372036854775808;") - ); - } - - [TestMethod] - public void DeserializesNullToZero() { - var result = PhpSerialization.Deserialize("N;"); - - Assert.AreEqual(0, result); - } - - [TestMethod] - public void DeserializesToNullable() { - var result = PhpSerialization.Deserialize("N;"); - - Assert.AreEqual(null, result); - - result = PhpSerialization.Deserialize("i:1;"); - - Assert.AreEqual(1, result); - } - - [TestMethod] - public void SupportsOtherNumberTypes() { - Assert.AreEqual( - short.MinValue, - PhpSerialization.Deserialize($"i:{short.MinValue};") - ); - Assert.AreEqual( - short.MaxValue, - PhpSerialization.Deserialize($"i:{short.MaxValue};") - ); - - Assert.AreEqual( - ushort.MinValue, - PhpSerialization.Deserialize($"i:{ushort.MinValue};") - ); - Assert.AreEqual( - ushort.MaxValue, - PhpSerialization.Deserialize($"i:{ushort.MaxValue};") - ); - - Assert.AreEqual( - uint.MinValue, - PhpSerialization.Deserialize($"i:{uint.MinValue};") - ); - Assert.AreEqual( - uint.MaxValue, - PhpSerialization.Deserialize($"i:{uint.MaxValue};") - ); - - Assert.AreEqual( - ulong.MinValue, - PhpSerialization.Deserialize($"i:{ulong.MinValue};") - ); - Assert.AreEqual( - ulong.MaxValue, - PhpSerialization.Deserialize($"i:{ulong.MaxValue};") - ); +public class LongDeserializationTest { + [Theory] + [InlineData("N;", null)] + [InlineData("i:1;", 1L)] + public void SupportsNull(string input, T expected) { + var result = PhpSerialization.Deserialize(input); + Assert.Equal(expected, result); + } - Assert.AreEqual( - sbyte.MinValue, - PhpSerialization.Deserialize($"i:{sbyte.MinValue};") - ); - Assert.AreEqual( - sbyte.MaxValue, - PhpSerialization.Deserialize($"i:{sbyte.MaxValue};") - ); - } + [Theory] + [InlineData("i:-32768;", short.MinValue)] + [InlineData("i:32767;", short.MaxValue)] + [InlineData("i:0;", ushort.MinValue)] + [InlineData("i:65535;", ushort.MaxValue)] + [InlineData("i:0;", uint.MinValue)] + [InlineData("i:4294967295;", uint.MaxValue)] + [InlineData("N;", 0L)] + [InlineData("i:0;", 0L)] + [InlineData("i:1;", 1L)] + [InlineData("i:-9223372036854775808;", long.MinValue)] + [InlineData("i:9223372036854775807;", long.MaxValue)] + [InlineData("i:0;", ulong.MinValue)] + [InlineData("i:18446744073709551615;", ulong.MaxValue)] + [InlineData("i:-128;", sbyte.MinValue)] + [InlineData("i:127;", sbyte.MaxValue)] + [InlineData("i:255;", byte.MaxValue)] + public void SupportsOtherNumberTypes(string input, T expected) { + var result = PhpSerialization.Deserialize(input); + Assert.IsType(result); + Assert.Equal(expected, result); } } \ No newline at end of file diff --git a/PhpSerializerNET.Test/Deserialize/NullDeserialization.cs b/PhpSerializerNET.Test/Deserialize/NullDeserialization.cs index eb59e19..acb3576 100644 --- a/PhpSerializerNET.Test/Deserialize/NullDeserialization.cs +++ b/PhpSerializerNET.Test/Deserialize/NullDeserialization.cs @@ -6,51 +6,35 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; using PhpSerializerNET.Test.DataTypes; -namespace PhpSerializerNET.Test.Deserialize { - [TestClass] - public class NullDeserializationTest { - [TestMethod] - public void DeserializesNull() { - var result = PhpSerialization.Deserialize("N;"); - - Assert.IsNull(result); - } - - [TestMethod] - public void DeserializesExplicitNull() { - var result = PhpSerialization.Deserialize("N;"); - - Assert.IsNull(result); - } - - [TestMethod] - public void DeserializesToNullableStruct() { - var result = PhpSerialization.Deserialize("N;"); - - Assert.IsNull(result); - } - - [TestMethod] - public void ExplicitToPrimitiveDefaultValues() { - Assert.AreEqual( - 0, - PhpSerialization.Deserialize("N;") - ); - Assert.AreEqual( - false, - PhpSerialization.Deserialize("N;") - ); - Assert.AreEqual( - 0, - PhpSerialization.Deserialize("N;") - ); - Assert.AreEqual( - null, - PhpSerialization.Deserialize("N;") - ); - } +namespace PhpSerializerNET.Test.Deserialize; + +public class NullDeserializationTest { + [Fact] + public void DeserializesNull() { + var result = PhpSerialization.Deserialize("N;"); + + Assert.Null(result); + } + + [Fact] + public void DeserializesExplicitNull() { + var result = PhpSerialization.Deserialize("N;"); + Assert.Null(result); + } + + [Fact] + public void DeserializesToNullableStruct() { + var result = PhpSerialization.Deserialize("N;"); + Assert.Null(result); + } + + [Fact] + public void ExplicitToPrimitiveDefaultValues() { + Assert.False(PhpSerialization.Deserialize("N;")); + Assert.Equal(0, PhpSerialization.Deserialize("N;")); + Assert.Null(PhpSerialization.Deserialize("N;")); } } \ No newline at end of file diff --git a/PhpSerializerNET.Test/Deserialize/ObjectDeserialization.cs b/PhpSerializerNET.Test/Deserialize/ObjectDeserialization.cs index feb3050..89eaf63 100644 --- a/PhpSerializerNET.Test/Deserialize/ObjectDeserialization.cs +++ b/PhpSerializerNET.Test/Deserialize/ObjectDeserialization.cs @@ -5,23 +5,22 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; using PhpSerializerNET.Test.DataTypes; -namespace PhpSerializerNET.Test.Deserialize { - [TestClass] - public class ObjectDeserializationTest { - [TestMethod] - public void IntegerKeysClass() { - var result = PhpSerialization.Deserialize( - "O:8:\"stdClass\":4:{i:0;s:3:\"Foo\";i:1;s:3:\"Bar\";s:1:\"a\";s:1:\"A\";s:1:\"b\";s:1:\"B\";}" - ); +namespace PhpSerializerNET.Test.Deserialize; - Assert.IsNotNull(result); - Assert.AreEqual("Foo", result.Foo); - Assert.AreEqual("Bar", result.Bar); - Assert.AreEqual("A", result.Baz); - Assert.AreEqual("B", result.Dummy); - } +public class ObjectDeserializationTest { + [Fact] + public void IntegerKeysClass() { + var result = PhpSerialization.Deserialize( + "O:8:\"stdClass\":4:{i:0;s:3:\"Foo\";i:1;s:3:\"Bar\";s:1:\"a\";s:1:\"A\";s:1:\"b\";s:1:\"B\";}" + ); + + Assert.NotNull(result); + Assert.Equal("Foo", result.Foo); + Assert.Equal("Bar", result.Bar); + Assert.Equal("A", result.Baz); + Assert.Equal("B", result.Dummy); } } diff --git a/PhpSerializerNET.Test/Deserialize/Options/AllowExcessKeys.cs b/PhpSerializerNET.Test/Deserialize/Options/AllowExcessKeys.cs index 36765c5..0178785 100644 --- a/PhpSerializerNET.Test/Deserialize/Options/AllowExcessKeys.cs +++ b/PhpSerializerNET.Test/Deserialize/Options/AllowExcessKeys.cs @@ -4,68 +4,67 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; using PhpSerializerNET.Test.DataTypes; namespace PhpSerializerNET.Test.Deserialize.Options { - [TestClass] public class AllowExcessKeysTest { const string StructTestInput = "a:3:{s:3:\"foo\";s:3:\"Foo\";s:3:\"bar\";s:3:\"Bar\";s:6:\"foobar\";s:6:\"FooBar\";}"; const string ObjectTestInput = "a:2:{s:7:\"AString\";s:3:\"foo\";s:7:\"BString\";s:3:\"bar\";}"; - [TestMethod] + [Fact] public void Struct_DeserializesWithOptionEnabled() { var value = PhpSerialization.Deserialize( StructTestInput, new PhpDeserializationOptions() { AllowExcessKeys = true } ); - Assert.AreEqual( + Assert.Equal( "Foo", value.foo ); - Assert.AreEqual( + Assert.Equal( "Bar", value.bar ); } - [TestMethod] + [Fact] public void Struct_ThrowsWithOptionDisabled() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize( + var ex = Assert.Throws(() => PhpSerialization.Deserialize( StructTestInput, new PhpDeserializationOptions() { AllowExcessKeys = false } )); - Assert.AreEqual("Could not bind the key \"foobar\" to struct of type AStruct: No such field.", ex.Message); + Assert.Equal("Could not bind the key \"foobar\" to struct of type AStruct: No such field.", ex.Message); } - [TestMethod] + [Fact] public void Object_DeserializesWithOptionEnabled() { var deserializedObject = PhpSerialization.Deserialize( ObjectTestInput, new PhpDeserializationOptions() { AllowExcessKeys = true } ); - Assert.IsNotNull(deserializedObject); + Assert.NotNull(deserializedObject); } - [TestMethod] + [Fact] public void Object_ThrowsWithOptionDisabled() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize( + var ex = Assert.Throws(() => PhpSerialization.Deserialize( ObjectTestInput, new PhpDeserializationOptions() { AllowExcessKeys = false } )); - Assert.AreEqual("Could not bind the key \"BString\" to object of type SimpleClass: No such property.", ex.Message); + Assert.Equal("Could not bind the key \"BString\" to object of type SimpleClass: No such property.", ex.Message); } - [TestMethod] + [Fact] public void Enabled_ProperlyAssignsAllKeys() { // Explicit test for issue #27. var result = PhpSerialization.Deserialize( "O:11:\"SimpleClass\":3:{s:1:\"_\";b:0;s:4:\"True\";b:1;s:5:\"False\";b:0;}", new PhpDeserializationOptions() { AllowExcessKeys = true } ); - Assert.AreEqual(true, result.True); - Assert.AreEqual(false, result.False); + Assert.True(result.True); + Assert.False(result.False); } } } \ No newline at end of file diff --git a/PhpSerializerNET.Test/Deserialize/Options/CaseSensitiveProperties.cs b/PhpSerializerNET.Test/Deserialize/Options/CaseSensitiveProperties.cs index f44f637..e99e54b 100644 --- a/PhpSerializerNET.Test/Deserialize/Options/CaseSensitiveProperties.cs +++ b/PhpSerializerNET.Test/Deserialize/Options/CaseSensitiveProperties.cs @@ -4,66 +4,65 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; using PhpSerializerNET.Test.DataTypes; namespace PhpSerializerNET.Test.Deserialize.Options { - [TestClass] public class CaseSensitivePropertiesTest { - [TestMethod] + [Fact] public void Disabled_Array_Deserializes() { var result = PhpSerialization.Deserialize( "a:2:{s:3:\"FOO\";s:3:\"Foo\";s:3:\"BAR\";s:3:\"Bar\";}", new PhpDeserializationOptions() { CaseSensitiveProperties = false } ); - Assert.AreEqual("Foo", result.foo); - Assert.AreEqual("Bar", result.bar); + Assert.Equal("Foo", result.foo); + Assert.Equal("Bar", result.bar); } - [TestMethod] + [Fact] public void Disabled_Object_Deserializes() { var result = PhpSerialization.Deserialize( "O:8:\"stdClass\":2:{s:3:\"FOO\";s:3:\"Foo\";s:3:\"BAR\";s:3:\"Bar\";}", new PhpDeserializationOptions() { CaseSensitiveProperties = false } ); - Assert.AreEqual("Foo", result.foo); - Assert.AreEqual("Bar", result.bar); + Assert.Equal("Foo", result.foo); + Assert.Equal("Bar", result.bar); } - [TestMethod] + [Fact] public void Enabled_Array_Throws() { - var exception = Assert.ThrowsException( + var exception = Assert.Throws( () => PhpSerialization.Deserialize( "a:2:{s:3:\"FOO\";s:3:\"Foo\";s:3:\"BAR\";s:3:\"Bar\";}", new PhpDeserializationOptions() { CaseSensitiveProperties = true } ) ); - Assert.AreEqual( + Assert.Equal( "Could not bind the key \"FOO\" to struct of type AStruct: No such field.", exception.Message ); } - [TestMethod] + [Fact] public void Enabled_Object_Throws() { - var exception = Assert.ThrowsException( + var exception = Assert.Throws( () => PhpSerialization.Deserialize( "O:8:\"stdClass\":2:{s:3:\"FOO\";s:3:\"Foo\";s:3:\"BAR\";s:3:\"Bar\";}", new PhpDeserializationOptions() { CaseSensitiveProperties = true } ) ); - Assert.AreEqual( + Assert.Equal( "Could not bind the key \"FOO\" to struct of type AStruct: No such field.", exception.Message ); } - [TestMethod] + [Fact] public void Disabled_Object_WorksWithMapping() { var deserializedObject = PhpSerialization.Deserialize( "O:8:\"stdClass\":2:{s:2:\"EN\";s:12:\"Hello World!\";s:2:\"DE\";s:11:\"Hallo Welt!\";}", @@ -71,11 +70,11 @@ public void Disabled_Object_WorksWithMapping() { ); // en and de mapped to differently named property: - Assert.AreEqual("Hello World!", deserializedObject.English); - Assert.AreEqual("Hallo Welt!", deserializedObject.German); + Assert.Equal("Hello World!", deserializedObject.English); + Assert.Equal("Hallo Welt!", deserializedObject.German); } - [TestMethod] + [Fact] public void Disabled_Array_WorksWithMapping() { var deserializedObject = PhpSerialization.Deserialize( "a:2:{s:2:\"EN\";s:12:\"Hello World!\";s:2:\"DE\";s:11:\"Hallo Welt!\";}", @@ -83,8 +82,8 @@ public void Disabled_Array_WorksWithMapping() { ); // en and de mapped to differently named property: - Assert.AreEqual("Hello World!", deserializedObject.English); - Assert.AreEqual("Hallo Welt!", deserializedObject.German); + Assert.Equal("Hello World!", deserializedObject.English); + Assert.Equal("Hallo Welt!", deserializedObject.German); } } } \ No newline at end of file diff --git a/PhpSerializerNET.Test/Deserialize/Options/EmptyStringToDefault.cs b/PhpSerializerNET.Test/Deserialize/Options/EmptyStringToDefault.cs index 18a76df..e8b8c46 100644 --- a/PhpSerializerNET.Test/Deserialize/Options/EmptyStringToDefault.cs +++ b/PhpSerializerNET.Test/Deserialize/Options/EmptyStringToDefault.cs @@ -7,168 +7,167 @@ This Source Code Form is subject to the terms of the Mozilla Public using System; using System.Collections.Generic; using System.Linq; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; using PhpSerializerNET.Test.DataTypes; namespace PhpSerializerNET.Test.Deserialize.Options { - [TestClass] public class EmptyStringToDefaultTest { private const string EmptyPhpStringInput = "s:0:\"\";"; #region Enabled - [TestMethod] + [Fact] public void Enabled_EmptyStringToInt() { var result = PhpSerialization.Deserialize(EmptyPhpStringInput); - Assert.AreEqual(default, result); + Assert.Equal(default, result); } - [TestMethod] + [Fact] public void Enabled_EmptyStringToLong() { var result = PhpSerialization.Deserialize(EmptyPhpStringInput); - Assert.AreEqual(default, result); + Assert.Equal(default, result); } - [TestMethod] + [Fact] public void Enabled_StringToDouble() { var result = PhpSerialization.Deserialize(EmptyPhpStringInput); - Assert.AreEqual(default, result); + Assert.Equal(default, result); } - [TestMethod] + [Fact] public void Enabled_StringToFloat() { var result = PhpSerialization.Deserialize(EmptyPhpStringInput); - Assert.AreEqual(default, result); + Assert.Equal(default, result); } - [TestMethod] + [Fact] public void Enabled_StringToDecimal() { var result = PhpSerialization.Deserialize(EmptyPhpStringInput); - Assert.AreEqual(default, result); + Assert.Equal(default, result); } - [TestMethod] + [Fact] public void Enabled_StringToBool() { var result = PhpSerialization.Deserialize(EmptyPhpStringInput); - Assert.AreEqual(default, result); + Assert.Equal(default, result); } - [TestMethod] + [Fact] public void Enabled_StringToChar() { var result = PhpSerialization.Deserialize(EmptyPhpStringInput); - Assert.AreEqual(default, result); + Assert.Equal(default, result); } - [TestMethod] + [Fact] public void Enabled_StringToEnum() { var result = PhpSerialization.Deserialize(EmptyPhpStringInput); - Assert.AreEqual(default, result); + Assert.Equal(default, result); } - [TestMethod] + [Fact] public void Enabled_StringToGuid() { var result = PhpSerialization.Deserialize(EmptyPhpStringInput); - Assert.AreEqual(default, result); + Assert.Equal(default, result); } - [TestMethod] + [Fact] public void Enabled_StringToString() { var result = PhpSerialization.Deserialize(EmptyPhpStringInput); - Assert.AreEqual(default, result); + Assert.Equal(default, result); } - [TestMethod] + [Fact] public void Enabled_StringToObject() { var result = PhpSerialization.Deserialize(EmptyPhpStringInput); - Assert.AreEqual(default, result); + Assert.Equal(default, result); } - [TestMethod] + [Fact] public void Enabled_StringToCustomObject() { var deserializedObject = PhpSerialization.Deserialize( "a:5:{s:7:\"AString\";s:0:\"\";s:9:\"AnInteger\";i:10;s:7:\"ADouble\";d:1.2345;s:4:\"True\";b:1;s:5:\"False\";b:0;}" ); - Assert.AreEqual(default, deserializedObject.AString); + Assert.Equal(default, deserializedObject.AString); } - [TestMethod] + [Fact] public void Enabled_StringArrayToCharCollection() { var result = PhpSerialization.Deserialize>("a:2:{i:0;s:0:\"\";i:1;s:0:\"\";}"); - CollectionAssert.AreEqual(new List { default, default }, result); + Assert.Equal(new List { default, default }, result); } #region Nullables - [TestMethod] + [Fact] public void Enabled_EmptyStringToIntNullable() { var result = PhpSerialization.Deserialize(EmptyPhpStringInput); - Assert.AreEqual(default, result); + Assert.Equal(default, result); } - [TestMethod] + [Fact] public void Enabled_EmptyStringToLongNullable() { var result = PhpSerialization.Deserialize(EmptyPhpStringInput); - Assert.AreEqual(default, result); + Assert.Equal(default, result); } - [TestMethod] + [Fact] public void Enabled_StringToDoubleNullable() { var result = PhpSerialization.Deserialize(EmptyPhpStringInput); - Assert.AreEqual(default, result); + Assert.Equal(default, result); } - [TestMethod] + [Fact] public void Enabled_StringToFloatNullable() { var result = PhpSerialization.Deserialize(EmptyPhpStringInput); - Assert.AreEqual(default, result); + Assert.Equal(default, result); } - [TestMethod] + [Fact] public void Enabled_StringToDecimalNullable() { var result = PhpSerialization.Deserialize(EmptyPhpStringInput); - Assert.AreEqual(default, result); + Assert.Equal(default, result); } - [TestMethod] + [Fact] public void Enabled_StringToBoolNullable() { var result = PhpSerialization.Deserialize(EmptyPhpStringInput); - Assert.AreEqual(default, result); + Assert.Equal(default, result); } - [TestMethod] + [Fact] public void Enabled_StringToCharNullable() { var result = PhpSerialization.Deserialize(EmptyPhpStringInput); - Assert.AreEqual(default, result); + Assert.Equal(default, result); } - [TestMethod] + [Fact] public void Enabled_StringToEnumNullable() { var result = PhpSerialization.Deserialize(EmptyPhpStringInput); - Assert.AreEqual(default, result); + Assert.Equal(default, result); } - [TestMethod] + [Fact] public void Enabled_StringToGuidNullable() { var result = PhpSerialization.Deserialize(EmptyPhpStringInput); - Assert.AreEqual(default, result); + Assert.Equal(default, result); } #endregion - [TestMethod] + [Fact] public void Enabled_StringArrayToIntList() { var result = PhpSerialization.Deserialize>("a:1:{i:0;s:0:\"\";}"); - CollectionAssert.AreEqual(new List { default }, result); + Assert.Equal(new List { default }, result); } - [TestMethod] + [Fact] public void Enabled_StringArrayToNullableIntList() { - var result = PhpSerialization.Deserialize>("a:1:{i:0;s:0:\"\";}"); - CollectionAssert.AreEqual(new List { default }, result); + var result = PhpSerialization.Deserialize>("a:1:{i:1;s:0:\"\";}"); + Assert.Equal(new List { default }, result); } - [TestMethod] + [Fact] public void Enabled_ClassArrayToClassDictionary() { var result = PhpSerialization.Deserialize>( "a:1:{s:4:\"AKey\";a:5:{s:7:\"AString\";s:0:\"\";s:9:\"AnInteger\";i:0;s:7:\"ADouble\";d:0;s:4:\"True\";b:0;s:5:\"False\";b:0;}}" @@ -192,15 +191,15 @@ public void Enabled_ClassArrayToClassDictionary() { // No easy way to assert dicts in MsTest :/ - Assert.AreEqual(expected.Count, result.Count); + Assert.Equal(expected.Count, result.Count); foreach (var ((expectedKey, expectedValue), (actualKey, actualValue)) in expected.Zip(result)) { - Assert.AreEqual(expectedKey, actualKey); - Assert.AreEqual(expectedValue.ADouble, actualValue.ADouble); - Assert.AreEqual(expectedValue.AString, actualValue.AString); - Assert.AreEqual(expectedValue.AnInteger, actualValue.AnInteger); - Assert.AreEqual(expectedValue.False, actualValue.False); - Assert.AreEqual(expectedValue.True, actualValue.True); + Assert.Equal(expectedKey, actualKey); + Assert.Equal(expectedValue.ADouble, actualValue.ADouble); + Assert.Equal(expectedValue.AString, actualValue.AString); + Assert.Equal(expectedValue.AnInteger, actualValue.AnInteger); + Assert.Equal(expectedValue.False, actualValue.False); + Assert.Equal(expectedValue.True, actualValue.True); } } @@ -208,37 +207,37 @@ public void Enabled_ClassArrayToClassDictionary() { #region Disabled - [TestMethod] + [Fact] public void Disabled_EmptyStringToInt() { - var exception = Assert.ThrowsException( + var exception = Assert.Throws( () => PhpSerialization.Deserialize(EmptyPhpStringInput, new PhpDeserializationOptions { EmptyStringToDefault = false }) ); - Assert.AreEqual( + Assert.Equal( "Exception encountered while trying to assign '' to type Int32. See inner exception for details.", exception.Message ); } - [TestMethod] + [Fact] public void Disabled_StringToBool() { - var exception = Assert.ThrowsException( + var exception = Assert.Throws( () => PhpSerialization.Deserialize(EmptyPhpStringInput, new PhpDeserializationOptions { EmptyStringToDefault = false }) ); - Assert.AreEqual( + Assert.Equal( "Exception encountered while trying to assign '' to type Boolean. See inner exception for details.", exception.Message ); } - [TestMethod] + [Fact] public void Disabled_StringToDouble() { - var exception = Assert.ThrowsException( + var exception = Assert.Throws( () => PhpSerialization.Deserialize(EmptyPhpStringInput, new PhpDeserializationOptions { EmptyStringToDefault = false }) ); - Assert.AreEqual( + Assert.Equal( "Exception encountered while trying to assign '' to type Double. See inner exception for details.", exception.Message ); diff --git a/PhpSerializerNET.Test/Deserialize/Options/EnableTypeLookup.cs b/PhpSerializerNET.Test/Deserialize/Options/EnableTypeLookup.cs index 49b512f..09b714d 100644 --- a/PhpSerializerNET.Test/Deserialize/Options/EnableTypeLookup.cs +++ b/PhpSerializerNET.Test/Deserialize/Options/EnableTypeLookup.cs @@ -4,44 +4,43 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; using PhpSerializerNET.Test.DataTypes; -namespace PhpSerializerNET.Test.Deserialize.Options { - [TestClass] - public class EnableTypeLookupTest { - [TestMethod] - public void Enabled_Finds_Class() { - var result = PhpSerialization.Deserialize( - "O:11:\"MappedClass\":2:{s:2:\"en\";s:12:\"Hello World!\";s:2:\"de\";s:11:\"Hallo Welt!\";}", - new PhpDeserializationOptions() { EnableTypeLookup = true } - ); - - Assert.IsInstanceOfType(result, typeof(MappedClass)); - - // Check that everything was deserialized onto the properties: - var mappedClass = result as MappedClass; - Assert.AreEqual("Hello World!", mappedClass.English); - Assert.AreEqual("Hallo Welt!", mappedClass.German); - } - - [TestMethod] - public void Disabled_UseStdClass() { - var result = PhpSerialization.Deserialize( - "O:11:\"MappedClass\":2:{s:2:\"en\";s:12:\"Hello World!\";s:2:\"de\";s:11:\"Hallo Welt!\";}", - new PhpDeserializationOptions() { - EnableTypeLookup = false, - StdClass = StdClassOption.Dictionary, - } - ); - - Assert.IsInstanceOfType(result, typeof(PhpSerializerNET.PhpObjectDictionary)); - - // Check that everything was deserialized onto the properties: - var dictionary = result as PhpObjectDictionary; - Assert.AreEqual("Hello World!", dictionary["en"]); - Assert.AreEqual("Hallo Welt!", dictionary["de"]); - } +namespace PhpSerializerNET.Test.Deserialize.Options; +public class EnableTypeLookupTest { + [Fact] + public void Enabled_Finds_Class() { + var result = PhpSerialization.Deserialize( + "O:11:\"MappedClass\":2:{s:2:\"en\";s:12:\"Hello World!\";s:2:\"de\";s:11:\"Hallo Welt!\";}", + new PhpDeserializationOptions() { EnableTypeLookup = true } + ); + + Assert.IsType(result); + + // Check that everything was deserialized onto the properties: + var mappedClass = result as MappedClass; + Assert.Equal("Hello World!", mappedClass.English); + Assert.Equal("Hallo Welt!", mappedClass.German); } + + [Fact] + public void Disabled_UseStdClass() { + var result = PhpSerialization.Deserialize( + "O:11:\"MappedClass\":2:{s:2:\"en\";s:12:\"Hello World!\";s:2:\"de\";s:11:\"Hallo Welt!\";}", + new PhpDeserializationOptions() { + EnableTypeLookup = false, + StdClass = StdClassOption.Dictionary, + } + ); + + Assert.IsType(result); + + // Check that everything was deserialized onto the properties: + var dictionary = result as PhpObjectDictionary; + Assert.Equal("Hello World!", dictionary["en"]); + Assert.Equal("Hallo Welt!", dictionary["de"]); + } + } \ No newline at end of file diff --git a/PhpSerializerNET.Test/Deserialize/Options/InputEncoding.cs b/PhpSerializerNET.Test/Deserialize/Options/InputEncoding.cs index 7eb03a0..fc27396 100644 --- a/PhpSerializerNET.Test/Deserialize/Options/InputEncoding.cs +++ b/PhpSerializerNET.Test/Deserialize/Options/InputEncoding.cs @@ -5,10 +5,9 @@ This Source Code Form is subject to the terms of the Mozilla Public **/ using System.Text; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; namespace PhpSerializerNET.Test.Deserialize.Options { - [TestClass] public class InputEncodingTest { private static readonly string Latin1TestString = Encoding.Latin1.GetString( Encoding.Convert( @@ -18,20 +17,20 @@ public class InputEncodingTest { ) ); - [TestMethod] + [Fact] public void WrongEncodingFails() { - var ex = Assert.ThrowsException( + var ex = Assert.Throws( () => PhpSerialization.Deserialize(Latin1TestString) ); // The deserialization failed, because the length of "äöü" in bytes is 6 in UTF8 but 3 in Latin1, // which results in a misalignment and failure to find the end of the string. // I have cross-checked that the PHP implementation (at least in versions I tested) fails for the same reason. - Assert.AreEqual("Unexpected token at index 8. Expected '\"' but found '¶' instead.", ex.Message); + Assert.Equal("Unexpected token at index 8. Expected '\"' but found '¶' instead.", ex.Message); } - [TestMethod] + [Fact] public void CorrectEncodingWorks() { var result = PhpSerialization.Deserialize( Latin1TestString, @@ -40,7 +39,7 @@ public void CorrectEncodingWorks() { } ); - Assert.AreEqual("äöü", result); + Assert.Equal("äöü", result); } } } \ No newline at end of file diff --git a/PhpSerializerNET.Test/Deserialize/Options/NumberStringToBool.cs b/PhpSerializerNET.Test/Deserialize/Options/NumberStringToBool.cs index 0023dbd..e96ce8b 100644 --- a/PhpSerializerNET.Test/Deserialize/Options/NumberStringToBool.cs +++ b/PhpSerializerNET.Test/Deserialize/Options/NumberStringToBool.cs @@ -4,37 +4,36 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; namespace PhpSerializerNET.Test.Deserialize.Options { - [TestClass] public class NumberStringToBoolTest { - [TestMethod] + [Fact] public void Enabled_Deserializes_Implicit() { var options = new PhpDeserializationOptions() { NumberStringToBool = true }; - Assert.AreEqual(true, PhpSerialization.Deserialize("s:1:\"1\";", options)); - Assert.AreEqual(false, PhpSerialization.Deserialize("s:1:\"0\";", options)); + Assert.Equal(true, PhpSerialization.Deserialize("s:1:\"1\";", options)); + Assert.Equal(false, PhpSerialization.Deserialize("s:1:\"0\";", options)); } - [TestMethod] + [Fact] public void Enabled_Deserializes_Explicit() { var options = new PhpDeserializationOptions() { NumberStringToBool = true }; - Assert.AreEqual(true, PhpSerialization.Deserialize("s:1:\"1\";", options)); - Assert.AreEqual(false, PhpSerialization.Deserialize("s:1:\"0\";", options)); + Assert.True(PhpSerialization.Deserialize("s:1:\"1\";", options)); + Assert.False(PhpSerialization.Deserialize("s:1:\"0\";", options)); } - [TestMethod] + [Fact] public void Disabled_Throws() { - var exception = Assert.ThrowsException( + var exception = Assert.Throws( () => PhpSerialization.Deserialize( "s:1:\"0\";", new PhpDeserializationOptions() { NumberStringToBool = false } ) ); - Assert.AreEqual( + Assert.Equal( "Exception encountered while trying to assign '0' to type Boolean. See inner exception for details.", exception.Message ); diff --git a/PhpSerializerNET.Test/Deserialize/Options/StdClass.cs b/PhpSerializerNET.Test/Deserialize/Options/StdClass.cs index 236de25..a1b80b1 100644 --- a/PhpSerializerNET.Test/Deserialize/Options/StdClass.cs +++ b/PhpSerializerNET.Test/Deserialize/Options/StdClass.cs @@ -6,88 +6,87 @@ This Source Code Form is subject to the terms of the Mozilla Public using System.Collections; using System.Collections.Generic; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; using PhpSerializerNET.Test.DataTypes; namespace PhpSerializerNET.Test.Deserialize.Options { - [TestClass] public class StdClassTest { public struct MyStruct { public double John; public double Jane; } - [TestMethod] + [Fact] public void Option_Throw() { - var ex = Assert.ThrowsException( + var ex = Assert.Throws( () => PhpSerialization.Deserialize( "O:8:\"stdClass\":2:{s:4:\"John\";d:3.14;s:4:\"Jane\";d:2.718;}", new PhpDeserializationOptions() { StdClass = StdClassOption.Throw } ) ); - Assert.AreEqual( + Assert.Equal( "Encountered 'stdClass' and the behavior 'Throw' was specified in deserialization options.", ex.Message ); } - [TestMethod] + [Fact] public void Option_Dynamic() { dynamic result = (PhpDynamicObject)PhpSerialization.Deserialize( "O:8:\"stdClass\":2:{s:4:\"John\";d:3.14;s:4:\"Jane\";d:2.718;}", new PhpDeserializationOptions() { StdClass = StdClassOption.Dynamic } ); - Assert.AreEqual(3.14, result.John); - Assert.AreEqual(2.718, result.Jane); - Assert.AreEqual("stdClass", result.GetClassName()); + Assert.Equal(3.14, result.John); + Assert.Equal(2.718, result.Jane); + Assert.Equal("stdClass", result.GetClassName()); } - [TestMethod] + [Fact] public void Option_Dictionary() { var result = (IDictionary)PhpSerialization.Deserialize( "O:8:\"stdClass\":2:{s:4:\"John\";d:3.14;s:4:\"Jane\";d:2.718;}", new PhpDeserializationOptions() { StdClass = StdClassOption.Dictionary } ); - Assert.AreEqual(3.14, result["John"]); - Assert.AreEqual(2.718, result["Jane"]); + Assert.Equal(3.14, result["John"]); + Assert.Equal(2.718, result["Jane"]); } - [TestMethod] + [Fact] public void Overridden_By_Class() { var result = PhpSerialization.Deserialize( "O:8:\"stdClass\":2:{s:3:\"Foo\";d:3.14;s:3:\"Bar\";d:2.718;}", new PhpDeserializationOptions() { StdClass = StdClassOption.Dynamic } ); - Assert.IsInstanceOfType(result, typeof(NamedClass)); - Assert.AreEqual(3.14, result.Foo); - Assert.AreEqual(2.718, result.Bar); + Assert.IsType(result); + Assert.Equal(3.14, result.Foo); + Assert.Equal(2.718, result.Bar); } - [TestMethod] + [Fact] public void Overridden_By_Struct() { var result = PhpSerialization.Deserialize( "O:8:\"stdClass\":2:{s:4:\"John\";d:3.14;s:4:\"Jane\";d:2.718;}", new PhpDeserializationOptions() { StdClass = StdClassOption.Dynamic } ); - Assert.IsInstanceOfType(result, typeof(MyStruct)); - Assert.AreEqual(3.14, result.John); - Assert.AreEqual(2.718, result.Jane); + Assert.IsType(result); + Assert.Equal(3.14, result.John); + Assert.Equal(2.718, result.Jane); } - [TestMethod] + [Fact] public void Overridden_By_Dictionary() { var result = PhpSerialization.Deserialize>( "O:8:\"stdClass\":2:{s:4:\"John\";d:3.14;s:4:\"Jane\";d:2.718;}", new PhpDeserializationOptions() { StdClass = StdClassOption.Dynamic } ); - Assert.AreEqual(3.14, result["John"]); - Assert.AreEqual(2.718, result["Jane"]); + Assert.Equal(3.14, result["John"]); + Assert.Equal(2.718, result["Jane"]); } } } \ No newline at end of file diff --git a/PhpSerializerNET.Test/Deserialize/Options/UseLists.cs b/PhpSerializerNET.Test/Deserialize/Options/UseLists.cs index 73ed6c6..7aa1968 100644 --- a/PhpSerializerNET.Test/Deserialize/Options/UseLists.cs +++ b/PhpSerializerNET.Test/Deserialize/Options/UseLists.cs @@ -5,12 +5,11 @@ This Source Code Form is subject to the terms of the Mozilla Public **/ using System.Collections.Generic; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; namespace PhpSerializerNET.Test.Deserialize.Options { - [TestClass] public class UseListsTest { - [TestMethod] + [Fact] public void Option_Never() { var test = PhpSerialization.Deserialize( "a:2:{i:0;s:1:\"a\";i:1;s:1:\"b\";}", @@ -20,13 +19,13 @@ public void Option_Never() { ); var dictionary = test as Dictionary; - Assert.IsNotNull(dictionary); - Assert.AreEqual(2, dictionary.Count); - Assert.AreEqual("a", dictionary[(long)0]); - Assert.AreEqual("b", dictionary[(long)1]); + Assert.NotNull(dictionary); + Assert.Equal(2, dictionary.Count); + Assert.Equal("a", dictionary[(long)0]); + Assert.Equal("b", dictionary[(long)1]); } - [TestMethod] + [Fact] public void Option_Default() { var result = PhpSerialization.Deserialize( "a:2:{i:0;s:1:\"a\";i:1;s:1:\"b\";}", @@ -36,13 +35,13 @@ public void Option_Default() { ); var list = result as List; - Assert.IsNotNull(list); - Assert.AreEqual(2, list.Count); - Assert.AreEqual("a", list[0]); - Assert.AreEqual("b", list[1]); + Assert.NotNull(list); + Assert.Equal(2, list.Count); + Assert.Equal("a", list[0]); + Assert.Equal("b", list[1]); } - [TestMethod] + [Fact] public void Option_Default_NonConsequetive() { // Same option, non-consecutive integer keys: var result = PhpSerialization.Deserialize( @@ -52,14 +51,15 @@ public void Option_Default_NonConsequetive() { } ); + Assert.Equal(typeof (Dictionary), result.GetType()); var dictionary = result as Dictionary; - Assert.IsNotNull(dictionary); - Assert.AreEqual(2, dictionary.Count); - Assert.AreEqual("a", dictionary[(long)2]); - Assert.AreEqual("b", dictionary[(long)4]); + Assert.NotNull(dictionary); + Assert.Equal(2, dictionary.Count); + Assert.Equal("a", dictionary[(long)2]); + Assert.Equal("b", dictionary[(long)4]); } - [TestMethod] + [Fact] public void Option_OnAllIntegerKeys() { var test = PhpSerialization.Deserialize( "a:2:{i:0;s:1:\"a\";i:1;s:1:\"b\";}", @@ -69,14 +69,14 @@ public void Option_OnAllIntegerKeys() { ); var list = test as List; - Assert.IsNotNull(list); - Assert.AreEqual(2, list.Count); - Assert.AreEqual("a", list[0]); - Assert.AreEqual("b", list[1]); + Assert.NotNull(list); + Assert.Equal(2, list.Count); + Assert.Equal("a", list[0]); + Assert.Equal("b", list[1]); } - [TestMethod] + [Fact] public void Option_OnAllIntegerKeys_NonConsequetive() { // Same option, non-consecutive integer keys: var result = PhpSerialization.Deserialize( @@ -87,10 +87,10 @@ public void Option_OnAllIntegerKeys_NonConsequetive() { ); var list = result as List; - Assert.IsNotNull(list); - Assert.AreEqual(2, list.Count); - Assert.AreEqual("a", list[0]); - Assert.AreEqual("b", list[1]); + Assert.NotNull(list); + Assert.Equal(2, list.Count); + Assert.Equal("a", list[0]); + Assert.Equal("b", list[1]); } } } \ No newline at end of file diff --git a/PhpSerializerNET.Test/Deserialize/PhpDateTimeDeserialization.cs b/PhpSerializerNET.Test/Deserialize/PhpDateTimeDeserialization.cs index 37348f1..b5ce99e 100644 --- a/PhpSerializerNET.Test/Deserialize/PhpDateTimeDeserialization.cs +++ b/PhpSerializerNET.Test/Deserialize/PhpDateTimeDeserialization.cs @@ -4,22 +4,21 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; -namespace PhpSerializerNET.Test.Deserialize { - [TestClass] - public class PhpDateTimeDeserializationTest { - [TestMethod] - public void DeserializesCorrectly() { - var result = PhpSerialization.Deserialize( - "O:8:\"DateTime\":3:{s:4:\"date\";s:26:\"2021-08-18 09:10:23.441055\";s:13:\"timezone_type\";i:3;s:8:\"timezone\";s:3:\"UTC\";}" - ); +namespace PhpSerializerNET.Test.Deserialize; - Assert.IsInstanceOfType(result, typeof(PhpDateTime)); - var date = result as PhpDateTime; - Assert.AreEqual("UTC", date.Timezone); - Assert.AreEqual("2021-08-18 09:10:23.441055", date.Date); - Assert.AreEqual("DateTime", date.GetClassName()); - } +public class PhpDateTimeDeserializationTest { + [Fact] + public void DeserializesCorrectly() { + var result = PhpSerialization.Deserialize( + "O:8:\"DateTime\":3:{s:4:\"date\";s:26:\"2021-08-18 09:10:23.441055\";s:13:\"timezone_type\";i:3;s:8:\"timezone\";s:3:\"UTC\";}" + ); + + Assert.IsType(result); + var date = result as PhpDateTime; + Assert.Equal("UTC", date.Timezone); + Assert.Equal("2021-08-18 09:10:23.441055", date.Date); + Assert.Equal("DateTime", date.GetClassName()); } } diff --git a/PhpSerializerNET.Test/Deserialize/StringDeserialization.cs b/PhpSerializerNET.Test/Deserialize/StringDeserialization.cs index 85c4d93..d57e80a 100644 --- a/PhpSerializerNET.Test/Deserialize/StringDeserialization.cs +++ b/PhpSerializerNET.Test/Deserialize/StringDeserialization.cs @@ -6,94 +6,65 @@ This Source Code Form is subject to the terms of the Mozilla Public **/ using System; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; using PhpSerializerNET.Test.DataTypes; -namespace PhpSerializerNET.Test.Deserialize { - [TestClass] - public class StringDeserializationTest { - [TestMethod] - public void SerializeHelloWorld() { - Assert.AreEqual( - "s:12:\"Hello World!\";", - PhpSerialization.Serialize("Hello World!") - ); - } +namespace PhpSerializerNET.Test.Deserialize; - [TestMethod] - public void DeserializeEmptyStringExplicit() { - - Assert.AreEqual( - "", - PhpSerialization.Deserialize("s:0:\"\";", new PhpDeserializationOptions { - EmptyStringToDefault = false - }) - ); - - Assert.AreEqual( - default, - PhpSerialization.Deserialize("s:0:\"\";", new PhpDeserializationOptions { - EmptyStringToDefault = true - }) - ); - } - - [TestMethod] - public void DeserializeHelloWorld() { - Assert.AreEqual( - "Hello World!", - PhpSerialization.Deserialize("s:12:\"Hello World!\";") - ); - } - - [TestMethod] - public void DeserializeEmptyString() { - Assert.AreEqual( - "", - PhpSerialization.Deserialize("s:0:\"\";") - ); - } - - [TestMethod] - public void DeserializeUmlauts() { - Assert.AreEqual( - "äöüßÄÖÜ", - PhpSerialization.Deserialize("s:14:\"äöüßÄÖÜ\";") - ); - } +public class StringDeserializationTest { + [Theory] + [InlineData("s:12:\"Hello World!\";", "Hello World!")] + [InlineData("s:0:\"\";", "")] + [InlineData("s:14:\"äöüßÄÖÜ\";", "äöüßÄÖÜ")] + [InlineData("s:4:\"👻\";", "👻")] + [InlineData("s:9:\"_\";s:1:\"_\";", "_\";s:1:\"_")] // // This is really how the PHP implementation behaves. + public void DeserializesCorrectly(string input, string expected) { + Assert.Equal( + expected, PhpSerialization.Deserialize(input) + ); + } - [TestMethod] - public void DeserializeEmoji() { - Assert.AreEqual( - "👻", - PhpSerialization.Deserialize("s:4:\"👻\";") - ); - } + [Theory] + [InlineData("Hello World!", "s:12:\"Hello World!\";")] + [InlineData("", "s:0:\"\";")] + [InlineData("äöüßÄÖÜ", "s:14:\"äöüßÄÖÜ\";")] + [InlineData("👻", "s:4:\"👻\";")] + [InlineData("_\";s:1:\"_", "s:9:\"_\";s:1:\"_\";")] + public void SerializesCorrectly(string input, string expected) { + Assert.Equal( + expected, PhpSerialization.Serialize(input) + ); + } - [TestMethod] - public void DeserializeHalfnesting() { - // This is really how the PHP implementation behaves. - Assert.AreEqual( - "_\";s:1:\"_", - PhpSerialization.Deserialize("s:9:\"_\";s:1:\"_\";") - ); - } + [Fact] + public void DeserializeEmptyStringExplicit() { + Assert.Equal( + "", + PhpSerialization.Deserialize("s:0:\"\";", new PhpDeserializationOptions { + EmptyStringToDefault = false + }) + ); + Assert.Null( + PhpSerialization.Deserialize("s:0:\"\";", new PhpDeserializationOptions { + EmptyStringToDefault = true + }) + ); + } - [TestMethod] - public void ExplicitToGuid() { - Guid guid = PhpSerialization.Deserialize("s:36:\"82e2ebf0-43e6-4c10-82cf-57d60383a6be\";"); - Assert.AreEqual("82e2ebf0-43e6-4c10-82cf-57d60383a6be", guid.ToString()); - } + [Fact] + public void ExplicitToGuid() { + Guid guid = PhpSerialization.Deserialize("s:36:\"82e2ebf0-43e6-4c10-82cf-57d60383a6be\";"); + Assert.Equal("82e2ebf0-43e6-4c10-82cf-57d60383a6be", guid.ToString()); + } - [TestMethod] - public void DeserializesStringToGuidProperty() { - var result = PhpSerialization.Deserialize( - "a:1:{s:4:\"Guid\";s:36:\"82e2ebf0-43e6-4c10-82cf-57d60383a6be\";}" - ); - Assert.AreEqual( - new Guid("82e2ebf0-43e6-4c10-82cf-57d60383a6be"), - result.Guid - ); - } + [Fact] + public void DeserializesStringToGuidProperty() { + var result = PhpSerialization.Deserialize( + "a:1:{s:4:\"Guid\";s:36:\"82e2ebf0-43e6-4c10-82cf-57d60383a6be\";}" + ); + Assert.Equal( + new Guid("82e2ebf0-43e6-4c10-82cf-57d60383a6be"), + result.Guid + ); } } \ No newline at end of file diff --git a/PhpSerializerNET.Test/Deserialize/Validation/TestArrayValidation.cs b/PhpSerializerNET.Test/Deserialize/Validation/TestArrayValidation.cs index 51c7bee..cf0ddf4 100644 --- a/PhpSerializerNET.Test/Deserialize/Validation/TestArrayValidation.cs +++ b/PhpSerializerNET.Test/Deserialize/Validation/TestArrayValidation.cs @@ -4,60 +4,21 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace PhpSerializerNET.Test.Deserialize.Validation { - [TestClass] - public class TestArrayValidation { - - [TestMethod] - public void ThrowsOnMalformedArray() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("a")); - Assert.AreEqual("Unexpected end of input. Expected ':' at index 1, but input ends at index 0", ex.Message); - - } - - - [TestMethod] - public void ThrowsOnInvalidLength() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("a:-1:{};")); - Assert.AreEqual("Array at position 2 has illegal, missing or malformed length.", ex.Message); - } - - - [TestMethod] - public void ThrowsOnMissingBracket() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("a:100:};")); - Assert.AreEqual("Unexpected token at index 6. Expected '{' but found '}' instead.", ex.Message); - - - } - - [TestMethod] - public void ThrowsOnMissingColon() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("a:10000 ")); - Assert.AreEqual("Array at position 7 has illegal, missing or malformed length.", ex.Message); - - } - - [TestMethod] - public void ThrowsOnAbruptEOF() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("a:10000:")); - Assert.AreEqual("Unexpected end of input. Expected '{' at index 8, but input ends at index 7", ex.Message); - - - ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("a:1000000")); - Assert.AreEqual("Unexpected token at index 8. Expected ':' but found '0' instead.", ex.Message); - - } - - [TestMethod] - public void ThrowsOnFalseLength() { - var exception = Assert.ThrowsException( - () => PhpSerialization.Deserialize("a:2:{i:0;i:0;i:1;i:1;i:2;i:2;}") - ); - - Assert.AreEqual("Array at position 0 should be of length 2, but actual length is 3 or more.", exception.Message); - } +using Xunit; + +namespace PhpSerializerNET.Test.Deserialize.Validation; + +public class TestArrayValidation { + [Theory] + [InlineData("a", "Unexpected end of input. Expected ':' at index 1, but input ends at index 0")] + [InlineData("a:-1:{};", "Array at position 2 has illegal, missing or malformed length.")] + [InlineData("a:100:};", "Unexpected token at index 6. Expected '{' but found '}' instead.")] + [InlineData("a:10000 ", "Array at position 7 has illegal, missing or malformed length.")] + [InlineData("a:10000:", "Unexpected end of input. Expected '{' at index 8, but input ends at index 7")] + [InlineData("a:1000000", "Unexpected token at index 8. Expected ':' but found '0' instead.")] + [InlineData("a:2:{i:0;i:0;i:1;i:1;i:2;i:2;}", "Array at position 0 should be of length 2, but actual length is 3 or more.")] + public void ThrowsOnMalformedArray(string input, string exceptionMessage) { + var ex = Assert.Throws(() => PhpSerialization.Deserialize(input)); + Assert.Equal(exceptionMessage, ex.Message); } -} \ No newline at end of file +} diff --git a/PhpSerializerNET.Test/Deserialize/Validation/TestBoolValidation.cs b/PhpSerializerNET.Test/Deserialize/Validation/TestBoolValidation.cs index 20f6df8..d64d1fe 100644 --- a/PhpSerializerNET.Test/Deserialize/Validation/TestBoolValidation.cs +++ b/PhpSerializerNET.Test/Deserialize/Validation/TestBoolValidation.cs @@ -1,42 +1,21 @@ - /** This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace PhpSerializerNET.Test.Deserialize.Validation { - [TestClass] - public class TestBoolValidation { - [TestMethod] - public void ThrowsOnTruncatedInput() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("b")); - Assert.AreEqual( - "Unexpected end of input. Expected ':' at index 1, but input ends at index 0", - ex.Message - ); +using Xunit; - ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("b:1")); - Assert.AreEqual( - "Unexpected end of input. Expected ';' at index 3, but input ends at index 2", - ex.Message - ); - ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("b:")); - Assert.AreEqual( - "Unexpected end of input. Expected '0' or '1' at index 2, but input ends at index 1", - ex.Message - ); - } +namespace PhpSerializerNET.Test.Deserialize.Validation; - [TestMethod] - public void ThrowsOnInvalidValue() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("b:2;")); - Assert.AreEqual( - "Unexpected token in boolean at index 2. Expected either '1' or '0', but found '2' instead.", - ex.Message - ); - } +public class TestBoolValidation { + [Theory] + [InlineData("b", "Unexpected end of input. Expected ':' at index 1, but input ends at index 0")] + [InlineData("b:1", "Unexpected end of input. Expected ';' at index 3, but input ends at index 2")] + [InlineData("b:", "Unexpected end of input. Expected '0' or '1' at index 2, but input ends at index 1")] + [InlineData("b:2;", "Unexpected token in boolean at index 2. Expected either '1' or '0', but found '2' instead.")] + public void ThrowsOnMalformeBool(string input, string exceptionMessage) { + var ex = Assert.Throws(() => PhpSerialization.Deserialize(input)); + Assert.Equal(exceptionMessage, ex.Message); } -} \ No newline at end of file +} diff --git a/PhpSerializerNET.Test/Deserialize/Validation/TestDoubleValidation.cs b/PhpSerializerNET.Test/Deserialize/Validation/TestDoubleValidation.cs index 22129b7..a1a0660 100644 --- a/PhpSerializerNET.Test/Deserialize/Validation/TestDoubleValidation.cs +++ b/PhpSerializerNET.Test/Deserialize/Validation/TestDoubleValidation.cs @@ -4,39 +4,18 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace PhpSerializerNET.Test.Deserialize.Validation { - [TestClass] - public class TestDoubleValidation { - [TestMethod] - public void ThrowsOnTruncatedInput() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("d")); - Assert.AreEqual("Unexpected end of input. Expected ':' at index 1, but input ends at index 0", ex.Message); - - } - - [TestMethod] - public void ThrowsOnMissingColon() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("d ")); - Assert.AreEqual("Unexpected token at index 1. Expected ':' but found ' ' instead.", ex.Message); - - } - - [TestMethod] - public void ThrowsOnMissingSemicolon() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("d:111111")); - Assert.AreEqual("Unexpected end of input. Expected ':' at index 7, but input ends at index 7", ex.Message); - } - - - [TestMethod] - public void ThrowsOnInvalidCharacter() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("d:bgg5;")); - Assert.AreEqual( - "Unexpected token at index 2. 'b' is not a valid part of a floating point number.", - ex.Message - ); - } +using Xunit; + +namespace PhpSerializerNET.Test.Deserialize.Validation; +public class TestDoubleValidation { + [Theory] + [InlineData("d", "Unexpected end of input. Expected ':' at index 1, but input ends at index 0")] + [InlineData("b ", "Unexpected token at index 1. Expected ':' but found ' ' instead.")] + [InlineData("d:111111", "Unexpected end of input. Expected ':' at index 7, but input ends at index 7")] + [InlineData("d:bgg5;", "Unexpected token at index 2. 'b' is not a valid part of a floating point number.")] + [InlineData("d:;", "Unexpected token at index 2: Expected floating point number, but found ';' instead.")] + public void ThrowsOnMalformedDouble(string input, string exceptionMessage) { + var ex = Assert.Throws(() => PhpSerialization.Deserialize(input)); + Assert.Equal(exceptionMessage, ex.Message); } -} \ No newline at end of file +} diff --git a/PhpSerializerNET.Test/Deserialize/Validation/TestIntegerValidation.cs b/PhpSerializerNET.Test/Deserialize/Validation/TestIntegerValidation.cs index 658ecf0..e574c4c 100644 --- a/PhpSerializerNET.Test/Deserialize/Validation/TestIntegerValidation.cs +++ b/PhpSerializerNET.Test/Deserialize/Validation/TestIntegerValidation.cs @@ -1,46 +1,27 @@ - /** This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ +using Xunit; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace PhpSerializerNET.Test.Deserialize.Validation { - [TestClass] - public class TestIntegerValidation { - [TestMethod] - public void ThrowsOnTruncatedInput() { - // var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("i")); - // Assert.AreEqual( - // "Unexpected end of input. Expected ':' at index 1, but input ends at index 0", - // ex.Message - // ); - - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("i:1")); - Assert.AreEqual( - "Unexpected end of input. Expected ':' at index 2, but input ends at index 2", - ex.Message - ); - } +namespace PhpSerializerNET.Test.Deserialize.Validation; - [TestMethod] - public void ThrowsOnInvalidValue() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("i:12345b;")); - Assert.AreEqual( - "Unexpected token at index 7. 'b' is not a valid part of a number.", - ex.Message - ); - } - - [TestMethod] - public void ThrowOnMissingSemicolon() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("i:12345")); - Assert.AreEqual( - "Unexpected end of input. Expected ':' at index 6, but input ends at index 6", - ex.Message - ); - } +public class TestIntegerValidation { + [Theory] + [InlineData("i:;", "Unexpected token at index 2: Expected number, but found ';' instead.")] + [InlineData("i:INF;", "Unexpected token at index 2. 'I' is not a valid part of a number.")] + [InlineData("i:NaN;", "Unexpected token at index 2. 'N' is not a valid part of a number.")] + [InlineData("i:12345b:;", "Unexpected token at index 7. 'b' is not a valid part of a number.")] + [InlineData("i:12345.;", "Unexpected token at index 7. '.' is not a valid part of a number.")] + public void ThrowsOnMalformedInteger(string input, string exceptionMessage) { + var exception = Assert.Throws(() => { + PhpSerialization.Deserialize(input); + }); + Assert.Equal( + exceptionMessage, + exception.Message + ); } -} \ No newline at end of file + +} diff --git a/PhpSerializerNET.Test/Deserialize/Validation/TestNullValidation.cs b/PhpSerializerNET.Test/Deserialize/Validation/TestNullValidation.cs index 2792c41..164ff43 100644 --- a/PhpSerializerNET.Test/Deserialize/Validation/TestNullValidation.cs +++ b/PhpSerializerNET.Test/Deserialize/Validation/TestNullValidation.cs @@ -5,19 +5,16 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; -namespace PhpSerializerNET.Test.Deserialize.Validation { - [TestClass] - public class TestNullValidation { - [TestMethod] - public void ThrowsOnTruncatedInput() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("N")); - Assert.AreEqual("Unexpected end of input. Expected ';' at index 1, but input ends at index 0", ex.Message); - - ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("N?")); - Assert.AreEqual("Unexpected token at index 1. Expected ';' but found '?' instead.", ex.Message); - } +namespace PhpSerializerNET.Test.Deserialize.Validation; +public class TestNullValidation { + [Theory] + [InlineData("N", "Unexpected end of input. Expected ';' at index 1, but input ends at index 0")] + [InlineData("N?", "Unexpected token at index 1. Expected ';' but found '?' instead.")] + public void ThrowsOnTruncatedInput(string input, string exceptionMessage) { + var ex = Assert.Throws(() => PhpSerialization.Deserialize(input)); + Assert.Equal(exceptionMessage, ex.Message); } -} \ No newline at end of file +} diff --git a/PhpSerializerNET.Test/Deserialize/Validation/TestObjectValidation.cs b/PhpSerializerNET.Test/Deserialize/Validation/TestObjectValidation.cs index d2d8c05..1be27e3 100644 --- a/PhpSerializerNET.Test/Deserialize/Validation/TestObjectValidation.cs +++ b/PhpSerializerNET.Test/Deserialize/Validation/TestObjectValidation.cs @@ -4,104 +4,46 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace PhpSerializerNET.Test.Deserialize.Validation { - [TestClass] - public class TestObjectValidation { - [TestMethod] - public void ErrorOnInvalidNameLength() { - var ex = Assert.ThrowsException( - () => PhpSerialization.Deserialize( - "O:-1:\"stdClass\":1:{s:3:\"Foo\";N;}" - ) - ); - - Assert.AreEqual( - "Object at position 2 has illegal, missing or malformed length.", - ex.Message - ); - - ex = Assert.ThrowsException( - () => PhpSerialization.Deserialize( - "O:200:\"stdClass\":1:{s:3:\"Foo\";N;}" - ) - ); - - Assert.AreEqual( - "Illegal length of 200. The string at position 7 points to out of bounds index 207.", - ex.Message - ); - } - - [TestMethod] - public void ErrorOnInvalidName() { - var ex = Assert.ThrowsException( - () => PhpSerialization.Deserialize( - "O:8:\"stdClass:1:{s:3:\"Foo\";N;}" - ) - ); - - Assert.AreEqual( - "Unexpected token at index 13. Expected '\"' but found ':' instead.", - ex.Message - ); - - ex = Assert.ThrowsException( - () => PhpSerialization.Deserialize( - "O:2:stdClass\":1:{s:3:\"Foo\";N;}" - ) - ); - - Assert.AreEqual( - "Unexpected token at index 4. Expected '\"' but found 's' instead.", - ex.Message - ); - } - - [TestMethod] - public void ThrowsOnFalseLength() { - var exception = Assert.ThrowsException( - () => PhpSerialization.Deserialize("O:1:\"a\":2:{i:0;i:0;i:1;i:1;i:2;i:2;}") - ); - - Assert.AreEqual("Object at position 0 should have 2 properties, but actually has 3 or more properties.", exception.Message); - } - - [TestMethod] - public void ErrorOnInvalidSyntax() { - var ex = Assert.ThrowsException( - () => PhpSerialization.Deserialize( - "O:8:\"stdClass\"1:{s:3:\"Foo\";N;}" - ) - ); - - Assert.AreEqual( - "Unexpected token at index 14. Expected ':' but found '1' instead.", - ex.Message - ); - - ex = Assert.ThrowsException( - () => PhpSerialization.Deserialize( - "O:8:\"stdClass\":1{s:3:\"Foo\";N;}" - ) - ); - - Assert.AreEqual( - "Object at position 16 has illegal, missing or malformed length.", - ex.Message - ); - - ex = Assert.ThrowsException( - () => PhpSerialization.Deserialize( - "O:8:\"stdClass\":1:s:3:\"Foo\";N;}" - ) - ); - - Assert.AreEqual( - "Unexpected token at index 17. Expected '{' but found 's' instead.", - ex.Message - ); - } +using Xunit; + +namespace PhpSerializerNET.Test.Deserialize.Validation; + +public class TestObjectValidation { + [Theory] + [InlineData( + "O:-1:\"stdClass\":1:{s:3:\"Foo\";N;}", + "Object at position 2 has illegal, missing or malformed length." + )] + [InlineData( + "O:200:\"stdClass\":1:{s:3:\"Foo\";N;}", + "Illegal length of 200. The string at position 7 points to out of bounds index 207." + )] + [InlineData( + "O:8:\"stdClass:1:{s:3:\"Foo\";N;}", + "Unexpected token at index 13. Expected '\"' but found ':' instead." + )] + [InlineData( + "O:2:stdClass\":1:{s:3:\"Foo\";N;}", + "Unexpected token at index 4. Expected '\"' but found 's' instead." + )] + [InlineData( + "O:1:\"a\":2:{i:0;i:0;i:1;i:1;i:2;i:2;}", + "Object at position 0 should have 2 properties, but actually has 3 or more properties." + )] + [InlineData( + "O:8:\"stdClass\"1:{s:3:\"Foo\";N;}", + "Unexpected token at index 14. Expected ':' but found '1' instead." + )] + [InlineData( + "O:8:\"stdClass\":1{s:3:\"Foo\";N;}", + "Object at position 16 has illegal, missing or malformed length." + )] + [InlineData( + "O:8:\"stdClass\":1:s:3:\"Foo\";N;}", + "Unexpected token at index 17. Expected '{' but found 's' instead." + )] + public void ThrowsOnMalformedObject(string input, string exceptionMessage) { + var ex = Assert.Throws(() => PhpSerialization.Deserialize(input)); + Assert.Equal(exceptionMessage, ex.Message); } } \ No newline at end of file diff --git a/PhpSerializerNET.Test/Deserialize/Validation/TestOtherErrors.cs b/PhpSerializerNET.Test/Deserialize/Validation/TestOtherErrors.cs index 639fee6..3e86ecc 100644 --- a/PhpSerializerNET.Test/Deserialize/Validation/TestOtherErrors.cs +++ b/PhpSerializerNET.Test/Deserialize/Validation/TestOtherErrors.cs @@ -5,73 +5,72 @@ This Source Code Form is subject to the terms of the Mozilla Public **/ using System; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; using PhpSerializerNET.Test.DataTypes; -namespace PhpSerializerNET.Test.Deserialize.Validation { - [TestClass] - public class TestOtherErrors { - [TestMethod] - public void ThrowsOnUnexpectedToken() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("_")); - Assert.AreEqual("Unexpected token '_' at position 0.", ex.Message); - - ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("i:42;_")); - Assert.AreEqual("Unexpected token '_' at position 5.", ex.Message); - - ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("_i:42;")); - Assert.AreEqual("Unexpected token '_' at position 0.", ex.Message); - } - - [TestMethod] - public void ErrorOnTuple() { - var ex = Assert.ThrowsException( - () => PhpSerialization.Deserialize("s:7:\"AString\";s:7:\"AString\";") - ); - - Assert.AreEqual("Unexpected token 's' at position 14.", ex.Message); - } - - [TestMethod] - public void ErrorOnEmptyInput() { - var ex = Assert.ThrowsException( - () => PhpSerialization.Deserialize("") - ); - - const string expected = "PhpSerialization.Deserialize(): Parameter 'input' must not be null or empty. (Parameter 'input')"; - Assert.AreEqual(expected, ex.Message); - - ex = Assert.ThrowsException( - () => PhpSerialization.Deserialize("") - ); - - Assert.AreEqual(expected, ex.Message); - - ex = Assert.ThrowsException( - () => PhpSerialization.Deserialize("", typeof(string)) - ); - - Assert.AreEqual(expected, ex.Message); - } - - - [TestMethod] - public void ThrowOnIllegalKeyType() { - var ex = Assert.ThrowsException( - () => PhpSerialization.Deserialize("O:8:\"stdClass\":1:{b:1;s:4:\"true\";}") - ); - Assert.AreEqual( - "Error encountered deserizalizing an object of type 'PhpSerializerNET.Test.DataTypes.MyPhpObject': " + - "The key '1' (from the token at position 18) has an unsupported type of 'Boolean'.", - ex.Message - ); - } - - [TestMethod] - public void ThrowOnIntegerKeyPhpObject() { - var ex = Assert.ThrowsException( - () => PhpSerialization.Deserialize("O:8:\"stdClass\":1:{i:0;s:4:\"true\";}") - ); - } +namespace PhpSerializerNET.Test.Deserialize.Validation; + +public class TestOtherErrors { + [Fact] + public void ThrowsOnUnexpectedToken() { + var ex = Assert.Throws(() => PhpSerialization.Deserialize("_")); + Assert.Equal("Unexpected token '_' at position 0.", ex.Message); + + ex = Assert.Throws(() => PhpSerialization.Deserialize("i:42;_")); + Assert.Equal("Unexpected token '_' at position 5.", ex.Message); + + ex = Assert.Throws(() => PhpSerialization.Deserialize("_i:42;")); + Assert.Equal("Unexpected token '_' at position 0.", ex.Message); + } + + [Fact] + public void ErrorOnTuple() { + var ex = Assert.Throws( + () => PhpSerialization.Deserialize("s:7:\"AString\";s:7:\"AString\";") + ); + + Assert.Equal("Unexpected token 's' at position 14.", ex.Message); + } + + [Fact] + public void ErrorOnEmptyInput() { + var ex = Assert.Throws( + () => PhpSerialization.Deserialize("") + ); + + const string expected = "PhpSerialization.Deserialize(): Parameter 'input' must not be null or empty. (Parameter 'input')"; + Assert.Equal(expected, ex.Message); + + ex = Assert.Throws( + () => PhpSerialization.Deserialize("") + ); + + Assert.Equal(expected, ex.Message); + + ex = Assert.Throws( + () => PhpSerialization.Deserialize("", typeof(string)) + ); + + Assert.Equal(expected, ex.Message); + } + + + [Fact] + public void ThrowOnIllegalKeyType() { + var ex = Assert.Throws( + () => PhpSerialization.Deserialize("O:8:\"stdClass\":1:{b:1;s:4:\"true\";}") + ); + Assert.Equal( + "Error encountered deserizalizing an object of type 'PhpSerializerNET.Test.DataTypes.MyPhpObject': " + + "The key '1' (from the token at position 18) has an unsupported type of 'Boolean'.", + ex.Message + ); + } + + [Fact] + public void ThrowOnIntegerKeyPhpObject() { + var ex = Assert.Throws( + () => PhpSerialization.Deserialize("O:8:\"stdClass\":1:{i:0;s:4:\"true\";}") + ); } } diff --git a/PhpSerializerNET.Test/Deserialize/Validation/TestStringValidation.cs b/PhpSerializerNET.Test/Deserialize/Validation/TestStringValidation.cs index e3c0e51..b329b18 100644 --- a/PhpSerializerNET.Test/Deserialize/Validation/TestStringValidation.cs +++ b/PhpSerializerNET.Test/Deserialize/Validation/TestStringValidation.cs @@ -4,57 +4,21 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; -namespace PhpSerializerNET.Test.Deserialize.Validation { - [TestClass] - public class TestStringValidation { - [TestMethod] - public void ThrowsOnTruncatedInput() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("s")); - Assert.AreEqual("Unexpected end of input. Expected ':' at index 1, but input ends at index 0", ex.Message); - } +namespace PhpSerializerNET.Test.Deserialize.Validation; - [TestMethod] - public void ThrowsOnMissingStartQuote() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("s:3:abc\";")); - Assert.AreEqual( - "Unexpected token at index 4. Expected '\"' but found 'a' instead.", - ex.Message - ); - } - - [TestMethod] - public void ThrowsOnMissingEndQuote() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("s:3:\"abc;")); - Assert.AreEqual( - "Unexpected token at index 8. Expected '\"' but found ';' instead.", - ex.Message - ); - } - - [TestMethod] - public void ThrowsOnInvalidLength() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("s:3\"abc\";")); - Assert.AreEqual( - "String at position 3 has illegal, missing or malformed length.", - ex.Message - ); - } - - [TestMethod] - public void ThrowsOnOutOfBoundsLength() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("s:10:\"abc\";")); - Assert.AreEqual( - "Illegal length of 10. The string at position 6 points to out of bounds index 16.", - ex.Message - ); - } - - [TestMethod] - public void ThrowsOnMissingSemicolon() { - var ex = Assert.ThrowsException(() => PhpSerialization.Deserialize("s:3:\"abc\"")); - Assert.AreEqual("Unexpected end of input. Expected ';' at index 9, but input ends at index 8", ex.Message); - } +public class TestStringValidation { + [Theory] + [InlineData("s", "Unexpected end of input. Expected ':' at index 1, but input ends at index 0")] + [InlineData("s:3:abc\";", "Unexpected token at index 4. Expected '\"' but found 'a' instead.")] + [InlineData("s:3:\"abc;", "Unexpected token at index 8. Expected '\"' but found ';' instead.")] + [InlineData("s:3\"abc\";", "String at position 3 has illegal, missing or malformed length.")] + [InlineData("s:_:\"abc\";", "String at position 2 has illegal, missing or malformed length.")] + [InlineData("s:10:\"abc\";", "Illegal length of 10. The string at position 6 points to out of bounds index 16.")] + [InlineData("s:3:\"abc\"", "Unexpected end of input. Expected ';' at index 9, but input ends at index 8")] + public void ThrowsOnMalformedString(string input, string exceptionMessage) { + var ex = Assert.Throws(() => PhpSerialization.Deserialize(input)); + Assert.Equal(exceptionMessage, ex.Message); } } \ No newline at end of file diff --git a/PhpSerializerNET.Test/Other/DeserializeWithRuntimeType.cs b/PhpSerializerNET.Test/Other/DeserializeWithRuntimeType.cs index 9b921dc..a1baf85 100644 --- a/PhpSerializerNET.Test/Other/DeserializeWithRuntimeType.cs +++ b/PhpSerializerNET.Test/Other/DeserializeWithRuntimeType.cs @@ -4,47 +4,46 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; using PhpSerializerNET.Test.DataTypes; -namespace PhpSerializerNET.Test { - [TestClass] - public partial class DeserializeWithRuntimeTypeTest { - - [TestMethod] - public void DeserializeObjectWithRuntimeType() { - var expectedType = typeof(NamedClass); - var result = PhpSerialization.Deserialize("O:8:\"stdClass\":2:{s:3:\"Foo\";d:3.14;s:3:\"Bar\";d:2.718;}", expectedType); - - Assert.IsNotNull(result); - Assert.IsInstanceOfType(result, expectedType); - - Assert.AreEqual( - 3.14, - ((NamedClass)result).Foo - ); - Assert.AreEqual( - 2.718, - ((NamedClass)result).Bar - ); - } - - [TestMethod] - public void DeserializeArrayWithRuntimeType() { - var expectedType = typeof(NamedClass); - var result = PhpSerialization.Deserialize("a:2:{s:3:\"Foo\";d:3.14;s:3:\"Bar\";d:2.718;}", expectedType); - - Assert.IsNotNull(result); - Assert.IsInstanceOfType(result, expectedType); - - Assert.AreEqual( - 3.14, - ((NamedClass)result).Foo - ); - Assert.AreEqual( - 2.718, - ((NamedClass)result).Bar - ); - } +namespace PhpSerializerNET.Test; + +public partial class DeserializeWithRuntimeTypeTest { + + [Fact] + public void DeserializeObjectWithRuntimeType() { + var expectedType = typeof(NamedClass); + var result = PhpSerialization.Deserialize("O:8:\"stdClass\":2:{s:3:\"Foo\";d:3.14;s:3:\"Bar\";d:2.718;}", expectedType); + + Assert.NotNull(result); + Assert.IsType(expectedType, result); + + Assert.Equal( + 3.14, + ((NamedClass)result).Foo + ); + Assert.Equal( + 2.718, + ((NamedClass)result).Bar + ); + } + + [Fact] + public void DeserializeArrayWithRuntimeType() { + var expectedType = typeof(NamedClass); + var result = PhpSerialization.Deserialize("a:2:{s:3:\"Foo\";d:3.14;s:3:\"Bar\";d:2.718;}", expectedType); + + Assert.NotNull(result); + Assert.IsType(expectedType, result); + + Assert.Equal( + 3.14, + ((NamedClass)result).Foo + ); + Assert.Equal( + 2.718, + ((NamedClass)result).Bar + ); } } diff --git a/PhpSerializerNET.Test/Other/PhpDateTimeTest.cs b/PhpSerializerNET.Test/Other/PhpDateTimeTest.cs index 2399c75..3a9228f 100644 --- a/PhpSerializerNET.Test/Other/PhpDateTimeTest.cs +++ b/PhpSerializerNET.Test/Other/PhpDateTimeTest.cs @@ -5,20 +5,19 @@ This Source Code Form is subject to the terms of the Mozilla Public **/ using System; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; -namespace PhpSerializerNET.Test.Other { - [TestClass] - public class PhpDateTimeTest { - [TestMethod] - public void ThrowsOnSetClassName() { - var testObject = new PhpDateTime(); +namespace PhpSerializerNET.Test.Other; - var ex = Assert.ThrowsException(() => { - testObject.SetClassName("stdClass"); - }); - Assert.AreEqual("Cannot set name on object of type PhpDateTime name is of constant DateTime", ex.Message); - } +public class PhpDateTimeTest { + [Fact] + public void ThrowsOnSetClassName() { + var testObject = new PhpDateTime(); + var ex = Assert.Throws(() => { + testObject.SetClassName("stdClass"); + }); + Assert.Equal("Cannot set name on object of type PhpDateTime name is of constant DateTime", ex.Message); } + } diff --git a/PhpSerializerNET.Test/Other/PhpDynamicObjectTest.cs b/PhpSerializerNET.Test/Other/PhpDynamicObjectTest.cs index c6d4201..f1385c7 100644 --- a/PhpSerializerNET.Test/Other/PhpDynamicObjectTest.cs +++ b/PhpSerializerNET.Test/Other/PhpDynamicObjectTest.cs @@ -4,25 +4,24 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; -namespace PhpSerializerNET.Test.Other { - [TestClass] - public class PhpDynamicObjectTest { - [TestMethod] - public void CanReadAndWriteProps() { - dynamic testObject = new PhpDynamicObject(); +namespace PhpSerializerNET.Test.Other; - testObject.foo = "Foo"; - Assert.AreEqual("Foo", testObject.foo); - } +public class PhpDynamicObjectTest { + [Fact] + public void CanReadAndWriteProps() { + dynamic testObject = new PhpDynamicObject(); - [TestMethod] - public void GetAndSetClassname() { - dynamic testObject = new PhpDynamicObject(); + testObject.foo = "Foo"; + Assert.Equal("Foo", testObject.foo); + } + + [Fact] + public void GetAndSetClassname() { + dynamic testObject = new PhpDynamicObject(); - testObject.SetClassName("MyClass"); - Assert.AreEqual("MyClass", testObject.GetClassName()); - } + testObject.SetClassName("MyClass"); + Assert.Equal("MyClass", testObject.GetClassName()); } } diff --git a/PhpSerializerNET.Test/PhpSerializerNET.Test.csproj b/PhpSerializerNET.Test/PhpSerializerNET.Test.csproj index ea2fd58..7359fc0 100644 --- a/PhpSerializerNET.Test/PhpSerializerNET.Test.csproj +++ b/PhpSerializerNET.Test/PhpSerializerNET.Test.csproj @@ -4,15 +4,18 @@ false - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + - + - + \ No newline at end of file diff --git a/PhpSerializerNET.Test/Serialize/ArraySerialization.cs b/PhpSerializerNET.Test/Serialize/ArraySerialization.cs index 7908610..39bb442 100644 --- a/PhpSerializerNET.Test/Serialize/ArraySerialization.cs +++ b/PhpSerializerNET.Test/Serialize/ArraySerialization.cs @@ -4,24 +4,23 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using System.Collections.Generic; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; using PhpSerializerNET.Test.DataTypes; namespace PhpSerializerNET.Test.Serialize { - [TestClass] public class ArraySerialization { - [TestMethod] + [Fact] + public void StringArraySerializaton() { - string[] data = new string[3] { "a", "b", "c" }; + string[] data = ["a", "b", "c"]; - Assert.AreEqual( + Assert.Equal( "a:3:{i:0;s:1:\"a\";i:1;s:1:\"b\";i:2;s:1:\"c\";}", PhpSerialization.Serialize(data) ); } - [TestMethod] + [Fact] public void ObjectIntoMixedKeyArray() { var data = new MixedKeysObject() { Foo = "Foo", @@ -30,10 +29,10 @@ public void ObjectIntoMixedKeyArray() { Dummy = "B", }; - Assert.AreEqual( + Assert.Equal( "a:4:{i:0;s:3:\"Foo\";i:1;s:3:\"Bar\";s:1:\"a\";s:1:\"A\";s:1:\"b\";s:1:\"B\";}", PhpSerialization.Serialize(data) ); } } -} \ No newline at end of file +} diff --git a/PhpSerializerNET.Test/Serialize/BooleanSerialization.cs b/PhpSerializerNET.Test/Serialize/BooleanSerialization.cs index bfa4c65..09e8b7b 100644 --- a/PhpSerializerNET.Test/Serialize/BooleanSerialization.cs +++ b/PhpSerializerNET.Test/Serialize/BooleanSerialization.cs @@ -5,25 +5,15 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; -namespace PhpSerializerNET.Test.Serialize { - [TestClass] - public class BooleanSerializationTest { - [TestMethod] - public void SerializesTrue() { - Assert.AreEqual( - "b:1;", - PhpSerialization.Serialize(true) - ); - } - - [TestMethod] - public void SerializesFalse() { - Assert.AreEqual( - "b:0;", - PhpSerialization.Serialize(false) - ); - } +namespace PhpSerializerNET.Test.Serialize; + +public class BooleanSerializationTest { + [Theory] + [InlineData(true, "b:1;")] + [InlineData(false, "b:0;")] + public void Serializes(bool input, string output) { + Assert.Equal(output, PhpSerialization.Serialize(input)); } -} \ No newline at end of file +} diff --git a/PhpSerializerNET.Test/Serialize/CircularReferences.cs b/PhpSerializerNET.Test/Serialize/CircularReferences.cs index 43b7028..344c087 100644 --- a/PhpSerializerNET.Test/Serialize/CircularReferences.cs +++ b/PhpSerializerNET.Test/Serialize/CircularReferences.cs @@ -6,61 +6,60 @@ This Source Code Form is subject to the terms of the Mozilla Public using System; using System.Collections.Generic; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; -namespace PhpSerializerNET.Test.Serialize { - [TestClass] - public class CircularReferencesTest { - private class CircularClass { - public string Foo { get; set; } - public CircularClass Bar { get; set; } - } +namespace PhpSerializerNET.Test.Serialize; - [TestMethod] - public void SerializeCircularObject() { - var testObject = new CircularClass() { - Foo = "First" - }; - testObject.Bar = new CircularClass() { - Foo = "Second", - Bar = testObject - }; +public class CircularReferencesTest { + private class CircularClass { + public string Foo { get; set; } + public CircularClass Bar { get; set; } + } + + [Fact] + public void SerializeCircularObject() { + var testObject = new CircularClass() { + Foo = "First" + }; + testObject.Bar = new CircularClass() { + Foo = "Second", + Bar = testObject + }; - Assert.AreEqual( - "a:2:{s:3:\"Foo\";s:5:\"First\";s:3:\"Bar\";a:2:{s:3:\"Foo\";s:6:\"Second\";s:3:\"Bar\";N;}}", - PhpSerialization.Serialize(testObject) - ); - } + Assert.Equal( + "a:2:{s:3:\"Foo\";s:5:\"First\";s:3:\"Bar\";a:2:{s:3:\"Foo\";s:6:\"Second\";s:3:\"Bar\";N;}}", + PhpSerialization.Serialize(testObject) + ); + } - [TestMethod] - public void ThrowOnCircularReferencesOption() { - var testObject = new CircularClass() { - Foo = "First" - }; - testObject.Bar = new CircularClass() { - Foo = "Second", - Bar = testObject - }; + [Fact] + public void ThrowOnCircularReferencesOption() { + var testObject = new CircularClass() { + Foo = "First" + }; + testObject.Bar = new CircularClass() { + Foo = "Second", + Bar = testObject + }; - var ex = Assert.ThrowsException( - () => PhpSerialization.Serialize(testObject, new PhpSerializiationOptions(){ ThrowOnCircularReferences = true}) - ); - Assert.AreEqual( - "Input object has a circular reference.", - ex.Message - ); - } + var ex = Assert.Throws( + () => PhpSerialization.Serialize(testObject, new PhpSerializiationOptions() { ThrowOnCircularReferences = true }) + ); + Assert.Equal( + "Input object has a circular reference.", + ex.Message + ); + } - [TestMethod] - public void SerializeCircularList() { - List listA = new() { "A", "B" }; - List listB = new() { "C", "D", listA }; - listA.Add(listB); + [Fact] + public void SerializeCircularList() { + List listA = new() { "A", "B" }; + List listB = new() { "C", "D", listA }; + listA.Add(listB); - Assert.AreEqual( // strings: - "a:3:{i:0;s:1:\"A\";i:1;s:1:\"B\";i:2;a:3:{i:0;s:1:\"C\";i:1;s:1:\"D\";i:2;N;}}", - PhpSerialization.Serialize(listA) - ); - } + Assert.Equal( // strings: + "a:3:{i:0;s:1:\"A\";i:1;s:1:\"B\";i:2;a:3:{i:0;s:1:\"C\";i:1;s:1:\"D\";i:2;N;}}", + PhpSerialization.Serialize(listA) + ); } -} \ No newline at end of file +} diff --git a/PhpSerializerNET.Test/Serialize/DictionarySerialization.cs b/PhpSerializerNET.Test/Serialize/DictionarySerialization.cs index 9552d74..c17e3f5 100644 --- a/PhpSerializerNET.Test/Serialize/DictionarySerialization.cs +++ b/PhpSerializerNET.Test/Serialize/DictionarySerialization.cs @@ -5,14 +5,14 @@ This Source Code Form is subject to the terms of the Mozilla Public **/ using System.Collections.Generic; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; -namespace PhpSerializerNET.Test.Serialize { - [TestClass] - public class DictionarySerializationTest { - [TestMethod] - public void SerializesMixedData() { - var dictionary = new Dictionary(){ +namespace PhpSerializerNET.Test.Serialize; + +public class DictionarySerializationTest { + [Fact] + public void SerializesMixedData() { + var dictionary = new Dictionary(){ { "AString", "this is a string value" }, { "AnInteger", (long)10 }, { "ADouble", 1.2345 }, @@ -20,59 +20,58 @@ public void SerializesMixedData() { { "False", false } }; - var result = PhpSerialization.Serialize( - dictionary - ); - Assert.AreEqual( - "a:5:{s:7:\"AString\";s:22:\"this is a string value\";s:9:\"AnInteger\";i:10;s:7:\"ADouble\";d:1.2345;s:4:\"True\";b:1;s:5:\"False\";b:0;}", - result - ); - } + var result = PhpSerialization.Serialize( + dictionary + ); + Assert.Equal( + "a:5:{s:7:\"AString\";s:22:\"this is a string value\";s:9:\"AnInteger\";i:10;s:7:\"ADouble\";d:1.2345;s:4:\"True\";b:1;s:5:\"False\";b:0;}", + result + ); + } - [TestMethod] - public void SerializesWithDoubleKeys() { - var dictionary = new Dictionary(){ + [Fact] + public void SerializesWithDoubleKeys() { + var dictionary = new Dictionary(){ {1.1, "a"}, {1.2, "b"}, {1.3, "c"} }; - Assert.AreEqual( - "a:3:{d:1.1;s:1:\"a\";d:1.2;s:1:\"b\";d:1.3;s:1:\"c\";}", - PhpSerialization.Serialize(dictionary) - ); - } + Assert.Equal( + "a:3:{d:1.1;s:1:\"a\";d:1.2;s:1:\"b\";d:1.3;s:1:\"c\";}", + PhpSerialization.Serialize(dictionary) + ); + } - [TestMethod] - public void SerializesWithBooleanKeys() { - var dictionary = new Dictionary(){ + [Fact] + public void SerializesWithBooleanKeys() { + var dictionary = new Dictionary(){ {true, "True"}, {false, "False"} }; - var result = PhpSerialization.Serialize( - dictionary - ); + var result = PhpSerialization.Serialize( + dictionary + ); - Assert.AreEqual( - "a:2:{b:1;s:4:\"True\";b:0;s:5:\"False\";}", - result - ); - } + Assert.Equal( + "a:2:{b:1;s:4:\"True\";b:0;s:5:\"False\";}", + result + ); + } - [TestMethod] - public void TerminatesCircularReference() { - var dictionary = new Dictionary(){ + [Fact] + public void TerminatesCircularReference() { + var dictionary = new Dictionary(){ {"1", "a"} }; - dictionary.Add("2", dictionary); + dictionary.Add("2", dictionary); - var result = PhpSerialization.Serialize(dictionary); + var result = PhpSerialization.Serialize(dictionary); - Assert.AreEqual( - "a:2:{s:1:\"1\";s:1:\"a\";s:1:\"2\";N;}", - result - ); - } + Assert.Equal( + "a:2:{s:1:\"1\";s:1:\"a\";s:1:\"2\";N;}", + result + ); } -} \ No newline at end of file +} diff --git a/PhpSerializerNET.Test/Serialize/DoubleSerialization.cs b/PhpSerializerNET.Test/Serialize/DoubleSerialization.cs index 6cc615f..f918bd6 100644 --- a/PhpSerializerNET.Test/Serialize/DoubleSerialization.cs +++ b/PhpSerializerNET.Test/Serialize/DoubleSerialization.cs @@ -4,65 +4,64 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; -namespace PhpSerializerNET.Test.Serialize { - [TestClass] - public class DoubleSerializationTest { - [TestMethod] - public void SerializesDecimalValue() { - Assert.AreEqual( - "d:1.23456789;", - PhpSerialization.Serialize(1.23456789) - ); - } +namespace PhpSerializerNET.Test.Serialize; - [TestMethod] - public void SerializesOne() { - Assert.AreEqual( - "d:1;", - PhpSerialization.Serialize((double)1) - ); - } +public class DoubleSerializationTest { + [Fact] + public void SerializesDecimalValue() { + Assert.Equal( + "d:1.23456789;", + PhpSerialization.Serialize(1.23456789) + ); + } - [TestMethod] - public void SerializesMinValue() { - Assert.AreEqual( - "d:-1.7976931348623157E+308;", - PhpSerialization.Serialize(double.MinValue) - ); - } + [Fact] + public void SerializesOne() { + Assert.Equal( + "d:1;", + PhpSerialization.Serialize((double)1) + ); + } - [TestMethod] - public void SerializesMaxValue() { - Assert.AreEqual( - "d:1.7976931348623157E+308;", - PhpSerialization.Serialize(double.MaxValue) - ); - } + [Fact] + public void SerializesMinValue() { + Assert.Equal( + "d:-1.7976931348623157E+308;", + PhpSerialization.Serialize(double.MinValue) + ); + } - [TestMethod] - public void SerializesInfinity() { - Assert.AreEqual( - "d:INF;", - PhpSerialization.Serialize(double.PositiveInfinity) - ); - } + [Fact] + public void SerializesMaxValue() { + Assert.Equal( + "d:1.7976931348623157E+308;", + PhpSerialization.Serialize(double.MaxValue) + ); + } - [TestMethod] - public void SerializesNegativeInfinity() { - Assert.AreEqual( - "d:-INF;", - PhpSerialization.Serialize(double.NegativeInfinity) - ); - } + [Fact] + public void SerializesInfinity() { + Assert.Equal( + "d:INF;", + PhpSerialization.Serialize(double.PositiveInfinity) + ); + } + + [Fact] + public void SerializesNegativeInfinity() { + Assert.Equal( + "d:-INF;", + PhpSerialization.Serialize(double.NegativeInfinity) + ); + } - [TestMethod] - public void SerializesNaN() { - Assert.AreEqual( - "d:NAN;", - PhpSerialization.Serialize(double.NaN) - ); - } + [Fact] + public void SerializesNaN() { + Assert.Equal( + "d:NAN;", + PhpSerialization.Serialize(double.NaN) + ); } } \ No newline at end of file diff --git a/PhpSerializerNET.Test/Serialize/DynamicSerialization.cs b/PhpSerializerNET.Test/Serialize/DynamicSerialization.cs index 68fa496..b5c7b5c 100644 --- a/PhpSerializerNET.Test/Serialize/DynamicSerialization.cs +++ b/PhpSerializerNET.Test/Serialize/DynamicSerialization.cs @@ -6,47 +6,45 @@ This Source Code Form is subject to the terms of the Mozilla Public **/ using System.Dynamic; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace PhpSerializerNET.Test.Serialize { - [TestClass] - public class DynamicSerializationTest { - [TestMethod] - public void SerializesPhpDynamicObject() { - dynamic data = new PhpDynamicObject(); - data.Foo = "a"; - data.Bar = 3.1415; - - Assert.AreEqual( - "O:8:\"stdClass\":2:{s:3:\"Foo\";s:1:\"a\";s:3:\"Bar\";d:3.1415;}", - PhpSerialization.Serialize(data) - ); - } - - [TestMethod] - public void SerializesPhpDynamicObjectWithClassname() { - dynamic data = new PhpDynamicObject(); - data.SetClassName("phpDynamicObject"); - data.Foo = "a"; - data.Bar = 3.1415; - System.Console.WriteLine(data.Bar); - Assert.AreEqual( - "O:16:\"phpDynamicObject\":2:{s:3:\"Foo\";s:1:\"a\";s:3:\"Bar\";d:3.1415;}", - PhpSerialization.Serialize(data) - ); - } - - [TestMethod] - public void SerializesExpandoObject() { - dynamic data = new ExpandoObject(); - data.Foo = "a"; - data.Bar = 3.1415; - - Assert.AreEqual( - "O:8:\"stdClass\":2:{s:3:\"Foo\";s:1:\"a\";s:3:\"Bar\";d:3.1415;}", - PhpSerialization.Serialize(data) - ); - } +using Xunit; +namespace PhpSerializerNET.Test.Serialize; + +public class DynamicSerializationTest { + [Fact] + public void SerializesPhpDynamicObject() { + dynamic data = new PhpDynamicObject(); + data.Foo = "a"; + data.Bar = 3.1415; + + Assert.Equal( + "O:8:\"stdClass\":2:{s:3:\"Foo\";s:1:\"a\";s:3:\"Bar\";d:3.1415;}", + PhpSerialization.Serialize(data) + ); + } + + [Fact] + public void SerializesPhpDynamicObjectWithClassname() { + dynamic data = new PhpDynamicObject(); + data.SetClassName("phpDynamicObject"); + data.Foo = "a"; + data.Bar = 3.1415; + System.Console.WriteLine(data.Bar); + Assert.Equal( + "O:16:\"phpDynamicObject\":2:{s:3:\"Foo\";s:1:\"a\";s:3:\"Bar\";d:3.1415;}", + PhpSerialization.Serialize(data) + ); + } + + [Fact] + public void SerializesExpandoObject() { + dynamic data = new ExpandoObject(); + data.Foo = "a"; + data.Bar = 3.1415; + + Assert.Equal( + "O:8:\"stdClass\":2:{s:3:\"Foo\";s:1:\"a\";s:3:\"Bar\";d:3.1415;}", + PhpSerialization.Serialize(data) + ); } } \ No newline at end of file diff --git a/PhpSerializerNET.Test/Serialize/EnumSerialization.cs b/PhpSerializerNET.Test/Serialize/EnumSerialization.cs index d239e52..a636a6a 100644 --- a/PhpSerializerNET.Test/Serialize/EnumSerialization.cs +++ b/PhpSerializerNET.Test/Serialize/EnumSerialization.cs @@ -5,27 +5,25 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; using PhpSerializerNET.Test.DataTypes; -namespace PhpSerializerNET.Test.Serialize { +namespace PhpSerializerNET.Test.Serialize; - [TestClass] - public class EnumSerializationTest { - [TestMethod] - public void SerializeOne() { - Assert.AreEqual( - "i:1;", - PhpSerialization.Serialize(IntEnum.A) - ); - } +public class EnumSerializationTest { + [Fact] + public void SerializeOne() { + Assert.Equal( + "i:1;", + PhpSerialization.Serialize(IntEnum.A) + ); + } - [TestMethod] - public void SerializeToString() { - Assert.AreEqual( - "s:1:\"A\";", - PhpSerialization.Serialize(IntEnum.A, new PhpSerializiationOptions{NumericEnums = false}) - ); - } + [Fact] + public void SerializeToString() { + Assert.Equal( + "s:1:\"A\";", + PhpSerialization.Serialize(IntEnum.A, new PhpSerializiationOptions { NumericEnums = false }) + ); } -} \ No newline at end of file +} diff --git a/PhpSerializerNET.Test/Serialize/IPhpObjectSerialization.cs b/PhpSerializerNET.Test/Serialize/IPhpObjectSerialization.cs index d7364d8..5dfd27b 100644 --- a/PhpSerializerNET.Test/Serialize/IPhpObjectSerialization.cs +++ b/PhpSerializerNET.Test/Serialize/IPhpObjectSerialization.cs @@ -3,31 +3,30 @@ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; using PhpSerializerNET.Test.DataTypes; -namespace PhpSerializerNET.Test { - [TestClass] - public class IPhpObjectSerializationTest { +namespace PhpSerializerNET.Test; - [TestMethod] - public void SerializeIPhpObject() { - var data = new MyPhpObject() { Foo = "" }; - data.SetClassName("MyPhpObject"); - Assert.AreEqual( // strings: - "O:11:\"MyPhpObject\":1:{s:3:\"Foo\";s:0:\"\";}", - PhpSerialization.Serialize(data) - ); - } +public class IPhpObjectSerializationTest { - [TestMethod] - public void SerializePhpObjectDictionary() { - var data = new PhpObjectDictionary() { { "Foo", "" } }; - data.SetClassName("MyPhpObject"); - Assert.AreEqual( // strings: - "O:11:\"MyPhpObject\":1:{s:3:\"Foo\";s:0:\"\";}", - PhpSerialization.Serialize(data) - ); - } + [Fact] + public void SerializeIPhpObject() { + var data = new MyPhpObject() { Foo = "" }; + data.SetClassName("MyPhpObject"); + Assert.Equal( // strings: + "O:11:\"MyPhpObject\":1:{s:3:\"Foo\";s:0:\"\";}", + PhpSerialization.Serialize(data) + ); + } + + [Fact] + public void SerializePhpObjectDictionary() { + var data = new PhpObjectDictionary() { { "Foo", "" } }; + data.SetClassName("MyPhpObject"); + Assert.Equal( // strings: + "O:11:\"MyPhpObject\":1:{s:3:\"Foo\";s:0:\"\";}", + PhpSerialization.Serialize(data) + ); } } \ No newline at end of file diff --git a/PhpSerializerNET.Test/Serialize/IntegerSerialization.cs b/PhpSerializerNET.Test/Serialize/IntegerSerialization.cs index ca6537e..125418c 100644 --- a/PhpSerializerNET.Test/Serialize/IntegerSerialization.cs +++ b/PhpSerializerNET.Test/Serialize/IntegerSerialization.cs @@ -5,41 +5,40 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; -namespace PhpSerializerNET.Test.Serialize { - [TestClass] - public class IntegerSerializationTest { - [TestMethod] - public void SerializeZero() { - Assert.AreEqual( - "i:0;", - PhpSerialization.Serialize(0) - ); - } +namespace PhpSerializerNET.Test.Serialize; - [TestMethod] - public void SerializeOne() { - Assert.AreEqual( - "i:1;", - PhpSerialization.Serialize(1) - ); - } +public class IntegerSerializationTest { + [Fact] + public void SerializeZero() { + Assert.Equal( + "i:0;", + PhpSerialization.Serialize(0) + ); + } + + [Fact] + public void SerializeOne() { + Assert.Equal( + "i:1;", + PhpSerialization.Serialize(1) + ); + } - [TestMethod] - public void SerializeIntMaxValue() { - Assert.AreEqual( - "i:2147483647;", - PhpSerialization.Serialize(int.MaxValue) - ); - } + [Fact] + public void SerializeIntMaxValue() { + Assert.Equal( + "i:2147483647;", + PhpSerialization.Serialize(int.MaxValue) + ); + } - [TestMethod] - public void SerializeIntMinValue() { - Assert.AreEqual( - "i:-2147483648;", - PhpSerialization.Serialize(int.MinValue) - ); - } + [Fact] + public void SerializeIntMinValue() { + Assert.Equal( + "i:-2147483648;", + PhpSerialization.Serialize(int.MinValue) + ); } } \ No newline at end of file diff --git a/PhpSerializerNET.Test/Serialize/ListSerialization.cs b/PhpSerializerNET.Test/Serialize/ListSerialization.cs index 5a53b4a..46d2430 100644 --- a/PhpSerializerNET.Test/Serialize/ListSerialization.cs +++ b/PhpSerializerNET.Test/Serialize/ListSerialization.cs @@ -5,33 +5,32 @@ This Source Code Form is subject to the terms of the Mozilla Public **/ using System.Collections.Generic; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; -namespace PhpSerializerNET.Test.Serialize { - [TestClass] - public class ListSerializationTest { - [TestMethod] - public void SerializeListOfStrings() { - Assert.AreEqual( // strings: - "a:2:{i:0;s:5:\"Hello\";i:1;s:5:\"World\";}", - PhpSerialization.Serialize(new List() { "Hello", "World" }) - ); - } +namespace PhpSerializerNET.Test.Serialize; - [TestMethod] - public void SerializeListOfBools() { - Assert.AreEqual( // booleans: - "a:2:{i:0;b:1;i:1;b:0;}", - PhpSerialization.Serialize(new List() { true, false }) - ); - } +public class ListSerializationTest { + [Fact] + public void SerializeListOfStrings() { + Assert.Equal( // strings: + "a:2:{i:0;s:5:\"Hello\";i:1;s:5:\"World\";}", + PhpSerialization.Serialize(new List() { "Hello", "World" }) + ); + } + + [Fact] + public void SerializeListOfBools() { + Assert.Equal( // booleans: + "a:2:{i:0;b:1;i:1;b:0;}", + PhpSerialization.Serialize(new List() { true, false }) + ); + } - [TestMethod] - public void SerializeMixedList() { - Assert.AreEqual( // mixed types: - "a:5:{i:0;b:1;i:1;i:1;i:2;d:1.23;i:3;s:3:\"end\";i:4;N;}", - PhpSerialization.Serialize(new List() { true, 1, 1.23, "end", null }) - ); - } + [Fact] + public void SerializeMixedList() { + Assert.Equal( // mixed types: + "a:5:{i:0;b:1;i:1;i:1;i:2;d:1.23;i:3;s:3:\"end\";i:4;N;}", + PhpSerialization.Serialize(new List() { true, 1, 1.23, "end", null }) + ); } } diff --git a/PhpSerializerNET.Test/Serialize/LongSerialization.cs b/PhpSerializerNET.Test/Serialize/LongSerialization.cs index 3145945..8c2019c 100644 --- a/PhpSerializerNET.Test/Serialize/LongSerialization.cs +++ b/PhpSerializerNET.Test/Serialize/LongSerialization.cs @@ -4,24 +4,23 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; -namespace PhpSerializerNET.Test.Serialize { - [TestClass] - public class LongSerializationTest { - [TestMethod] - public void SerializeIntMaxValue() { - Assert.AreEqual( - "i:9223372036854775807;", - PhpSerialization.Serialize(long.MaxValue) - ); - } - [TestMethod] - public void SerializeMinValue() { - Assert.AreEqual( - "i:-9223372036854775808;", - PhpSerialization.Serialize(long.MinValue) - ); - } +namespace PhpSerializerNET.Test.Serialize; + +public class LongSerializationTest { + [Fact] + public void SerializeIntMaxValue() { + Assert.Equal( + "i:9223372036854775807;", + PhpSerialization.Serialize(long.MaxValue) + ); + } + [Fact] + public void SerializeMinValue() { + Assert.Equal( + "i:-9223372036854775808;", + PhpSerialization.Serialize(long.MinValue) + ); } -} \ No newline at end of file +} diff --git a/PhpSerializerNET.Test/Serialize/NullSerialization.cs b/PhpSerializerNET.Test/Serialize/NullSerialization.cs index 97d7a58..a00ae8d 100644 --- a/PhpSerializerNET.Test/Serialize/NullSerialization.cs +++ b/PhpSerializerNET.Test/Serialize/NullSerialization.cs @@ -4,17 +4,16 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; -namespace PhpSerializerNET.Test.Serialize { - [TestClass] - public class NullSerializationTest { - [TestMethod] - public void SerializesNull() { - Assert.AreEqual( - "N;", - PhpSerialization.Serialize(null) - ); - } +namespace PhpSerializerNET.Test.Serialize; + +public class NullSerializationTest { + [Fact] + public void SerializesNull() { + Assert.Equal( + "N;", + PhpSerialization.Serialize(null) + ); } -} \ No newline at end of file +} diff --git a/PhpSerializerNET.Test/Serialize/ObjectSerialization.cs b/PhpSerializerNET.Test/Serialize/ObjectSerialization.cs index 548d795..9af7da9 100644 --- a/PhpSerializerNET.Test/Serialize/ObjectSerialization.cs +++ b/PhpSerializerNET.Test/Serialize/ObjectSerialization.cs @@ -5,76 +5,75 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; using PhpSerializerNET.Test.DataTypes; -namespace PhpSerializerNET.Test.Serialize { - [TestClass] - public class ObjectSerializationTest { - [TestMethod] - public void SerializesToStdClass() { - var testObject = new UnnamedClass() { - Foo = 3.14, - Bar = 2.718, - }; - Assert.AreEqual( - "O:8:\"stdClass\":2:{s:3:\"Foo\";d:3.14;s:3:\"Bar\";d:2.718;}", - PhpSerialization.Serialize(testObject) - ); - } +namespace PhpSerializerNET.Test.Serialize; - [TestMethod] - public void SerializesToSpecificClass() { - var testObject = new NamedClass() { - Foo = 3.14, - Bar = 2.718, - }; - Assert.AreEqual( - "O:7:\"myClass\":2:{s:3:\"Foo\";d:3.14;s:3:\"Bar\";d:2.718;}", - PhpSerialization.Serialize(testObject) - ); - } +public class ObjectSerializationTest { + [Fact] + public void SerializesToStdClass() { + var testObject = new UnnamedClass() { + Foo = 3.14, + Bar = 2.718, + }; + Assert.Equal( + "O:8:\"stdClass\":2:{s:3:\"Foo\";d:3.14;s:3:\"Bar\";d:2.718;}", + PhpSerialization.Serialize(testObject) + ); + } - [TestMethod] - public void SerializeObjectToArray() { - var testObject = new MappedClass() { - English = "Hello world!", - German = "Hallo Welt!", - It = "Ciao mondo!" - }; + [Fact] + public void SerializesToSpecificClass() { + var testObject = new NamedClass() { + Foo = 3.14, + Bar = 2.718, + }; + Assert.Equal( + "O:7:\"myClass\":2:{s:3:\"Foo\";d:3.14;s:3:\"Bar\";d:2.718;}", + PhpSerialization.Serialize(testObject) + ); + } - Assert.AreEqual( - "a:3:{s:2:\"en\";s:12:\"Hello world!\";s:2:\"de\";s:11:\"Hallo Welt!\";s:4:\"Guid\";a:1:{s:5:\"Empty\";N;}}", - PhpSerialization.Serialize(testObject) - ); - } + [Fact] + public void SerializeObjectToArray() { + var testObject = new MappedClass() { + English = "Hello world!", + German = "Hallo Welt!", + It = "Ciao mondo!" + }; - [TestMethod] - public void SerializeObjectToObject() { - var testObject = new UnnamedClass() { - Foo = 1, - Bar = 2, - }; + Assert.Equal( + "a:3:{s:2:\"en\";s:12:\"Hello world!\";s:2:\"de\";s:11:\"Hallo Welt!\";s:4:\"Guid\";a:1:{s:5:\"Empty\";N;}}", + PhpSerialization.Serialize(testObject) + ); + } - Assert.AreEqual( - "O:8:\"stdClass\":2:{s:3:\"Foo\";d:1;s:3:\"Bar\";d:2;}", - PhpSerialization.Serialize(testObject) - ); - } + [Fact] + public void SerializeObjectToObject() { + var testObject = new UnnamedClass() { + Foo = 1, + Bar = 2, + }; + + Assert.Equal( + "O:8:\"stdClass\":2:{s:3:\"Foo\";d:1;s:3:\"Bar\";d:2;}", + PhpSerialization.Serialize(testObject) + ); + } - [TestMethod] - public void ObjectIntoMixedKeyArray() { - var data = new MixedKeysPhpClass() { - Foo = "Foo", - Bar = "Bar", - Baz = "A", - Dummy = "B", - }; + [Fact] + public void ObjectIntoMixedKeyArray() { + var data = new MixedKeysPhpClass() { + Foo = "Foo", + Bar = "Bar", + Baz = "A", + Dummy = "B", + }; - Assert.AreEqual( - "O:8:\"stdClass\":4:{i:0;s:3:\"Foo\";i:1;s:3:\"Bar\";s:1:\"a\";s:1:\"A\";s:1:\"b\";s:1:\"B\";}", - PhpSerialization.Serialize(data) - ); - } + Assert.Equal( + "O:8:\"stdClass\":4:{i:0;s:3:\"Foo\";i:1;s:3:\"Bar\";s:1:\"a\";s:1:\"A\";s:1:\"b\";s:1:\"B\";}", + PhpSerialization.Serialize(data) + ); } -} \ No newline at end of file +} diff --git a/PhpSerializerNET.Test/Serialize/PhpDateTimeSerialization.cs b/PhpSerializerNET.Test/Serialize/PhpDateTimeSerialization.cs index c4ae929..c7b0826 100644 --- a/PhpSerializerNET.Test/Serialize/PhpDateTimeSerialization.cs +++ b/PhpSerializerNET.Test/Serialize/PhpDateTimeSerialization.cs @@ -5,22 +5,21 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; -namespace PhpSerializerNET.Test.Serialize { - [TestClass] - public class PhpDateTimeSerializationTest { - [TestMethod] - public void Serializes1() { - var testObject = new PhpDateTime() { - Date = "2021-12-15 19:32:38.980103", - TimezoneType = 3, - Timezone = "UTC", - }; - Assert.AreEqual( - "O:8:\"DateTime\":3:{s:4:\"date\";s:26:\"2021-12-15 19:32:38.980103\";s:13:\"timezone_type\";i:3;s:8:\"timezone\";s:3:\"UTC\";}", - PhpSerialization.Serialize(testObject) - ); - } +namespace PhpSerializerNET.Test.Serialize; + +public class PhpDateTimeSerializationTest { + [Fact] + public void Serializes1() { + var testObject = new PhpDateTime() { + Date = "2021-12-15 19:32:38.980103", + TimezoneType = 3, + Timezone = "UTC", + }; + Assert.Equal( + "O:8:\"DateTime\":3:{s:4:\"date\";s:26:\"2021-12-15 19:32:38.980103\";s:13:\"timezone_type\";i:3;s:8:\"timezone\";s:3:\"UTC\";}", + PhpSerialization.Serialize(testObject) + ); } -} \ No newline at end of file +} diff --git a/PhpSerializerNET.Test/Serialize/StringSerialization.cs b/PhpSerializerNET.Test/Serialize/StringSerialization.cs index 1b8956d..1192d37 100644 --- a/PhpSerializerNET.Test/Serialize/StringSerialization.cs +++ b/PhpSerializerNET.Test/Serialize/StringSerialization.cs @@ -5,42 +5,39 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; -namespace PhpSerializerNET.Test.Serialize { - [TestClass] - public class StringSerializationTest { - [TestMethod] - public void SerializeHelloWorld() { - Assert.AreEqual( - "s:12:\"Hello World!\";", - PhpSerialization.Serialize("Hello World!") - ); - } - - [TestMethod] - public void SerializeEmptyString() { - Assert.AreEqual( - "s:0:\"\";", - PhpSerialization.Serialize("") - ); - } +namespace PhpSerializerNET.Test.Serialize; +public class StringSerializationTest { + [Fact] + public void SerializeHelloWorld() { + Assert.Equal( + "s:12:\"Hello World!\";", + PhpSerialization.Serialize("Hello World!") + ); + } - [TestMethod] - public void SerializeUmlauts() { - Assert.AreEqual( - "s:14:\"äöüßÄÖÜ\";", - PhpSerialization.Serialize("äöüßÄÖÜ") - ); - } + [Fact] + public void SerializeEmptyString() { + Assert.Equal( + "s:0:\"\";", + PhpSerialization.Serialize("") + ); + } - [TestMethod] - public void SerializeEmoji() { - Assert.AreEqual( - "s:4:\"👻\";", - PhpSerialization.Serialize("👻") - ); - } + [Fact] + public void SerializeUmlauts() { + Assert.Equal( + "s:14:\"äöüßÄÖÜ\";", + PhpSerialization.Serialize("äöüßÄÖÜ") + ); + } + [Fact] + public void SerializeEmoji() { + Assert.Equal( + "s:4:\"👻\";", + PhpSerialization.Serialize("👻") + ); } -} \ No newline at end of file +} diff --git a/PhpSerializerNET.Test/Serialize/StructSerialization.cs b/PhpSerializerNET.Test/Serialize/StructSerialization.cs index 1495c6c..1432c22 100644 --- a/PhpSerializerNET.Test/Serialize/StructSerialization.cs +++ b/PhpSerializerNET.Test/Serialize/StructSerialization.cs @@ -4,30 +4,29 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; using PhpSerializerNET.Test.DataTypes; -namespace PhpSerializerNET.Test.Serialize { - [TestClass] - public class StructSerializationTest { - [TestMethod] - public void SerializeStruct() { - Assert.AreEqual( - "a:2:{s:3:\"foo\";s:3:\"Foo\";s:3:\"bar\";s:3:\"Bar\";}", - PhpSerialization.Serialize( - new AStruct() { foo = "Foo", bar = "Bar" } - ) - ); - } +namespace PhpSerializerNET.Test.Serialize; - [TestMethod] - public void SerializeStructWithIgnore() { - Assert.AreEqual( - "a:1:{s:3:\"foo\";s:3:\"Foo\";}", - PhpSerialization.Serialize( - new AStructWithIgnore() { foo = "Foo", bar = "Bar" } - ) - ); - } +public class StructSerializationTest { + [Fact] + public void SerializeStruct() { + Assert.Equal( + "a:2:{s:3:\"foo\";s:3:\"Foo\";s:3:\"bar\";s:3:\"Bar\";}", + PhpSerialization.Serialize( + new AStruct() { foo = "Foo", bar = "Bar" } + ) + ); } -} \ No newline at end of file + + [Fact] + public void SerializeStructWithIgnore() { + Assert.Equal( + "a:1:{s:3:\"foo\";s:3:\"Foo\";}", + PhpSerialization.Serialize( + new AStructWithIgnore() { foo = "Foo", bar = "Bar" } + ) + ); + } +} diff --git a/PhpSerializerNET/PhpSerializationException.cs b/PhpSerializerNET/Deserialization/DeserializationException.cs similarity index 100% rename from PhpSerializerNET/PhpSerializationException.cs rename to PhpSerializerNET/Deserialization/DeserializationException.cs diff --git a/PhpSerializerNET/Deserialization/PhpDataType.cs b/PhpSerializerNET/Deserialization/PhpDataType.cs new file mode 100644 index 0000000..efc1ee5 --- /dev/null +++ b/PhpSerializerNET/Deserialization/PhpDataType.cs @@ -0,0 +1,42 @@ +/** + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +**/ + +namespace PhpSerializerNET; + +/// +/// PHP data types that can be de/serialized. +/// +internal enum PhpDataType : byte { + /// + /// Null (N;) + /// + Null, + /// + /// Boolean value (b:n;) + /// + Boolean, + /// + /// Integer value (i:[value];) + /// + Integer, + /// + /// Floating point number (f:[value];) + /// + Floating, + /// + /// String (s:[length]:"[value]") + /// + String, + /// + /// Array (a:[length]:{[children]}) + /// + Array, + /// + /// Object (O:[identLength]:"[ident]":[length]:{[children]}) + /// + Object +} + diff --git a/PhpSerializerNET/Deserialization/PhpDeserializer.cs b/PhpSerializerNET/Deserialization/PhpDeserializer.cs new file mode 100644 index 0000000..f047fb6 --- /dev/null +++ b/PhpSerializerNET/Deserialization/PhpDeserializer.cs @@ -0,0 +1,503 @@ +/** + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +**/ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace PhpSerializerNET; + +internal ref struct PhpDeserializer { + private readonly PhpDeserializationOptions _options; + private Encoding _inputEncoding; + private readonly Span _tokens; + private readonly ReadOnlySpan _input; + private int _currentToken = 0; + + internal PhpDeserializer(Span tokens, ReadOnlySpan input, PhpDeserializationOptions options) { + _options = options; + _input = input; + _tokens = tokens; + _inputEncoding = _options.InputEncoding; + } + + internal object Deserialize() { + return this.DeserializeToken(); + } + + internal object Deserialize(Type targetType) { + return this.DeserializeToken(targetType); + } + + internal T Deserialize() { + return (T)this.Deserialize(typeof(T)); + } + + private object DeserializeToken() { + var token = this._tokens[this._currentToken]; + this._currentToken++; + switch (token.Type) { + case PhpDataType.Boolean: + return token.Value.GetBool(this._input); + case PhpDataType.Integer: + return token.Value.GetLong(this._input); + case PhpDataType.Floating: + return token.Value.GetDouble(this._input); + case PhpDataType.String: + if (this._options.NumberStringToBool) { + if (_input[token.Value.Start] == (byte)'1' || _input[token.Value.Start] == (byte)'0') { + return token.Value.GetBool(this._input); + } + } + return this.GetString(token); + case PhpDataType.Array: + return MakeCollection(token); + case PhpDataType.Object: + return MakeClass(token); + case PhpDataType.Null: + default: + return null; + } + } + + + + private object DeserializeToken(Type targetType) { + if (targetType == null) { + throw new ArgumentNullException(nameof(targetType)); + } + var token = this._tokens[this._currentToken]; + this._currentToken++; + switch (token.Type) { + case PhpDataType.Boolean: + return DeserializeBoolean(targetType, token); + case PhpDataType.Integer: + return DeserializeInteger(targetType, token); + case PhpDataType.Floating: + return DeserializeDouble(targetType, token); + case PhpDataType.String: + return DeserializeTokenFromSimpleType( + targetType, + token.Type, + this.GetString(token), + token.Position + ); + case PhpDataType.Object: { + object result; + if (typeof(IDictionary).IsAssignableFrom(targetType)) { + result = MakeDictionary(targetType, token); + } else if (targetType.IsClass) { + result = MakeObject(targetType, token); + } else { + result = MakeStruct(targetType, token); + } + if (result is IPhpObject phpObject and not PhpDateTime) { + phpObject.SetClassName(this.GetString(token)); + } + return result; + } + case PhpDataType.Array: { + if (targetType.IsAssignableTo(typeof(IList))) { + return this.MakeList(targetType, token); + } else if (targetType.IsAssignableTo(typeof(IDictionary))) { + return this.MakeDictionary(targetType, token); + } else if (targetType.IsClass) { + return this.MakeObject(targetType, token); + } else { + return this.MakeStruct(targetType, token); + } + } + case PhpDataType.Null: + default: + if (targetType.IsValueType) { + return Activator.CreateInstance(targetType); + } else { + return null; + } + } + } + + private object DeserializeInteger(Type targetType, in PhpToken token) { + return Type.GetTypeCode(targetType) switch { + TypeCode.Int16 => short.Parse(token.Value.GetSlice(_input), CultureInfo.InvariantCulture), + TypeCode.Int32 => int.Parse(token.Value.GetSlice(_input), CultureInfo.InvariantCulture), + TypeCode.Int64 => long.Parse(token.Value.GetSlice(_input), CultureInfo.InvariantCulture), + TypeCode.UInt16 => ushort.Parse(token.Value.GetSlice(_input), CultureInfo.InvariantCulture), + TypeCode.UInt32 => uint.Parse(token.Value.GetSlice(_input), CultureInfo.InvariantCulture), + TypeCode.UInt64 => ulong.Parse(token.Value.GetSlice(_input), CultureInfo.InvariantCulture), + TypeCode.SByte => sbyte.Parse(token.Value.GetSlice(_input), CultureInfo.InvariantCulture), + _ => this.DeserializeTokenFromSimpleType( + targetType, + token.Type, + this.GetString(token), + token.Position + ), + }; + } + + private object DeserializeDouble(Type targetType, in PhpToken token) { + if (targetType == typeof(double) || targetType == typeof(float)) { + return token.Value.GetDouble(_input); + } + + string value = this.GetString(token); + if (value == "INF") { + value = double.PositiveInfinity.ToString(CultureInfo.InvariantCulture); + } else if (value == "-INF") { + value = double.NegativeInfinity.ToString(CultureInfo.InvariantCulture); + } + return this.DeserializeTokenFromSimpleType(targetType, token.Type, value, token.Position); + } + + private object DeserializeBoolean(Type targetType, in PhpToken token) { + if (targetType == typeof(bool) || targetType == typeof(bool?)) { + return token.Value.GetBool(_input); + } + Type underlyingType = targetType; + if (targetType.IsNullableReferenceType()) { + underlyingType = targetType.GenericTypeArguments[0]; + } + + if (underlyingType.IsIConvertible()) { + return ((IConvertible)token.Value.GetBool(_input)).ToType(underlyingType, CultureInfo.InvariantCulture); + } else { + throw new DeserializationException( + $"Can not assign value \"{this.GetString(token)}\" (at position {token.Position}) to target type of {targetType.Name}." + ); + } + } + + private object DeserializeTokenFromSimpleType( + Type targetType, + PhpDataType dataType, + string value, + int tokenPosition + ) { + if (!targetType.IsPrimitive && targetType.IsNullableReferenceType()) { + if (value == "" && _options.EmptyStringToDefault) { + return null; + } + + targetType = targetType.GenericTypeArguments[0]; + if (targetType == null) { + throw new NullReferenceException("Could not get underlying type for nullable reference type " + targetType); + } + } + + // Short-circuit strings: + if (targetType == typeof(string)) { + return value == "" && _options.EmptyStringToDefault + ? default + : value; + } + + if (targetType.IsEnum) { + // Enums are converted by name if the token is a string and by underlying value if they are not + if (value == "" && this._options.EmptyStringToDefault) { + return Activator.CreateInstance(targetType); + } + + if (dataType != PhpDataType.String) { + return Enum.Parse(targetType, value); + } + + FieldInfo foundFieldInfo = TypeLookup.GetEnumInfo(targetType, value, this._options); + + if (foundFieldInfo == null) { + throw new DeserializationException( + $"Exception encountered while trying to assign '{value}' to type '{targetType.Name}'. " + + $"The value could not be matched to an enum member."); + } + + return foundFieldInfo.GetRawConstantValue(); + } + + if (targetType.IsIConvertible()) { + if (value == "" && _options.EmptyStringToDefault) { + return Activator.CreateInstance(targetType); + } + + if (targetType == typeof(bool)) { + if (_options.NumberStringToBool && value is "0" or "1") { + return value.PhpToBool(); + } + } + + try { + return ((IConvertible)value).ToType(targetType, CultureInfo.InvariantCulture); + } catch (Exception exception) { + throw new DeserializationException( + $"Exception encountered while trying to assign '{value}' to type {targetType.Name}. See inner exception for details.", + exception + ); + } + } + + if (targetType == typeof(Guid)) { + return value == "" && _options.EmptyStringToDefault + ? default + : new Guid(value); + } + + if (targetType == typeof(object)) { + return value == "" && _options.EmptyStringToDefault + ? default + : value; + } + + throw new DeserializationException($"Can not assign value \"{value}\" (at position {tokenPosition}) to target type of {targetType.Name}."); + } + + private object MakeClass(in PhpToken token) { + var typeName = this.GetString(token); + object constructedObject; + Type targetType = null; + if (typeName != "stdClass" && this._options.EnableTypeLookup) { + targetType = TypeLookup.FindTypeInAssymbly(typeName, this._options.TypeCache.HasFlag(TypeCacheFlag.ClassNames)); + } + if (targetType != null && typeName != "stdClass") { + _currentToken--; // go back one because we're basically re-entering the object-token from the top. + // If we don't decrement the pointer, we'd start with the first child token instead of the object token. + constructedObject = this.DeserializeToken(targetType); + } else { + dynamic result; + if (_options.StdClass == StdClassOption.Dynamic) { + result = new PhpDynamicObject(); + } else if (this._options.StdClass == StdClassOption.Dictionary) { + result = new PhpObjectDictionary(); + } else { + throw new DeserializationException("Encountered 'stdClass' and the behavior 'Throw' was specified in deserialization options."); + } + for (int i = 0; i < token.Length; i++) { + var key = this.DeserializeToken(); + var value = this.DeserializeToken(); + result.TryAdd( + (string)key, + value + ); + } + constructedObject = result; + } + if (constructedObject is IPhpObject phpObject and not PhpDateTime) { + phpObject.SetClassName(typeName); + } + return constructedObject; + } + + private object MakeStruct(Type targetType, in PhpToken token) { + var result = Activator.CreateInstance(targetType); + Dictionary fields = TypeLookup.GetFieldInfos(targetType, this._options); + + for (int i = 0; i < token.Length; i++) { + var fieldName = this._tokens[this._currentToken++].Value.GetString(_input, _options.InputEncoding); + if (!this._options.CaseSensitiveProperties) { + fieldName = fieldName.ToLower(); + } + + if (!fields.ContainsKey(fieldName)) { + if (!this._options.AllowExcessKeys) { + throw new DeserializationException( + $"Could not bind the key \"{fieldName}\" to struct of type {targetType.Name}: No such field." + ); + } + continue; + } + if (fields[fieldName] != null) { + var field = fields[fieldName]; + try { + field.SetValue(result, DeserializeToken(field.FieldType)); + } catch (Exception exception) { + var valueToken = this._tokens[this._currentToken]; + throw new DeserializationException( + $"Exception encountered while trying to assign '{valueToken.Value}' to {targetType.Name}.{field.Name}. " + + "See inner exception for details.", + exception + ); + } + } + } + return result; + } + + private object MakeObject(Type targetType, in PhpToken token) { + var result = Activator.CreateInstance(targetType); + Dictionary properties = TypeLookup.GetPropertyInfos(targetType, this._options); + + for (int i = 0; i < token.Length; i++) { + object propertyName; + var nameToken = this._tokens[_currentToken++]; + if (nameToken.Type == PhpDataType.String) { + propertyName = this._options.CaseSensitiveProperties + ? this.GetString(nameToken) + : this.GetString(nameToken).ToLower(); + } else if (nameToken.Type == PhpDataType.Integer) { + propertyName = nameToken.Value.GetLong(_input); + } else { + throw new DeserializationException( + $"Error encountered deserizalizing an object of type '{targetType.FullName}': " + + $"The key '{this.GetString(nameToken)}' (from the token at position {nameToken.Position}) has an unsupported type of '{nameToken.Type}'." + ); + } + if (!properties.ContainsKey(propertyName)) { + if (!this._options.AllowExcessKeys) { + throw new DeserializationException( + $"Could not bind the key \"{propertyName}\" to object of type {targetType.Name}: No such property." + ); + } + _currentToken++; + continue; + } + var property = properties[propertyName]; + if (property != null) { // null if PhpIgnore'd + try { + property.SetValue( + result, + DeserializeToken(property.PropertyType) + ); + } catch (Exception exception) { + var valueToken = _tokens[_currentToken-1]; + throw new DeserializationException( + $"Exception encountered while trying to assign '{this.GetString(valueToken)}' to {targetType.Name}.{property.Name}. See inner exception for details.", + exception + ); + } + } else { + _currentToken++; + } + } + return result; + } + + private object MakeArray(Type targetType, in PhpToken token) { + var elementType = targetType.GetElementType() ?? throw new InvalidOperationException("targetType.GetElementType() returned null"); + Array result = Array.CreateInstance(elementType, token.Length); + + var arrayIndex = 0; + for (int i = 0; i < token.Length; i++) { + _currentToken++; + result.SetValue( + elementType == typeof(object) + ? DeserializeToken() + : DeserializeToken(elementType), + arrayIndex + ); + arrayIndex++; + } + return result; + } + + private object MakeList(Type targetType, in PhpToken token) { + for (int i = 0; i < token.Length * 2; i+=2) { + if (this._tokens[_currentToken+i].Type != PhpDataType.Integer) { + var badToken = this._tokens[_currentToken+i]; + throw new DeserializationException( + $"Can not deserialize array at position {token.Position} to list: " + + $"It has a non-integer key '{this.GetString(badToken)}' at element {i} (position {badToken.Position})." + ); + } + } + + if (targetType.IsArray) { + return MakeArray(targetType, token); + } + var result = (IList)Activator.CreateInstance(targetType, token.Length); + if (result == null) { + throw new NullReferenceException("Activator.CreateInstance(targetType) returned null"); + } + Type itemType; + if (targetType.GenericTypeArguments.Length >= 1) { + itemType = targetType.GenericTypeArguments[0]; + } else { + itemType = typeof(object); + } + + for (int i = 0; i < token.Length; i++) { + _currentToken++; + result.Add( + itemType == typeof(object) + ? DeserializeToken() + : DeserializeToken(itemType) + ); + } + return result; + } + + private object MakeDictionary(Type targetType, in PhpToken token) { + var result = (IDictionary)Activator.CreateInstance(targetType); + if (result == null) { + throw new NullReferenceException($"Activator.CreateInstance({targetType.FullName}) returned null"); + } + if (!targetType.GenericTypeArguments.Any()) { + for (int i = 0; i < token.Length; i++) { + result.Add( + DeserializeToken(), + DeserializeToken() + ); + } + return result; + } + Type keyType = targetType.GenericTypeArguments[0]; + Type valueType = targetType.GenericTypeArguments[1]; + + for (int i = 0; i < token.Length; i++) { + result.Add( + keyType == typeof(object) + ? DeserializeToken() + : DeserializeToken(keyType), + valueType == typeof(object) + ? DeserializeToken() + : DeserializeToken(valueType) + ); + } + return result; + } + + private object MakeCollection(in PhpToken token) { + if (this._options.UseLists == ListOptions.Never) { + return this.MakeDictionary(typeof(Dictionary), token); + } + long previousKey = -1; + bool isList = true; + bool consecutive = true; + for (int i = 0; i < token.Length*2; i+=2) { + if (this._tokens[_currentToken+i].Type != PhpDataType.Integer) { + isList = false; + break; + } else { + var key = this._tokens[_currentToken+i].Value.GetLong(_input); + if (i == 0 || key == previousKey + 1) { + previousKey = key; + } else { + consecutive = false; + } + } + } + if (!isList || (this._options.UseLists == ListOptions.Default && consecutive == false)) { + var result = new Dictionary(token.Length); + for (int i = 0; i < token.Length; i++) { + result.Add( + this.DeserializeToken(), + this.DeserializeToken() + ); + } + return result; + } else { + var result = new List(token.Length); + for (int i = 0; i < token.Length; i++) { + _currentToken++; + result.Add(this.DeserializeToken()); + } + return result; + } + } + + private string GetString(in PhpToken token) { + return token.Value.GetString(this._input, this._options.InputEncoding); + } +} \ No newline at end of file diff --git a/PhpSerializerNET/PhpDeserializiationOptions.cs b/PhpSerializerNET/Deserialization/PhpDeserializiationOptions.cs similarity index 100% rename from PhpSerializerNET/PhpDeserializiationOptions.cs rename to PhpSerializerNET/Deserialization/PhpDeserializiationOptions.cs diff --git a/PhpSerializerNET/Deserialization/PhpToken.cs b/PhpSerializerNET/Deserialization/PhpToken.cs new file mode 100644 index 0000000..510d299 --- /dev/null +++ b/PhpSerializerNET/Deserialization/PhpToken.cs @@ -0,0 +1,24 @@ +/** + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +**/ +namespace PhpSerializerNET; +#nullable enable + +/// +/// PHP data token. Holds the type, position (in the input string), length and value. +/// +internal readonly struct PhpToken { + internal readonly PhpDataType Type; + internal readonly int Position; + internal readonly int Length; + + internal readonly ValueSpan Value; + internal PhpToken(PhpDataType type, int position, in ValueSpan value, int length = 0) { + this.Type = type; + this.Position = position; + this.Value = value; + this.Length = length; + } +} diff --git a/PhpSerializerNET/Deserialization/PhpTokenValidator.cs b/PhpSerializerNET/Deserialization/PhpTokenValidator.cs new file mode 100644 index 0000000..52c11e4 --- /dev/null +++ b/PhpSerializerNET/Deserialization/PhpTokenValidator.cs @@ -0,0 +1,255 @@ +/** + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +**/ + +using System; +using System.Runtime.CompilerServices; + +namespace PhpSerializerNET; + +#nullable enable + +internal ref struct PhpTokenValidator { + private int _position; + private int _tokenCount; + private readonly ReadOnlySpan _input; + private readonly int _lastIndex; + + internal PhpTokenValidator(in ReadOnlySpan input) { + this._tokenCount = 1; + this._input = input; + this._position = 0; + this._lastIndex = this._input.Length - 1; + } + + internal void GetToken() { + switch ( this._input[this._position++]) { + case (byte)'b': + this.GetCharacter(':'); + this.GetBoolean(); + this.GetCharacter(';'); + break; + case (byte)'N': + this.GetCharacter(';'); + break; + case (byte)'s': + this.GetCharacter(':'); + int length = this.GetLength(PhpDataType.String); + this.GetCharacter(':'); + this.GetCharacter('"'); + this.GetNCharacters(length); + this.GetCharacter('"'); + this.GetCharacter(';'); + break; + case (byte)'i': + this.GetCharacter(':'); + this.GetInteger(); + this.GetCharacter(';'); + break; + case (byte)'d': + this.GetCharacter(':'); + this.GetFloat(); + this.GetCharacter(';'); + break; + case (byte)'a': + this.GetArrayToken(); + break; + case (byte)'O': + this.GetObjectToken(); + break; + default: + throw new DeserializationException( + $"Unexpected token '{this.GetCharAt(this._position - 1)}' at position {this._position - 1}." + ); + }; + } + + private char GetCharAt(int position) { + return (char)this._input[position]; + } + + private void GetCharacter(char character) { + if (this._lastIndex < this._position) { + throw new DeserializationException( + $"Unexpected end of input. Expected '{character}' at index {this._position}, but input ends at index {this._lastIndex}" + ); + } + if (this._input[this._position++] != character) { + throw new DeserializationException( + $"Unexpected token at index {this._position - 1}. Expected '{character}' but found '{this.GetCharAt(this._position - 1)}' instead." + ); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void GetFloat() { + int i = this._position; + for (; this._input[i] != (byte)';' && i < this._lastIndex; i++) { + _ = this._input[this._position] switch { + >= (byte)'0' and <= (byte)'9' => true, + (byte)'+' => true, + (byte)'-' => true, + (byte)'.' => true, + (byte)'E' or (byte)'e' => true, // exponents. + (byte)'I' or (byte)'F' => true, // infinity. + (byte)'N' or (byte)'A' => true, // NaN. + _ => throw new DeserializationException( + $"Unexpected token at index {this._position}. " + + $"'{this.GetCharAt(this._position)}' is not a valid part of a floating point number." + ), + }; + } + if (i == this._position) { + throw new DeserializationException( + $"Unexpected token at index {i}: Expected floating point number, but found ';' instead." + ); + } + this._position = i; + + // Edgecase: input ends here without a delimeter following. Normal handling would give a misleading exception: + if (this._lastIndex == this._position && this._input[this._position] != (byte)';') { + throw new DeserializationException( + $"Unexpected end of input. Expected ':' at index {this._position}, but input ends at index {this._lastIndex}" + ); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void GetInteger() { + int i = this._position; + for (; this._input[i] != ';' && i < this._lastIndex; i++) { + _ = this._input[i] switch { + >= (byte)'0' and <= (byte)'9' => true, + (byte)'+' => true, + (byte)'-' => true, + _ => throw new DeserializationException( + $"Unexpected token at index {i}. " + + $"'{this.GetCharAt(i)}' is not a valid part of a number." + ), + }; + } + if (i == this._position) { + throw new DeserializationException( + $"Unexpected token at index {i}: Expected number, but found ';' instead." + ); + } + this._position = i; + + // Edgecase: input ends here without a delimeter following. Normal handling would give a misleading exception: + if (this._lastIndex == this._position && this._input[this._position] != (byte)';') { + throw new DeserializationException( + $"Unexpected end of input. Expected ':' at index {this._position}, but input ends at index {this._lastIndex}" + ); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetLength(PhpDataType dataType) { + int length = 0; + + for (; this._input[this._position] != ':' && this._position < this._lastIndex; this._position++) { + length = this._input[this._position] switch { + >= (byte)'0' and <= (byte)'9' => length * 10 + (_input[_position] - 48), + _ => throw new DeserializationException( + $"{dataType} at position {this._position} has illegal, missing or malformed length." + ), + }; + } + return length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void GetBoolean() { + if (this._lastIndex < this._position) { + throw new DeserializationException( + $"Unexpected end of input. Expected '0' or '1' at index {this._position}, but input ends at index {this._lastIndex}" + ); + } + var item = this._input[this._position++]; + if (item != 48 && item != 49) { + throw new DeserializationException( + $"Unexpected token in boolean at index {this._position - 1}. " + + $"Expected either '1' or '0', but found '{(char)item}' instead." + ); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void GetNCharacters(int length) { + if (this._position + length > this._lastIndex) { + throw new DeserializationException( + $"Illegal length of {length}. The string at position {this._position} points to out of bounds index {this._position + length}." + ); + } + this._position += length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void GetObjectToken() { + int position = _position - 1; + this.GetCharacter(':'); + int classNamelength = this.GetLength(PhpDataType.Object); + this.GetCharacter(':'); + this.GetCharacter('"'); + this.GetNCharacters(classNamelength); + this.GetCharacter('"'); + this.GetCharacter(':'); + int propertyCount = this.GetLength(PhpDataType.Object); + this.GetCharacter(':'); + this.GetCharacter('{'); + int i = 0; + while (this._input[this._position] != '}') { + this.GetToken(); + this.GetToken(); + i++; + if (i > propertyCount) { + throw new DeserializationException( + $"Object at position {position} should have {propertyCount} properties, " + + $"but actually has {i} or more properties." + ); + } + } + this._tokenCount += propertyCount * 2; + this.GetCharacter('}'); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void GetArrayToken() { + int position = _position - 1; + this.GetCharacter(':'); + int length = this.GetLength(PhpDataType.Array); + this.GetCharacter(':'); + this.GetCharacter('{'); + int i = 0; + while (this._input[this._position] != '}') { + this.GetToken(); + this.GetToken(); + i++; + if (i > length) { + throw new DeserializationException( + $"Array at position {position} should be of length {length}, " + + $"but actual length is {i} or more." + ); + } + } + this._tokenCount += length * 2; + this.GetCharacter('}'); + } + + /// + /// Validate the PHP data and return the number of tokens found. + /// + /// The raw UTF8 bytes of PHP data to validate. + /// The number of tokens found. + /// + internal static int Validate(ReadOnlySpan input) { + var validatior = new PhpTokenValidator(input); + validatior.GetToken(); + if (validatior._position <= validatior._lastIndex) { + throw new DeserializationException($"Unexpected token '{(char)input[validatior._position]}' at position {validatior._position}."); + } + return validatior._tokenCount; + } +} diff --git a/PhpSerializerNET/Deserialization/PhpTokenizer.cs b/PhpSerializerNET/Deserialization/PhpTokenizer.cs new file mode 100644 index 0000000..cccd90b --- /dev/null +++ b/PhpSerializerNET/Deserialization/PhpTokenizer.cs @@ -0,0 +1,183 @@ +/** + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +**/ + +using System; +using System.Globalization; +using System.Runtime.CompilerServices; + +#nullable enable + +namespace PhpSerializerNET; + +public ref struct PhpTokenizer { + private Span _tokens; + private readonly ReadOnlySpan _input; + private int _position; + private int _tokenPosition; + + private PhpTokenizer(ReadOnlySpan input, Span array) { + this._input = input; + this._tokens = array; + this._position = 0; + this._tokenPosition = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Advance() { + this._position++; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Advance(int positons) { + this._position += positons; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ValueSpan GetNumbers() { + int start = this._position; + while (this._input[this._position] != (byte)';') { + this._position++; + } + return new ValueSpan(start, this._position-start); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetLength() { + if (this._input[this._position + 1] == ':') { + return _input[_position++] - 48; + } + int start = this._position; + while (this._input[this._position] != (byte)':') { + this._position++; + } + return int.Parse(this._input.Slice(start, this._position - start), CultureInfo.InvariantCulture); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + internal void GetToken() { + switch (this._input[this._position++]) { + case (byte)'b': + this.GetBooleanToken(); + break; + case (byte)'N': + this._tokens[this._tokenPosition++] = new PhpToken(PhpDataType.Null, _position - 1, ValueSpan.Empty); + this.Advance(); + break; + case (byte)'s': + this.GetStringToken(); + break; + case (byte)'i': + this.GetIntegerToken(); + break; + case (byte)'d': + this.GetFloatingToken(); + break; + case (byte)'a': + this.GetArrayToken(); + break; + case (byte)'O': + this.GetObjectToken(); + break; + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void GetNullToken() { + this._tokens[this._tokenPosition++] = new PhpToken(PhpDataType.Null, _position - 1, ValueSpan.Empty); + this.Advance(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void GetBooleanToken() { + this.Advance(); + this._tokens[this._tokenPosition++] = new PhpToken( + PhpDataType.Boolean, + _position - 2, + new ValueSpan(this._position++, 1) + ); + this.Advance(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void GetStringToken() { + int position = _position - 1; + this.Advance(); + int length = this.GetLength(); + this.Advance(2); + this._tokens[this._tokenPosition++] = new PhpToken( + PhpDataType.String, + position, + new ValueSpan(this._position, length) + ); + this.Advance(2 + length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void GetIntegerToken() { + this.Advance(); + this._tokens[this._tokenPosition++] = new PhpToken( + PhpDataType.Integer, + this._position - 2, + this.GetNumbers() + ); + this.Advance(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void GetFloatingToken() { + this.Advance(); + this._tokens[this._tokenPosition++] = new PhpToken( + PhpDataType.Floating, + this._position - 2, + this.GetNumbers() + ); + this.Advance(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void GetArrayToken() { + int position = _position - 1; + this.Advance(); + int length = this.GetLength(); + this._tokens[this._tokenPosition++] = new PhpToken( + PhpDataType.Array, + position, + ValueSpan.Empty, + length + ); + this.Advance(2); + for (int i = 0; i < length * 2; i++) { + this.GetToken(); + } + this.Advance(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void GetObjectToken() { + int position = _position - 1; + this.Advance(); + int classNameLength = this.GetLength(); + this.Advance(2); + ValueSpan classNameSpan = new ValueSpan(this._position, classNameLength); + this.Advance(2 + classNameLength); + int propertyCount = this.GetLength(); + this._tokens[this._tokenPosition++] = new PhpToken( + PhpDataType.Object, + position, + classNameSpan, + propertyCount + ); + this.Advance(2); + for (int i = 0; i < propertyCount * 2; i++) { + this.GetToken(); + } + this.Advance(); + } + + internal static void Tokenize(ReadOnlySpan inputBytes, Span tokens) { + new PhpTokenizer(inputBytes, tokens).GetToken(); + } +} \ No newline at end of file diff --git a/PhpSerializerNET/Deserialization/ValueSpan.cs b/PhpSerializerNET/Deserialization/ValueSpan.cs new file mode 100644 index 0000000..d0b5aa4 --- /dev/null +++ b/PhpSerializerNET/Deserialization/ValueSpan.cs @@ -0,0 +1,46 @@ +/** + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +**/ +namespace PhpSerializerNET; + +using System; +using System.Globalization; +using System.Text; + +internal readonly struct ValueSpan { + private static ValueSpan _empty = new ValueSpan(0,0); + internal readonly int Start; + internal readonly int Length; + public ValueSpan(int start, int length) { + this.Start = start; + this.Length = length; + } + + public static ValueSpan Empty => _empty; + + public ReadOnlySpan GetSlice(ReadOnlySpan input) => input.Slice(this.Start, this.Length); + + + internal double GetDouble(ReadOnlySpan input) { + var value = input.Slice(Start, Length); + return value switch { + [(byte)'I', (byte)'N', (byte)'F'] => double.PositiveInfinity, + [(byte)'-', (byte)'I', (byte)'N', (byte)'F'] => double.NegativeInfinity, + [(byte)'N', (byte)'A', (byte)'N'] => double.NaN, + _ => double.Parse(value, CultureInfo.InvariantCulture), + }; + } + + internal bool GetBool(ReadOnlySpan input) => input[this.Start] == '1'; + + internal long GetLong(ReadOnlySpan input) => long.Parse( + input.Slice(this.Start, this.Length), + CultureInfo.InvariantCulture + ); + + internal string GetString(ReadOnlySpan input, Encoding inputEncoding) { + return inputEncoding.GetString(input.Slice(this.Start, this.Length)); + } +} \ No newline at end of file diff --git a/PhpSerializerNET/Extensions/ArrayExtensions.cs b/PhpSerializerNET/Extensions/ArrayExtensions.cs index fb948c8..a5e6552 100644 --- a/PhpSerializerNET/Extensions/ArrayExtensions.cs +++ b/PhpSerializerNET/Extensions/ArrayExtensions.cs @@ -4,6 +4,7 @@ This Source Code Form is subject to the terms of the Mozilla Public file, You can obtain one at http://mozilla.org/MPL/2.0/. **/ +using System; using System.Collections.Generic; using System.Reflection; using System.Text; @@ -11,26 +12,7 @@ This Source Code Form is subject to the terms of the Mozilla Public namespace PhpSerializerNET; internal static class ArrayExtensions { - - public static string Utf8Substring(this byte[] array, int start, int length, Encoding encoding) { - if (length > array.Length - start) { - return ""; - } - - if (encoding == Encoding.UTF8) { - // Using the ReadonlySpan<> saves some copying: - return Encoding.UTF8.GetString(new System.ReadOnlySpan(array, start, length)); - } else { - // Sadly, Encoding.Convert does not accept a Span. - byte[] substring = new byte[length]; - System.Buffer.BlockCopy(array, start, substring, 0, length); - return Encoding.UTF8.GetString( - Encoding.Convert(encoding, Encoding.UTF8, substring) - ); - } - } - - public static Dictionary GetAllProperties(this PropertyInfo[] properties, PhpDeserializationOptions options) { + internal static Dictionary GetAllProperties(this PropertyInfo[] properties, PhpDeserializationOptions options) { var result = new Dictionary(properties.Length); foreach (var property in properties) { var isIgnored = false; @@ -63,7 +45,7 @@ public static Dictionary GetAllProperties(this PropertyInf return result; } - public static Dictionary GetAllFields(this FieldInfo[] fields, PhpDeserializationOptions options) { + internal static Dictionary GetAllFields(this FieldInfo[] fields, PhpDeserializationOptions options) { var result = new Dictionary(fields.Length); foreach (var field in fields) { var isIgnored = false; diff --git a/PhpSerializerNET/PhpDeserializer.cs b/PhpSerializerNET/PhpDeserializer.cs deleted file mode 100644 index 767eeea..0000000 --- a/PhpSerializerNET/PhpDeserializer.cs +++ /dev/null @@ -1,478 +0,0 @@ -/** - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -**/ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Reflection; - -namespace PhpSerializerNET; - -internal class PhpDeserializer { - private readonly PhpDeserializationOptions _options; - private readonly PhpSerializeToken _token; - - public PhpDeserializer(string input, PhpDeserializationOptions options) { - _options = options; - if (_options == null) { - _options = PhpDeserializationOptions.DefaultOptions; - } - this._token = new PhpTokenizer(input, this._options.InputEncoding).Tokenize(); - } - - public object Deserialize() { - return this.DeserializeToken(this._token); - } - - public object Deserialize(Type targetType) { - return this.DeserializeToken(targetType, this._token); - } - - public T Deserialize() { - return (T)this.Deserialize(typeof(T)); - } - - /// - /// Reset the type lookup cache. - /// Can be useful for scenarios in which new types are loaded at runtime in between deserialization tasks. - /// - public static void ClearTypeCache() { - TypeLookup.ClearTypeCache(); - } - - /// - /// Reset the property info cache. - /// Can be useful for scenarios in which new types are loaded at runtime in between deserialization tasks. - /// - public static void ClearPropertyInfoCache() { - TypeLookup.ClearPropertyInfoCache(); - } - - private object DeserializeToken(PhpSerializeToken token) { - switch (token.Type) { - case PhpSerializerType.Boolean: - return token.Value.PhpToBool(); - case PhpSerializerType.Integer: - return token.Value.PhpToLong(); - case PhpSerializerType.Floating: - return token.Value.PhpToDouble(); - case PhpSerializerType.String: - if (this._options.NumberStringToBool && (token.Value == "0" || token.Value == "1")) { - return token.Value.PhpToBool(); - } - return token.Value; - case PhpSerializerType.Array: - return MakeCollection(token); - case PhpSerializerType.Object: - return MakeClass(token); - case PhpSerializerType.Null: - default: - return null; - } - } - - private object MakeClass(PhpSerializeToken token) { - var typeName = token.Value; - object constructedObject; - Type targetType = null; - if (typeName != "stdClass" && this._options.EnableTypeLookup) { - targetType = TypeLookup.FindTypeInAssymbly(typeName, this._options.TypeCache.HasFlag(TypeCacheFlag.ClassNames)); - } - if (targetType != null && typeName != "stdClass") { - constructedObject = this.DeserializeToken(targetType, token); - } else { - dynamic result; - if (_options.StdClass == StdClassOption.Dynamic) { - result = new PhpDynamicObject(); - } else if (this._options.StdClass == StdClassOption.Dictionary) { - result = new PhpObjectDictionary(); - } else { - throw new DeserializationException("Encountered 'stdClass' and the behavior 'Throw' was specified in deserialization options."); - } - for (int i = 0; i < token.Children.Length; i += 2) { - result.TryAdd( - token.Children[i].Value, - this.DeserializeToken(token.Children[i + 1]) - ); - } - constructedObject = result; - } - if (constructedObject is IPhpObject phpObject and not PhpDateTime) { - phpObject.SetClassName(typeName); - } - return constructedObject; - } - - private object DeserializeToken(Type targetType, PhpSerializeToken token) { - if (targetType == null) { - throw new ArgumentNullException(nameof(targetType)); - } - - switch (token.Type) { - case PhpSerializerType.Boolean: { - return DeserializeBoolean(targetType, token); - } - case PhpSerializerType.Integer: - return DeserializeInteger(targetType, token); - case PhpSerializerType.Floating: - return DeserializeDouble(targetType, token); - case PhpSerializerType.String: - return DeserializeTokenFromSimpleType(targetType, token); - case PhpSerializerType.Object: { - object result; - if (typeof(IDictionary).IsAssignableFrom(targetType)) { - result = MakeDictionary(targetType, token); - } else if (targetType.IsClass) { - result = MakeObject(targetType, token); - } else { - result = MakeStruct(targetType, token); - } - if (result is IPhpObject phpObject and not PhpDateTime) { - phpObject.SetClassName(token.Value); - } - return result; - } - case PhpSerializerType.Array: { - if (targetType.IsAssignableTo(typeof(IList))) { - return this.MakeList(targetType, token); - } else if (targetType.IsAssignableTo(typeof(IDictionary))) { - return this.MakeDictionary(targetType, token); - } else if (targetType.IsClass) { - return this.MakeObject(targetType, token); - } else { - return this.MakeStruct(targetType, token); - } - } - case PhpSerializerType.Null: - default: - if (targetType.IsValueType) { - return Activator.CreateInstance(targetType); - } else { - return null; - } - } - } - - private object DeserializeInteger(Type targetType, PhpSerializeToken token) { - return Type.GetTypeCode(targetType) switch { - TypeCode.Int16 => short.Parse(token.Value), - TypeCode.Int32 => int.Parse(token.Value), - TypeCode.Int64 => long.Parse(token.Value), - TypeCode.UInt16 => ushort.Parse(token.Value), - TypeCode.UInt32 => uint.Parse(token.Value), - TypeCode.UInt64 => ulong.Parse(token.Value), - TypeCode.SByte => sbyte.Parse(token.Value), - _ => this.DeserializeTokenFromSimpleType(targetType, token), - }; - } - - private object DeserializeDouble(Type targetType, PhpSerializeToken token) { - if (targetType == typeof(double) || targetType == typeof(float)) { - return token.Value.PhpToDouble(); - } - - token.Value = token.Value switch { - "INF" => double.PositiveInfinity.ToString(CultureInfo.InvariantCulture), - "-INF" => double.NegativeInfinity.ToString(CultureInfo.InvariantCulture), - _ => token.Value, - }; - return this.DeserializeTokenFromSimpleType(targetType, token); - } - - private static object DeserializeBoolean(Type targetType, PhpSerializeToken token) { - if (targetType == typeof(bool) || targetType == typeof(bool?)) { - return token.Value.PhpToBool(); - } - Type underlyingType = targetType; - if (targetType.IsNullableReferenceType()) { - underlyingType = targetType.GenericTypeArguments[0]; - } - - if (underlyingType.IsIConvertible()) { - return ((IConvertible)token.Value.PhpToBool()).ToType(underlyingType, CultureInfo.InvariantCulture); - } else { - throw new DeserializationException( - $"Can not assign value \"{token.Value}\" (at position {token.Position}) to target type of {targetType.Name}." - ); - } - } - - private object DeserializeTokenFromSimpleType(Type givenType, PhpSerializeToken token) { - var targetType = givenType; - if (!targetType.IsPrimitive && targetType.IsNullableReferenceType()) { - if (token.Value == "" && _options.EmptyStringToDefault) { - return null; - } - - targetType = targetType.GenericTypeArguments[0]; - if (targetType == null) { - throw new NullReferenceException("Could not get underlying type for nullable reference type " + givenType); - } - } - - // Short-circuit strings: - if (targetType == typeof(string)) { - return token.Value == "" && _options.EmptyStringToDefault - ? default - : token.Value; - } - - if (targetType.IsEnum) { - // Enums are converted by name if the token is a string and by underlying value if they are not - if (token.Value == "" && this._options.EmptyStringToDefault) { - return Activator.CreateInstance(targetType); - } - - if (token.Type != PhpSerializerType.String) { - return Enum.Parse(targetType, token.Value); - } - - FieldInfo foundFieldInfo = TypeLookup.GetEnumInfo(targetType, token.Value, this._options); - - if (foundFieldInfo == null) { - throw new DeserializationException( - $"Exception encountered while trying to assign '{token.Value}' to type '{targetType.Name}'. " + - $"The value could not be matched to an enum member."); - } - - return foundFieldInfo.GetRawConstantValue(); - } - - if (targetType.IsIConvertible()) { - if (token.Value == "" && _options.EmptyStringToDefault) { - return Activator.CreateInstance(targetType); - } - - if (targetType == typeof(bool)) { - if (_options.NumberStringToBool && token.Value is "0" or "1") { - return token.Value.PhpToBool(); - } - } - - try { - return ((IConvertible)token.Value).ToType(targetType, CultureInfo.InvariantCulture); - } catch (Exception exception) { - throw new DeserializationException( - $"Exception encountered while trying to assign '{token.Value}' to type {targetType.Name}. See inner exception for details.", - exception - ); - } - } - - if (targetType == typeof(Guid)) { - return token.Value == "" && _options.EmptyStringToDefault - ? default - : new Guid(token.Value); - } - - if (targetType == typeof(object)) { - return token.Value == "" && _options.EmptyStringToDefault - ? default - : token.Value; - } - - throw new DeserializationException($"Can not assign value \"{token.Value}\" (at position {token.Position}) to target type of {targetType.Name}."); - } - - private object MakeStruct(Type targetType, PhpSerializeToken token) { - var result = Activator.CreateInstance(targetType); - Dictionary fields = TypeLookup.GetFieldInfos(targetType, this._options); - - for (int i = 0; i < token.Children.Length; i += 2) { - var fieldName = this._options.CaseSensitiveProperties ? token.Children[i].Value : token.Children[i].Value.ToLower(); - var valueToken = token.Children[i + 1]; - if (!fields.ContainsKey(fieldName)) { - if (!this._options.AllowExcessKeys) { - throw new DeserializationException( - $"Could not bind the key \"{token.Children[i].Value}\" to struct of type {targetType.Name}: No such field." - ); - } - continue; - } - if (fields[fieldName] != null) { - var field = fields[fieldName]; - try { - field.SetValue(result, DeserializeToken(field.FieldType, valueToken)); - } catch (Exception exception) { - throw new DeserializationException( - $"Exception encountered while trying to assign '{valueToken.Value}' to {targetType.Name}.{field.Name}. " + - "See inner exception for details.", - exception - ); - } - } - } - return result; - } - - private object MakeObject(Type targetType, PhpSerializeToken token) { - var result = Activator.CreateInstance(targetType); - Dictionary properties = TypeLookup.GetPropertyInfos(targetType, this._options); - - for (int i = 0; i < token.Children.Length; i += 2) { - object propertyName; - if (token.Children[i].Type == PhpSerializerType.String) { - propertyName = this._options.CaseSensitiveProperties ? token.Children[i].Value : token.Children[i].Value.ToLower(); - } else if (token.Children[i].Type == PhpSerializerType.Integer) { - propertyName = token.Children[i].Value.PhpToLong(); - } else { - throw new DeserializationException( - $"Error encountered deserizalizing an object of type '{targetType.FullName}': " + - $"The key '{token.Children[i].Value}' (from the token at position {token.Children[i].Position}) has an unsupported type of '{token.Children[i].Type}'." - ); - } - - var valueToken = token.Children[i + 1]; - - if (!properties.ContainsKey(propertyName)) { - if (!this._options.AllowExcessKeys) { - throw new DeserializationException( - $"Could not bind the key \"{token.Children[i].Value}\" to object of type {targetType.Name}: No such property." - ); - } - continue; - } - var property = properties[propertyName]; - if (property != null) { // null if PhpIgnore'd - try { - property.SetValue( - result, - DeserializeToken(property.PropertyType, valueToken) - ); - } catch (Exception exception) { - throw new DeserializationException( - $"Exception encountered while trying to assign '{valueToken.Value}' to {targetType.Name}.{property.Name}. See inner exception for details.", - exception - ); - } - } - } - return result; - } - - private object MakeArray(Type targetType, PhpSerializeToken token) { - var elementType = targetType.GetElementType() ?? throw new InvalidOperationException("targetType.GetElementType() returned null"); - Array result = Array.CreateInstance(elementType, token.Children.Length / 2); - - var arrayIndex = 0; - for (int i = 1; i < token.Children.Length; i += 2) { - result.SetValue( - elementType == typeof(object) - ? DeserializeToken(token.Children[i]) - : DeserializeToken(elementType, token.Children[i]), - arrayIndex - ); - arrayIndex++; - } - return result; - } - - private object MakeList(Type targetType, PhpSerializeToken token) { - for (int i = 0; i < token.Children.Length; i += 2) { - if (token.Children[i].Type != PhpSerializerType.Integer) { - throw new DeserializationException( - $"Can not deserialize array at position {token.Position} to list: " + - $"It has a non-integer key '{token.Children[i].Value}' at element {i} (position {token.Children[i].Position})." - ); - } - } - - if (targetType.IsArray) { - return MakeArray(targetType, token); - } - var result = (IList)Activator.CreateInstance(targetType); - if (result == null) { - throw new NullReferenceException("Activator.CreateInstance(targetType) returned null"); - } - Type itemType = typeof(object); - if (targetType.GenericTypeArguments.Length >= 1) { - itemType = targetType.GenericTypeArguments[0]; - } - - for (int i = 1; i < token.Children.Length; i += 2) { - result.Add( - itemType == typeof(object) - ? DeserializeToken(token.Children[i]) - : DeserializeToken(itemType, token.Children[i]) - ); - } - return result; - } - - private object MakeDictionary(Type targetType, PhpSerializeToken token) { - var result = (IDictionary)Activator.CreateInstance(targetType); - if (result == null) { - throw new NullReferenceException($"Activator.CreateInstance({targetType.FullName}) returned null"); - } - if (!targetType.GenericTypeArguments.Any()) { - for (int i = 0; i < token.Children.Length; i += 2) { - var keyToken = token.Children[i]; - var valueToken = token.Children[i + 1]; - result.Add( - DeserializeToken(keyToken), - DeserializeToken(valueToken) - ); - } - return result; - } - Type keyType = targetType.GenericTypeArguments[0]; - Type valueType = targetType.GenericTypeArguments[1]; - - for (int i = 0; i < token.Children.Length; i += 2) { - var keyToken = token.Children[i]; - var valueToken = token.Children[i + 1]; - result.Add( - keyType == typeof(object) - ? DeserializeToken(keyToken) - : DeserializeToken(keyType, keyToken), - valueType == typeof(object) - ? DeserializeToken(valueToken) - : DeserializeToken(valueType, valueToken) - ); - } - return result; - } - - private object MakeCollection(PhpSerializeToken token) { - if (this._options.UseLists == ListOptions.Never) { - return this.MakeDictionary(typeof(Dictionary), token); - } - long previousKey = -1; - bool isList = true; - bool consecutive = true; - for (int i = 0; i < token.Children.Length; i += 2) { - if (token.Children[i].Type != PhpSerializerType.Integer) { - isList = false; - break; - } else { - var key = token.Children[i].Value.PhpToLong(); - if (i == 0 || key == previousKey + 1) { - previousKey = key; - } else { - consecutive = false; - } - } - } - if (!isList || (this._options.UseLists == ListOptions.Default && consecutive == false)) { - var result = new Dictionary(); - for (int i = 0; i < token.Children.Length; i += 2) { - result.Add( - this.DeserializeToken(token.Children[i]), - this.DeserializeToken(token.Children[i + 1]) - ); - } - return result; - } else { - var result = new List(); - for (int i = 1; i < token.Children.Length; i += 2) { - result.Add(this.DeserializeToken(token.Children[i])); - } - return result; - } - } -} \ No newline at end of file diff --git a/PhpSerializerNET/PhpSerialization.cs b/PhpSerializerNET/PhpSerialization.cs index 4d68796..4d8a02f 100644 --- a/PhpSerializerNET/PhpSerialization.cs +++ b/PhpSerializerNET/PhpSerialization.cs @@ -13,6 +13,17 @@ This Source Code Form is subject to the terms of the Mozilla Public namespace PhpSerializerNET; public static class PhpSerialization { + /// + /// Reset the type lookup cache. + /// Can be useful for scenarios in which new types are loaded at runtime in between deserialization tasks. + /// + public static void ClearTypeCache() => TypeLookup.ClearTypeCache(); + + /// + /// Reset the property info cache. + /// Can be useful for scenarios in which new types are loaded at runtime in between deserialization tasks. + /// + public static void ClearPropertyInfoCache() => TypeLookup.ClearPropertyInfoCache(); /// /// Deserialize the given string into an object. @@ -37,7 +48,18 @@ public static class PhpSerialization { if (string.IsNullOrEmpty(input)) { throw new ArgumentOutOfRangeException(nameof(input), "PhpSerialization.Deserialize(): Parameter 'input' must not be null or empty."); } - return new PhpDeserializer(input, options).Deserialize(); + if (options == null) { + options = PhpDeserializationOptions.DefaultOptions; + } + int size = options.InputEncoding.GetByteCount(input); + Span inputBytes = size < 256 + ? stackalloc byte[size] + : new byte[size]; + options.InputEncoding.GetBytes(input, inputBytes); + int tokenCount = PhpTokenValidator.Validate(inputBytes); + Span tokens = new PhpToken[tokenCount]; + PhpTokenizer.Tokenize(inputBytes, tokens); + return new PhpDeserializer(tokens, inputBytes, options).Deserialize(); } /// @@ -63,7 +85,18 @@ public static T Deserialize( if (string.IsNullOrEmpty(input)) { throw new ArgumentOutOfRangeException(nameof(input), "PhpSerialization.Deserialize(): Parameter 'input' must not be null or empty."); } - return new PhpDeserializer(input, options).Deserialize(); + if (options == null) { + options = PhpDeserializationOptions.DefaultOptions; + } + int size = options.InputEncoding.GetByteCount(input); + Span inputBytes = size < 256 + ? stackalloc byte[size] + : new byte[size]; + options.InputEncoding.GetBytes(input, inputBytes); + int tokenCount = PhpTokenValidator.Validate(inputBytes); + Span tokens = new PhpToken[tokenCount]; + PhpTokenizer.Tokenize(inputBytes, tokens); + return new PhpDeserializer(tokens, inputBytes, options).Deserialize(); } /// @@ -93,7 +126,18 @@ public static T Deserialize( if (string.IsNullOrEmpty(input)) { throw new ArgumentOutOfRangeException(nameof(input), "PhpSerialization.Deserialize(): Parameter 'input' must not be null or empty."); } - return new PhpDeserializer(input, options).Deserialize(type); + if (options == null) { + options = PhpDeserializationOptions.DefaultOptions; + } + int size = options.InputEncoding.GetByteCount(input); + Span inputBytes = size < 256 + ? stackalloc byte[size] + : new byte[size]; + options.InputEncoding.GetBytes(input, inputBytes); + int tokenCount = PhpTokenValidator.Validate(inputBytes); + Span tokens = new PhpToken[tokenCount]; + PhpTokenizer.Tokenize(inputBytes, tokens); + return new PhpDeserializer(tokens, inputBytes, options).Deserialize(type); } /// @@ -112,18 +156,4 @@ public static string Serialize(object? input, PhpSerializiationOptions? options return new PhpSerializer(options) .Serialize(input) ?? throw new NullReferenceException($"{nameof(PhpSerializer)}.{nameof(Serialize)} returned null"); } - - /// - /// Reset the type lookup cache. - /// Can be useful for scenarios in which new types are loaded at runtime in between deserialization tasks. - /// - public static void ClearTypeCache() => - PhpDeserializer.ClearTypeCache(); - - /// - /// Reset the property info cache. - /// Can be useful for scenarios in which new types are loaded at runtime in between deserialization tasks. - /// - public static void ClearPropertyInfoCache() => - PhpDeserializer.ClearPropertyInfoCache(); } diff --git a/PhpSerializerNET/PhpSerializeToken.cs b/PhpSerializerNET/PhpSerializeToken.cs deleted file mode 100644 index 747669d..0000000 --- a/PhpSerializerNET/PhpSerializeToken.cs +++ /dev/null @@ -1,16 +0,0 @@ -/** - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -**/ -namespace PhpSerializerNET; - -/// -/// PHP Serialization format token. Holds type, length, position (of the token in the input string) and child information. -/// -internal record struct PhpSerializeToken( - PhpSerializerType Type, - int Position, - string Value, - PhpSerializeToken[] Children -); \ No newline at end of file diff --git a/PhpSerializerNET/PhpSerializerNET.csproj b/PhpSerializerNET/PhpSerializerNET.csproj index 7227b38..c168b97 100644 --- a/PhpSerializerNET/PhpSerializerNET.csproj +++ b/PhpSerializerNET/PhpSerializerNET.csproj @@ -1,8 +1,8 @@ PhpSerializerNET - net6.0;net7.0;net8.0 - 10.0 + net8.0 + 12.0 1.4.0 StringEpsilon A library for working with the PHP serialization format. diff --git a/PhpSerializerNET/PhpSerializerType.cs b/PhpSerializerNET/PhpSerializerType.cs deleted file mode 100644 index 1434411..0000000 --- a/PhpSerializerNET/PhpSerializerType.cs +++ /dev/null @@ -1,17 +0,0 @@ -/** - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -**/ - -namespace PhpSerializerNET; - -internal enum PhpSerializerType { - Null, - Boolean, - Integer, - Floating, - String, - Array, - Object -} diff --git a/PhpSerializerNET/PhpTokenizer.cs b/PhpSerializerNET/PhpTokenizer.cs deleted file mode 100644 index 711f26d..0000000 --- a/PhpSerializerNET/PhpTokenizer.cs +++ /dev/null @@ -1,302 +0,0 @@ -/** - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -**/ - -using System; -using System.Runtime.CompilerServices; -using System.Text; - -namespace PhpSerializerNET; - -public class PhpTokenizer { - - private int _position; - private readonly Encoding _inputEncoding; - - private readonly byte[] _input; - private readonly int _lastIndex; - -#if DEBUG - private char DebugCurrentCharacter => (char)_input[_position]; - private char[] DebugInput => _inputEncoding.GetChars(_input); -#endif - - public PhpTokenizer(string input, Encoding inputEncoding) { - this._inputEncoding = inputEncoding; - this._input = Encoding.Convert( - Encoding.Default, - this._inputEncoding, - Encoding.Default.GetBytes(input) - ); - this._position = 0; - this._lastIndex = this._input.Length - 1; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CheckBounds(string expectation) { - if (this._lastIndex < this._position) { - throw new DeserializationException( - $"Unexpected end of input. Expected '{expectation}' at index {this._position}, but input ends at index {this._lastIndex}" - ); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CheckBounds(char expectation) { - if (this._lastIndex < this._position) { - throw new DeserializationException( - $"Unexpected end of input. Expected '{expectation}' at index {this._position}, but input ends at index {this._lastIndex}" - ); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private PhpSerializerType GetDataType() { - var result = (char)this._input[this._position] switch { - 'N' => PhpSerializerType.Null, - 'b' => PhpSerializerType.Boolean, - 's' => PhpSerializerType.String, - 'i' => PhpSerializerType.Integer, - 'd' => PhpSerializerType.Floating, - 'a' => PhpSerializerType.Array, - 'O' => PhpSerializerType.Object, - _ => throw new DeserializationException($"Unexpected token '{(char)this._input[this._position]}' at position {this._position}.") - }; - this._position++; - return result; - } - - private void GetCharacter(char character) { - this.CheckBounds(character); - if (this._input[this._position] != character) { - throw new DeserializationException( - $"Unexpected token at index {this._position}. Expected '{character}' but found '{(char)this._input[this._position]}' instead." - ); - } - this._position++; - } - - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void GetTerminator() { - this.GetCharacter(';'); - } - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void GetDelimiter() { - this.GetCharacter(':'); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private string GetNumbers(bool isFloating) { - bool valid = true; - int start = this._position; - int end = this._position; - - for (; this._input[this._position] != ';' && this._position < this._lastIndex && valid; this._position++) { - _ = (char)this._input[this._position] switch { - >= '0' and <= '9' => true, - '+' => true, - '-' => true, - '.' => isFloating, - 'E' or 'e' => isFloating, // exponents. - 'I' or 'N' or 'F' => isFloating, // infinity. - 'N' or 'A' => isFloating, // NaN. - _ => throw new DeserializationException( - $"Unexpected token at index {this._position}. " + - $"'{(char)this._input[this._position]}' is not a valid part of a {(isFloating ? "floating point " : "")}number." - ), - }; - end++; - } - - this._position = end; - - // Edgecase: input ends here without a delimeter following. Normal handling would give a misleading exception: - if (this._lastIndex == this._position && (char)this._input[this._position] != ';') { - throw new DeserializationException( - $"Unexpected end of input. Expected ':' at index {this._position}, but input ends at index {this._lastIndex}" - ); - } - return this._input.Utf8Substring(start, end - start, this._inputEncoding); - } - - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int GetLength(PhpSerializerType dataType) { - int length = 0; - - for (; this._input[this._position] != ':' && this._position < this._lastIndex; this._position++) { - length = (char)this._input[this._position] switch { - >= '0' and <= '9' => length * 10 + (_input[_position] - 48), - _ => throw new DeserializationException( - $"{dataType} at position {this._position} has illegal, missing or malformed length." - ), - }; - } - return length; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private string GetBoolean() { - this.CheckBounds("0' or '1"); - - string result = (char)this._input[this._position] switch { - '1' => "1", - '0' => "0", - _ => throw new DeserializationException( - $"Unexpected token in boolean at index {this._position}. Expected either '1' or '0', but found '{(char)this._input[this._position]}' instead." - ) - }; - this._position++; - return result; - } - - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void GetBracketClose() { - this.GetCharacter('}'); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void GetBracketOpen() { - this.GetCharacter('{'); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private string GetNCharacters(int length) { - if (this._position + length > this._lastIndex) { - throw new DeserializationException( - $"Illegal length of {length}. The string at position {this._position} points to out of bounds index {this._position + length}." - ); - } - int start = this._position; - this._position += length; - return this._input.Utf8Substring(start, length, this._inputEncoding); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal PhpSerializeToken GetToken() { - return this.GetDataType() switch { - PhpSerializerType.Boolean => this.GetBooleanToken(), - PhpSerializerType.Null => this.GetNullToken(), - PhpSerializerType.String => this.GetStringToken(), - PhpSerializerType.Integer => this.GetIntegerToken(), - PhpSerializerType.Floating => this.GetFloatingToken(), - PhpSerializerType.Array => this.GetArrayToken(), - PhpSerializerType.Object => this.GetObjectToken(), - _ => new PhpSerializeToken() - }; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private PhpSerializeToken GetObjectToken() { - var result = new PhpSerializeToken() { - Type = PhpSerializerType.Object, - Position = _position - 1, - }; - this.GetDelimiter(); - int classNamelength = this.GetLength(PhpSerializerType.Object); - this.GetDelimiter(); - this.GetCharacter('"'); - result.Value = this.GetNCharacters(classNamelength); - this.GetCharacter('"'); - this.GetDelimiter(); - int propertyCount = this.GetLength(PhpSerializerType.Object); - this.GetDelimiter(); - this.GetBracketOpen(); - result.Children = new PhpSerializeToken[propertyCount * 2]; - int i = 0; - try { - while (this._input[this._position] != '}') { - result.Children[i++] = this.GetToken(); - } - } catch (System.IndexOutOfRangeException ex) { - throw new DeserializationException( - $"Object at position {result.Position} should have {propertyCount} properties, " + - $"but actually has {(int)((i + 1) / 2)} or more properties.", - ex - ); - } - this.GetBracketClose(); - return result; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private PhpSerializeToken GetArrayToken() { - var result = new PhpSerializeToken() { Type = PhpSerializerType.Array, Position = _position - 1 }; - this.GetDelimiter(); - int length = this.GetLength(PhpSerializerType.Array); - this.GetDelimiter(); - this.GetBracketOpen(); - result.Children = new PhpSerializeToken[length * 2]; - int i = 0; - try { - while (this._input[this._position] != '}') { - result.Children[i++] = this.GetToken(); - } - } catch (IndexOutOfRangeException ex) { - throw new DeserializationException( - $"Array at position {result.Position} should be of length {length}, " + - $"but actual length is {(int)((i + 1) / 2)} or more.", - ex - ); - } - this.GetBracketClose(); - return result; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private PhpSerializeToken GetFloatingToken() { - var result = new PhpSerializeToken() { Type = PhpSerializerType.Floating, Position = _position - 1 }; - this.GetDelimiter(); - result.Value = this.GetNumbers(true); - this.GetTerminator(); - return result; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private PhpSerializeToken GetIntegerToken() { - var result = new PhpSerializeToken() { Type = PhpSerializerType.Integer, Position = _position - 1 }; - this.GetDelimiter(); - result.Value = this.GetNumbers(false); - this.GetTerminator(); - return result; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private PhpSerializeToken GetStringToken() { - var result = new PhpSerializeToken() { Type = PhpSerializerType.String, Position = _position - 1 }; - this.GetDelimiter(); - int length = this.GetLength(result.Type); - this.GetDelimiter(); - this.GetCharacter('"'); - result.Value = this.GetNCharacters(length); - this.GetCharacter('"'); - this.GetTerminator(); - return result; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private PhpSerializeToken GetNullToken() { - this.GetTerminator(); - return new PhpSerializeToken() { Type = PhpSerializerType.Null, Position = _position - 2 }; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private PhpSerializeToken GetBooleanToken() { - var result = new PhpSerializeToken() { Type = PhpSerializerType.Boolean, Position = _position - 1 }; - this.GetDelimiter(); - result.Value = this.GetBoolean(); - this.GetTerminator(); - return result; - } - - internal PhpSerializeToken Tokenize() { - var result = this.GetToken(); - if (this._position <= this._lastIndex) { - throw new DeserializationException($"Unexpected token '{(char)this._input[this._position]}' at position {this._position}."); - } - return result; - } -} \ No newline at end of file