diff --git a/ObjectPrinting/DataMember.cs b/ObjectPrinting/DataMember.cs new file mode 100644 index 00000000..96238455 --- /dev/null +++ b/ObjectPrinting/DataMember.cs @@ -0,0 +1,29 @@ +using System; +using System.Reflection; + +namespace ObjectPrinting +{ + public class DataMember + { + public DataMember(FieldInfo fieldInfo) + { + Name = fieldInfo.Name; + GetValue = fieldInfo.GetValue; + Type = fieldInfo.FieldType; + MemberInfo = fieldInfo; + } + + public DataMember(PropertyInfo property) + { + Name = property.Name; + GetValue = property.GetValue; + Type = property.PropertyType; + MemberInfo = property; + } + + public string Name { get; } + public Type Type { get; } + public Func GetValue { get; } + public MemberInfo MemberInfo { get; } + } +} \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinter.cs b/ObjectPrinting/ObjectPrinter.cs index 3c7867c3..9a19ba1d 100644 --- a/ObjectPrinting/ObjectPrinter.cs +++ b/ObjectPrinting/ObjectPrinter.cs @@ -1,6 +1,6 @@ namespace ObjectPrinting { - public class ObjectPrinter + public static class ObjectPrinter { public static PrintingConfig For() { diff --git a/ObjectPrinting/PrinterExtensions.cs b/ObjectPrinting/PrinterExtensions.cs new file mode 100644 index 00000000..f22948ca --- /dev/null +++ b/ObjectPrinting/PrinterExtensions.cs @@ -0,0 +1,10 @@ +namespace ObjectPrinting +{ + public static class PrinterExtensions + { + public static PrintingConfig CreatePrinter(this T instance) + { + return ObjectPrinter.For(); + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index a9e08211..b6ff88d3 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,41 +1,98 @@ using System; -using System.Linq; -using System.Text; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; +using ObjectPrinting.Serialization; namespace ObjectPrinting { public class PrintingConfig { + private readonly HashSet excludedMembers = new HashSet(); + private readonly HashSet excludedTypes = new HashSet(); + + private readonly Dictionary membersSerializesInfos = + new Dictionary(); + + private readonly Dictionary typeSerializesInfos = + new Dictionary(); + + private Func handleMaxRecursion; + + private readonly int maxRecursion = 1; + public string PrintToString(TOwner obj) { return PrintToString(obj, 0); } + public PrintingConfig OnMaxRecursion(Func func) + { + handleMaxRecursion = func; + + return this; + } + + public PrintingConfig Exclude(Expression> exclude) + { + if (exclude == null) + throw new ArgumentNullException(); + + var memberInfo = GetMemberInfo(exclude); + excludedMembers.Add(memberInfo); + + return this; + } + + private static MemberInfo GetMemberInfo(Expression> expression) + { + var memberExpression = expression.Body is UnaryExpression unaryExpression + ? (MemberExpression)unaryExpression.Operand + : (MemberExpression)expression.Body; + + return memberExpression.Member; + } + + + public PrintingConfig Exclude() + { + excludedTypes.Add(typeof(TPropType)); + + return this; + } + private string PrintToString(object obj, int nestingLevel) { - //TODO apply configurations - if (obj == null) - return "null" + Environment.NewLine; - - var finalTypes = new[] - { - typeof(int), typeof(double), typeof(float), typeof(string), - typeof(DateTime), typeof(TimeSpan) - }; - if (finalTypes.Contains(obj.GetType())) - return obj + Environment.NewLine; - - var identation = new string('\t', nestingLevel + 1); - var sb = new StringBuilder(); - var type = obj.GetType(); - sb.AppendLine(type.Name); - foreach (var propertyInfo in type.GetProperties()) - { - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); - } - return sb.ToString(); + var serializer = new Serializer( + excludedMembers, + excludedTypes, + membersSerializesInfos, + typeSerializesInfos, + maxRecursion, + handleMaxRecursion); + + return serializer.Serialize(obj, nestingLevel); + } + + public IUsing Printing() + { + var config = new SerializationConfig(this); + typeSerializesInfos[typeof(T)] = config; + + return config; + } + + public IUsing Printing(Expression> property) + { + if (property == null) + throw new ArgumentNullException(); + + var memberInfo = GetMemberInfo(property); + + var config = new SerializationConfig(this); + membersSerializesInfos[memberInfo] = config; + + return config; } } } \ No newline at end of file diff --git a/ObjectPrinting/Serialization/IHasSerializationFunc.cs b/ObjectPrinting/Serialization/IHasSerializationFunc.cs new file mode 100644 index 00000000..3f9f3bd4 --- /dev/null +++ b/ObjectPrinting/Serialization/IHasSerializationFunc.cs @@ -0,0 +1,9 @@ +using System; + +namespace ObjectPrinting.Serialization +{ + public interface IHasSerializationFunc + { + Func SerializationFunc { get; } + } +} \ No newline at end of file diff --git a/ObjectPrinting/Serialization/ISerializationConfig.cs b/ObjectPrinting/Serialization/ISerializationConfig.cs new file mode 100644 index 00000000..2d3b6507 --- /dev/null +++ b/ObjectPrinting/Serialization/ISerializationConfig.cs @@ -0,0 +1,9 @@ +namespace ObjectPrinting.Serialization +{ + public interface ISerializationConfig : + IWrap, + IUsing, + IHasSerializationFunc + { + } +} \ No newline at end of file diff --git a/ObjectPrinting/Serialization/IUsing.cs b/ObjectPrinting/Serialization/IUsing.cs new file mode 100644 index 00000000..02831afa --- /dev/null +++ b/ObjectPrinting/Serialization/IUsing.cs @@ -0,0 +1,9 @@ +using System; + +namespace ObjectPrinting.Serialization +{ + public interface IUsing + { + public IWrap Using(Func serialize); + } +} \ No newline at end of file diff --git a/ObjectPrinting/Serialization/IWrap.cs b/ObjectPrinting/Serialization/IWrap.cs new file mode 100644 index 00000000..341134c5 --- /dev/null +++ b/ObjectPrinting/Serialization/IWrap.cs @@ -0,0 +1,11 @@ +using System; + +namespace ObjectPrinting.Serialization +{ + public interface IWrap + { + PrintingConfig And { get; } + + IWrap Wrap(Func modify); + } +} \ No newline at end of file diff --git a/ObjectPrinting/Serialization/SerializationConfig.cs b/ObjectPrinting/Serialization/SerializationConfig.cs new file mode 100644 index 00000000..df3e5282 --- /dev/null +++ b/ObjectPrinting/Serialization/SerializationConfig.cs @@ -0,0 +1,38 @@ +using System; + +namespace ObjectPrinting.Serialization +{ + public class SerializationConfig : ISerializationConfig + { + private Func serialize; + + public SerializationConfig(PrintingConfig printingConfig) + { + And = printingConfig; + } + + public PrintingConfig And { get; } + + public IWrap Using(Func serialize) + { + if (serialize == null) + throw new ArgumentNullException(); + + this.serialize = serialize; + return this; + } + + public IWrap Wrap(Func modify) + { + if (modify == null) + throw new ArgumentNullException(); + + var currentFunc = serialize; + serialize = value => modify(currentFunc(value)); + return this; + } + + public Func SerializationFunc => + p => serialize((TSerialization)p); + } +} \ No newline at end of file diff --git a/ObjectPrinting/Serialization/Serializer.cs b/ObjectPrinting/Serialization/Serializer.cs new file mode 100644 index 00000000..9ba98fc6 --- /dev/null +++ b/ObjectPrinting/Serialization/Serializer.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace ObjectPrinting.Serialization +{ + public class Serializer + { + private const BindingFlags SerializingMembersFlag = BindingFlags.Public | BindingFlags.Instance; + private readonly HashSet complexObjectLinks = new HashSet(); + private readonly HashSet excludedMembers; + private readonly HashSet excludedTypes; + + private readonly HashSet finalTypes = new HashSet + { + typeof(int), typeof(double), typeof(float), typeof(string), typeof(DateTime), typeof(TimeSpan), typeof(Guid), + typeof(long), typeof(decimal), typeof(char), typeof(byte), typeof(bool), typeof(short) + }; + + private readonly Func handleMaxRecursion; + + private readonly int maxRecursion; + private readonly Dictionary membersSerializesInfos; + private readonly Dictionary typeSerializesInfos; + + public Serializer( + HashSet excludedMembers, + HashSet excludedTypes, + Dictionary membersSerializesInfos, + Dictionary typeSerializesInfos, + int maxRecursion, + Func handleMaxRecursion) + { + this.typeSerializesInfos = typeSerializesInfos; + this.excludedMembers = excludedMembers; + this.excludedTypes = excludedTypes; + this.membersSerializesInfos = membersSerializesInfos; + this.maxRecursion = maxRecursion; + this.handleMaxRecursion = handleMaxRecursion; + } + + public string Serialize(object obj, int nestingLevel) + { + if (obj == null) + return $"null{Environment.NewLine}"; + + if (finalTypes.Contains(obj.GetType())) + return typeSerializesInfos.TryGetValue(obj.GetType(), out var typeSerialization1) + ? typeSerialization1.SerializationFunc(obj) + Environment.NewLine + : obj + Environment.NewLine; + + if (complexObjectLinks.Contains(obj)) + { + if (handleMaxRecursion != null) + return handleMaxRecursion(obj) + Environment.NewLine; + + return $"Maximum recursion has been reached{Environment.NewLine}"; + } + + complexObjectLinks.Add(obj); + + if (typeSerializesInfos.TryGetValue(obj.GetType(), out var typeSerialization)) + { + var result = typeSerialization.SerializationFunc(obj); + complexObjectLinks.Remove(obj); + return result + Environment.NewLine; + } + + if (obj is ICollection collection) + { + var result = SerializeCollection(collection, nestingLevel); + complexObjectLinks.Remove(obj); + return result; + } + + var type = obj.GetType(); + var sb = new StringBuilder().AppendLine(type.Name); + var indentation = string.Intern(new string('\t', nestingLevel + 1)); + HandleMembers(type, sb, indentation, obj, nestingLevel); + + complexObjectLinks.Remove(obj); + return sb.ToString(); + } + + private void HandleMembers(Type type, StringBuilder sb, string indentation, object obj, int nestingLevel) + { + foreach (var propertyInfo in type.GetProperties(SerializingMembersFlag)) + { + var data = new DataMember(propertyInfo); + var serializedValue = HandleMember(data, obj, indentation, nestingLevel); + if (serializedValue != null) + sb.Append(serializedValue); + } + + foreach (var fieldInfo in type.GetFields(SerializingMembersFlag)) + { + var data = new DataMember(fieldInfo); + var serializedValue = HandleMember(data, obj, indentation, nestingLevel); + if (serializedValue != null) + sb.Append(serializedValue); + } + } + + private string HandleMember(DataMember member, object obj, string indentation, + int nestingLevel) + { + if (excludedMembers.Any(memberInfo => memberInfo.Name == member.Name) || + excludedTypes.Contains(member.Type)) + return null; + + if (membersSerializesInfos.TryGetValue(member.MemberInfo, out var memberSerialization)) + return GetSerializedString(obj, member, indentation, memberSerialization.SerializationFunc); + + return GetSerializedString( + obj, + member, + indentation, + value => Serialize( + value, + nestingLevel + 1), + false); + } + + private string SerializeCollection(ICollection collection, int nestingLevel) + { + if (collection == null) + return $"null{Environment.NewLine}"; + + var sb = new StringBuilder(); + sb.AppendLine(collection.GetType().Name); + + var indentation = GetIndentation(nestingLevel + 1); + foreach (var element in collection) + { + sb.Append(indentation); + sb.Append(Serialize(element, nestingLevel + 1)); + } + + return sb.ToString(); + } + + private string GetIndentation(int nestingLevel) + { + return string.Intern(new string('\t', nestingLevel)); + } + + private string GetSerializedString( + object obj, + DataMember memberInfo, + string indentation, + Func serializeMember, + bool needNewLine = true) + { + var serializedString = serializeMember(memberInfo.GetValue(obj)); + var stringEnd = needNewLine ? Environment.NewLine : string.Empty; + + return $"{indentation}{memberInfo.Name} = {serializedString}{stringEnd}"; + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/Serialization/StringHelper.cs b/ObjectPrinting/Serialization/StringHelper.cs new file mode 100644 index 00000000..bbd1d8eb --- /dev/null +++ b/ObjectPrinting/Serialization/StringHelper.cs @@ -0,0 +1,15 @@ +namespace ObjectPrinting.Serialization +{ + public static class StringHelper + { + public static string Trim(string value, int length) + { + if (value == null) + return null; + + return value.Length <= length + ? value + : value[..length]; + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/Serialization/UsingExtensions.cs b/ObjectPrinting/Serialization/UsingExtensions.cs new file mode 100644 index 00000000..31641ab7 --- /dev/null +++ b/ObjectPrinting/Serialization/UsingExtensions.cs @@ -0,0 +1,25 @@ +using System; +using System.Globalization; + +namespace ObjectPrinting.Serialization +{ + public static class UsingExtensions + { + public static IWrap Using( + this IUsing @using, + CultureInfo culture) + where TSerialization : IFormattable + { + return @using.Using( + p => p.ToString(null, culture)); + } + + public static IWrap Trim( + this IUsing @using, + int length) + + { + return @using.Using(value => StringHelper.Trim(value, length)); + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/Serialization/WrapExtensions.cs b/ObjectPrinting/Serialization/WrapExtensions.cs new file mode 100644 index 00000000..6d23cabf --- /dev/null +++ b/ObjectPrinting/Serialization/WrapExtensions.cs @@ -0,0 +1,12 @@ +namespace ObjectPrinting.Serialization +{ + public static class WrapExtensions + { + public static IWrap Trim( + this IWrap wrap, + int length) + { + return wrap.Wrap(value => StringHelper.Trim(value, length)); + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs deleted file mode 100644 index 4c8b2445..00000000 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -using NUnit.Framework; - -namespace ObjectPrinting.Tests -{ - [TestFixture] - public class ObjectPrinterAcceptanceTests - { - [Test] - public void Demo() - { - var person = new Person { Name = "Alex", Age = 19 }; - - var printer = ObjectPrinter.For(); - //1. Исключить из сериализации свойства определенного типа - //2. Указать альтернативный способ сериализации для определенного типа - //3. Для числовых типов указать культуру - //4. Настроить сериализацию конкретного свойства - //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - //6. Исключить из сериализации конкретного свойства - - string s1 = printer.PrintToString(person); - - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - //8. ...с конфигурированием - } - } -} \ No newline at end of file diff --git a/ObjectPrintingTests/ObjectPrinterAcceptanceTests.cs b/ObjectPrintingTests/ObjectPrinterAcceptanceTests.cs new file mode 100644 index 00000000..ae9fc4ff --- /dev/null +++ b/ObjectPrintingTests/ObjectPrinterAcceptanceTests.cs @@ -0,0 +1,36 @@ +using System; +using System.Globalization; +using FluentAssertions; +using NUnit.Framework; +using ObjectPrinting; +using ObjectPrinting.Serialization; +using ObjectPrintingTests.TestHelpers; + +namespace ObjectPrintingTests +{ + [TestFixture] + public class ObjectPrinterAcceptanceTests + { + [Test] + public void Demo() + { + var person = new Person { Name = "Alex", Age = 19, Height = 180.5 }; + + var printer = ObjectPrinter.For(); + + var actualString = printer.Exclude() + .Printing(p => p.Age) + .Using(age => (age + 1000).ToString()) + .Wrap(p => p + "1") + .And.Printing() + .Using(CultureInfo.InvariantCulture) + .And.Printing(p => p.Name) + .Trim(1) + .And.Exclude(p => p.PublicField) + .OnMaxRecursion((_) => throw new ArgumentException()) + .PrintToString(person); + + actualString.Should().Be($"Person{Environment.NewLine}\tId = 00000000-0000-0000-0000-000000000000{Environment.NewLine}\tName = A{Environment.NewLine}\tHeight = 180.5{Environment.NewLine}\tAge = 10191{Environment.NewLine}"); + } + } +} \ No newline at end of file diff --git a/ObjectPrintingTests/ObjectPrintingTest.cs b/ObjectPrintingTests/ObjectPrintingTest.cs new file mode 100644 index 00000000..a7dfa484 --- /dev/null +++ b/ObjectPrintingTests/ObjectPrintingTest.cs @@ -0,0 +1,369 @@ +using NUnit.Framework; +using FluentAssertions; +using ObjectPrinting; +using System.Globalization; +using ObjectPrinter = ObjectPrinting.ObjectPrinter; +using System; +using System.Collections.Generic; +using ObjectPrintingTests.TestHelpers; +using ObjectPrinting.Serialization; +using ObjectPrinting.Solved.Tests; +using Person = ObjectPrintingTests.TestHelpers.Person; + +namespace ObjectPrintingTests +{ + public class ObjectPrintingTest + { + [Test] + public void WhenPassComplexObject_ShouldReturnCorrectSerializeString() + { + var person = new Person(); + + var printer = person.CreatePrinter(); + + var actual = printer.PrintToString(person); + + actual.Should().Be($"Person{Environment.NewLine}\tId = 00000000-0000-0000-0000-000000000000{Environment.NewLine}\tName = null{Environment.NewLine}\tHeight = 0{Environment.NewLine}\tAge = 0{Environment.NewLine}\tSubPerson = null{Environment.NewLine}\tPublicField = null{Environment.NewLine}"); + } + + [Test] + public void WhenReachedMaxRecursion_ShouldThrowException() + { + var person = new Person { Name = "Alex", Age = 19, Height = 180.5, SubPerson = new SubPerson() }; + person.SubPerson.Age = 15; + person.SubPerson.Person = person; + + var printer = ObjectPrinter.For(); + + printer + .OnMaxRecursion(_ => throw new ArgumentException()); + + Action act = () => { printer.PrintToString(person); }; + + act.Should().Throw(); + } + + [Test] + public void WhenAreNoCircularLinks_ShouldReturnCorrectAnswer() + { + var subPerson = new SubPerson(); + var person = new Person() {SubPerson = subPerson}; + + var parent = new Parent() + { + Person = person, + SubPerson = subPerson + }; + + var actual = parent + .CreatePrinter() + .OnMaxRecursion((_) => "РЕКУРСИЯ") + .PrintToString(parent); + + actual.Should().Be($"Parent{Environment.NewLine}\tPerson = Person{Environment.NewLine}\t\tId = 00000000-0000-0000-0000-000000000000{Environment.NewLine}\t\tName = null{Environment.NewLine}\t\tHeight = 0{Environment.NewLine}\t\tAge = 0{Environment.NewLine}\t\tSubPerson = SubPerson{Environment.NewLine}\t\t\tPerson = null{Environment.NewLine}\t\t\tAge = 0{Environment.NewLine}\t\tPublicField = null{Environment.NewLine}\tSubPerson = SubPerson{Environment.NewLine}\t\tPerson = null{Environment.NewLine}\t\tAge = 0{Environment.NewLine}"); + } + + [Test] + public void WhenObjectReferenceToItself_ShouldReturnCorrectAnswer() + { + var currentObject = new SomethingObject(); + currentObject.ToSameObject = currentObject; + + var actual = currentObject + .CreatePrinter() + .OnMaxRecursion((_) => "РЕКУРСИЯ") + .PrintToString(currentObject); + + actual.Should().Be($"SomethingObject{Environment.NewLine}\tToSameObject = РЕКУРСИЯ{Environment.NewLine}\tCount = 1{Environment.NewLine}"); + } + + [Test] + public void WhenItsTypeSpecified_ShouldExcludeMember() + { + var person = new Person { Name = "Alex", Age = 19 }; + + var printer = ObjectPrinter.For(); + printer.Exclude(p => p.Age) + .Exclude(); + + var actual = printer.PrintToString(person); + + actual.Should().Be( + $"Person{Environment.NewLine}\tId = 00000000-0000-0000-0000-000000000000{Environment.NewLine}\tName = Alex{Environment.NewLine}\tSubPerson = null{Environment.NewLine}\tPublicField = null{Environment.NewLine}"); + } + + [Test] + public void WhenItsSpecifiedForType_ShouldUseTrimming() + { + var person = new Person { Name = "Petr", Age = 20, Height = 180 }; + var printer = ObjectPrinter.For(); + + var actual = printer + .Printing(p => p.Name) + .Trim(1) + .And.PrintToString(person); + + actual.Should().Be( + $"Person{Environment.NewLine}\tId = 00000000-0000-0000-0000-000000000000{Environment.NewLine}\tName = P{Environment.NewLine}\tHeight = 180{Environment.NewLine}\tAge = 20{Environment.NewLine}\tSubPerson = null{Environment.NewLine}\tPublicField = null{Environment.NewLine}"); + } + + [Test] + public void WithGivenFunc_ShouldSerializeMember() + { + var person = new Person { Name = "Petr", Age = 20, Height = 180 }; + var printer = ObjectPrinter.For(); + + var actual = printer + .Printing(p => p.Age) + .Using(age => (age + 1000).ToString()) + .And.PrintToString(person); + + actual.Should().Be( + $"Person{Environment.NewLine}\tId = 00000000-0000-0000-0000-000000000000{Environment.NewLine}\tName = Petr{Environment.NewLine}\tHeight = 180{Environment.NewLine}\tAge = 1020{Environment.NewLine}\tSubPerson = null{Environment.NewLine}\tPublicField = null{Environment.NewLine}"); + } + + [Test] + public void WhenPassCustomSetCulture_ShouldAddedCultureInfo() + { + var person = new Person { Name = "Petr", Age = 20, Height = 180.5 }; + var printer = ObjectPrinter.For(); + + var actual = printer + .Printing() + .Using(CultureInfo.InvariantCulture) + .And.PrintToString(person); + + actual.Should().Be( + $"Person{Environment.NewLine}\tId = 00000000-0000-0000-0000-000000000000{Environment.NewLine}\tName = Petr{Environment.NewLine}\tHeight = 180.5{Environment.NewLine}\tAge = 20{Environment.NewLine}\tSubPerson = null{Environment.NewLine}\tPublicField = null{Environment.NewLine}"); + } + + [Test] + public void WhenCyclicLinksWasFound_ShouldPrintWithRecursionLimit() + { + var person = new Person { Name = "Petr", Age = 20, Height = 180, SubPerson = new SubPerson() }; + person.SubPerson.Age = 15; + person.SubPerson.Person = person; + var printer = ObjectPrinter.For(); + + var actual = printer + .PrintToString(person); + + actual.Should().Be( + $"Person{Environment.NewLine}\tId = 00000000-0000-0000-0000-000000000000{Environment.NewLine}\tName = Petr{Environment.NewLine}\tHeight = 180{Environment.NewLine}\tAge = 20{Environment.NewLine}\tSubPerson = SubPerson{Environment.NewLine}\t\tPerson = Maximum recursion has been reached{Environment.NewLine}\t\tAge = 15{Environment.NewLine}\tPublicField = null{Environment.NewLine}"); + } + + [Test] + public void WhenPassNull_ShouldReturnNullInString() + { + var printer = ObjectPrinter.For(); + var actual = printer.PrintToString(null); + + actual.Should().Be($"null{Environment.NewLine}"); + } + + [Test] + public void WhenPassFinalType_ShouldReturnStringRepresentationOfThisType() + { + var printer = ObjectPrinter.For(); + var actual = printer.PrintToString(1); + + actual.Should().Be($"1{Environment.NewLine}"); + } + + [Test] + public void WhenDoCustomSerializeAndTrimString_ShouldReturnCorrectResult() + { + var person = new Person { Name = "Alex", Age = 19, Height = 160 }; + var printer = ObjectPrinter.For(); + + var actual = printer + .Printing(p => p.Name) + .Using(n => n + ":))") + .Trim(6) + .And.PrintToString(person); + + actual.Should().Be($"Person{Environment.NewLine}\tId = 00000000-0000-0000-0000-000000000000{Environment.NewLine}\tName = Alex:){Environment.NewLine}\tHeight = 160{Environment.NewLine}\tAge = 19{Environment.NewLine}\tSubPerson = null{Environment.NewLine}\tPublicField = null{Environment.NewLine}"); + } + + [Test] + public void WhenFieldIsList_ShouldReturnCorrectCollectionSerialization() + { + var collections = new CollectionsKeeper(); + collections.stringsList = new List() { "один", "два" }; + + var printer = collections.CreatePrinter(); + + var actual = printer + .PrintToString(collections); + + actual.Should().Be($"CollectionsKeeper{Environment.NewLine}\tstringsList = List`1{Environment.NewLine}\t\tодин{Environment.NewLine}\t\tдва{Environment.NewLine}\tintsArray = null{Environment.NewLine}\tdictionary = null{Environment.NewLine}"); + } + + [Test] + public void WhenFieldIsArray_ShouldReturnCorrectCollectionSerialization() + { + var collections = new CollectionsKeeper(); + collections.intsArray = new int[] { 1, 2, 3 }; + + var printer = collections.CreatePrinter(); + + var actual = printer + .PrintToString(collections); + + actual.Should().Be($"CollectionsKeeper{Environment.NewLine}\tstringsList = null{Environment.NewLine}\tintsArray = Int32[]{Environment.NewLine}\t\t1{Environment.NewLine}\t\t2{Environment.NewLine}\t\t3{Environment.NewLine}\tdictionary = null{Environment.NewLine}"); + } + + [Test] + public void WhenFieldIsDictionary_ShouldReturnCorrectCollectionSerialization() + { + var collections = new CollectionsKeeper(); + collections.dictionary = new Dictionary() { {1, "один"} , { 2, "два" }}; + + var printer = collections.CreatePrinter(); + + var actual = printer + .PrintToString(collections); + + actual.Should().Be($"CollectionsKeeper{Environment.NewLine}\tstringsList = null{Environment.NewLine}\tintsArray = null{Environment.NewLine}\tdictionary = Dictionary`2{Environment.NewLine}\t\tKeyValuePair`2{Environment.NewLine}\t\t\tKey = 1{Environment.NewLine}\t\t\tValue = один{Environment.NewLine}\t\tKeyValuePair`2{Environment.NewLine}\t\t\tKey = 2{Environment.NewLine}\t\t\tValue = два{Environment.NewLine}"); + } + + [Test] + public void WhenFieldIsIEnumerable_ShouldReturnCorrectCollectionSerialization() + { + IEnumerable enumerable = new[] { 1,2 }; + + var printer = enumerable.CreatePrinter(); + + var actual = printer + .PrintToString(enumerable); + + actual.Should().Be($"Int32[]{Environment.NewLine}\t1{Environment.NewLine}\t2{Environment.NewLine}"); + } + + [Test] + public void WhenFieldIsComplexObject_ShouldReturnCorrectCollectionSerialization() + { + var person = new Person(); + person.SubPerson = new SubPerson(); + + var printer = person.CreatePrinter(); + + var actual = printer + .PrintToString(person); + + actual.Should().Be($"Person{Environment.NewLine}\tId = 00000000-0000-0000-0000-000000000000{Environment.NewLine}\tName = null{Environment.NewLine}\tHeight = 0{Environment.NewLine}\tAge = 0{Environment.NewLine}\tSubPerson = SubPerson{Environment.NewLine}\t\tPerson = null{Environment.NewLine}\t\tAge = 0{Environment.NewLine}\tPublicField = null{Environment.NewLine}"); + } + + [Test] + public void WhenCollectionElementIsComplexObject_ShouldReturnCorrectCollectionSerialization() + { + var listWithComplexObject = new List () { new Person() }; + + var printer = listWithComplexObject.CreatePrinter(); + + var actual = printer + .PrintToString(listWithComplexObject); + + actual.Should().Be($"List`1{Environment.NewLine}\tPerson{Environment.NewLine}\t\tId = 00000000-0000-0000-0000-000000000000{Environment.NewLine}\t\tName = null{Environment.NewLine}\t\tHeight = 0{Environment.NewLine}\t\tAge = 0{Environment.NewLine}\t\tSubPerson = null{Environment.NewLine}\t\tPublicField = null{Environment.NewLine}"); + } + + [Test] + public void WhenPassDictionaryToPrinter_ShouldReturnThisCollectionInString() + { + var dict = new Dictionary{{1, "один"} }; + + var printer = dict.CreatePrinter(); + + var actual = printer.PrintToString(dict); + + actual.Should().Be($"Dictionary`2{Environment.NewLine}\tKeyValuePair`2{Environment.NewLine}\t\tKey = 1{Environment.NewLine}\t\tValue = один{Environment.NewLine}"); + } + + [Test] + public void WhenPassListToPrinter_ShouldReturnThisCollectionInString() + { + var list = new List{1.7f, 6.5f}; + + var printer = list.CreatePrinter(); + + var actual = printer.PrintToString(list); + + actual.Should().Be($"List`1{Environment.NewLine}\t1,7{Environment.NewLine}\t6,5{Environment.NewLine}"); + } + + [Test] + public void WhenToSameObjectInTwoFields_ShouldReturnCorrectResultWithoutRecursion() + { + var test = new Test(); + var obj = new SomethingObject(); + + test.ToSameObject = obj; + test.ToSameObject2 = obj; + + var printer = test.CreatePrinter(); + + var actual = printer.PrintToString(test); + + actual.Should().Be($"Test{Environment.NewLine}\tToSameObject = SomethingObject{Environment.NewLine}\t\tToSameObject = null{Environment.NewLine}\t\tCount = 1{Environment.NewLine}\tToSameObject2 = SomethingObject{Environment.NewLine}\t\tToSameObject = null{Environment.NewLine}\t\tCount = 1{Environment.NewLine}"); + } + + [Test] + public void WhenPassEmptyCollection_ShouldPrintOnlyCollectionType() + { + var list = new List(); + + var printer = list.CreatePrinter(); + + var actual = printer.PrintToString(list); + + actual.Should().Be($"List`1{Environment.NewLine}"); + + } + + [Test] + public void WhenPassFinalTypeWithCustomSerialization_ShouldReturnCustomSerializationFuncResultString() + { + var currentString = new string("abcd"); + + var printer = currentString.CreatePrinter(); + + var actual = printer + .Printing() + .Trim(3) + .And.PrintToString(currentString); + + actual.Should().Be($"abc{Environment.NewLine}"); + } + + [Test] + public void WhenPassNullToUsing_ShouldThrowNullReferenceExceprion() + { + var currentString = "abc"; + var printer = currentString.CreatePrinter(); + + Action actual = () => { printer + .Printing() + .Using(null) + .And.PrintToString(currentString); }; + + actual.Should().Throw(); + } + + [Test] + public void WhenPassNullToWrap_ShouldThrowNullReferenceExceprion() + { + var currentString = "abc"; + var printer = currentString.CreatePrinter(); + + Action actual = () => { + printer + .Printing() + .Using(p => currentString + 1) + .Wrap(null) + .And.PrintToString(currentString); + }; + + actual.Should().Throw(); + } + } +} diff --git a/ObjectPrintingTests/ObjectPrintingTests.csproj b/ObjectPrintingTests/ObjectPrintingTests.csproj new file mode 100644 index 00000000..8ebd2e8a --- /dev/null +++ b/ObjectPrintingTests/ObjectPrintingTests.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + + + diff --git a/ObjectPrintingTests/PrinterExtensionsTests.cs b/ObjectPrintingTests/PrinterExtensionsTests.cs new file mode 100644 index 00000000..27c5c659 --- /dev/null +++ b/ObjectPrintingTests/PrinterExtensionsTests.cs @@ -0,0 +1,19 @@ +using FluentAssertions; +using NUnit.Framework; +using ObjectPrinting; +using ObjectPrinting.Solved.Tests; + +namespace ObjectPrintingTests +{ + internal class PrinterExtensionsTests + { + [Test] + public void WhenCreatePrinterWithExtensionMethod_ShouldNotBeNull() + { + var person = new Person(); + var printer = person.CreatePrinter(); + + printer.Should().NotBe(null); + } + } +} \ No newline at end of file diff --git a/ObjectPrintingTests/TestHelpers/CollectionsKeeper.cs b/ObjectPrintingTests/TestHelpers/CollectionsKeeper.cs new file mode 100644 index 00000000..a3ddc8bb --- /dev/null +++ b/ObjectPrintingTests/TestHelpers/CollectionsKeeper.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace ObjectPrintingTests.TestHelpers +{ + public class CollectionsKeeper + { + public List stringsList; + + public int[] intsArray; + + public Dictionary dictionary; + } +} \ No newline at end of file diff --git a/ObjectPrintingTests/TestHelpers/Parent.cs b/ObjectPrintingTests/TestHelpers/Parent.cs new file mode 100644 index 00000000..6afee895 --- /dev/null +++ b/ObjectPrintingTests/TestHelpers/Parent.cs @@ -0,0 +1,8 @@ +namespace ObjectPrintingTests.TestHelpers +{ + public class Parent + { + public Person Person { get; set; } + public SubPerson SubPerson { get; set; } + } +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrintingTests/TestHelpers/Person.cs similarity index 56% rename from ObjectPrinting/Tests/Person.cs rename to ObjectPrintingTests/TestHelpers/Person.cs index f9555955..b0e362ea 100644 --- a/ObjectPrinting/Tests/Person.cs +++ b/ObjectPrintingTests/TestHelpers/Person.cs @@ -1,6 +1,6 @@ using System; -namespace ObjectPrinting.Tests +namespace ObjectPrintingTests.TestHelpers { public class Person { @@ -8,5 +8,9 @@ public class Person public string Name { get; set; } public double Height { get; set; } public int Age { get; set; } + public SubPerson SubPerson { get; set; } + + public string PublicField; + private string privateField; } } \ No newline at end of file diff --git a/ObjectPrintingTests/TestHelpers/SomethingObject.cs b/ObjectPrintingTests/TestHelpers/SomethingObject.cs new file mode 100644 index 00000000..781ad9d5 --- /dev/null +++ b/ObjectPrintingTests/TestHelpers/SomethingObject.cs @@ -0,0 +1,8 @@ +namespace ObjectPrintingTests.TestHelpers +{ + public class SomethingObject + { + public SomethingObject ToSameObject { get; set; } + public int Count = 1; + } +} \ No newline at end of file diff --git a/ObjectPrintingTests/TestHelpers/SubPerson.cs b/ObjectPrintingTests/TestHelpers/SubPerson.cs new file mode 100644 index 00000000..2d656e23 --- /dev/null +++ b/ObjectPrintingTests/TestHelpers/SubPerson.cs @@ -0,0 +1,9 @@ +namespace ObjectPrintingTests.TestHelpers +{ + public class SubPerson + { + public Person Person { get; set; } + + public int Age { get; set; } + } +} \ No newline at end of file diff --git a/ObjectPrintingTests/TestHelpers/Test.cs b/ObjectPrintingTests/TestHelpers/Test.cs new file mode 100644 index 00000000..09344e55 --- /dev/null +++ b/ObjectPrintingTests/TestHelpers/Test.cs @@ -0,0 +1,8 @@ +namespace ObjectPrintingTests.TestHelpers +{ + public class Test + { + public SomethingObject ToSameObject { get; set; } + public SomethingObject ToSameObject2 { get; set; } + } +} \ No newline at end of file diff --git a/fluent-api.sln b/fluent-api.sln index 69c8db9e..c6377338 100644 --- a/fluent-api.sln +++ b/fluent-api.sln @@ -1,17 +1,19 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25123.0 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34330.188 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObjectPrinting", "ObjectPrinting\ObjectPrinting.csproj", "{07B8C9B7-8289-46CB-9875-048A57758EEE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ObjectPrinting", "ObjectPrinting\ObjectPrinting.csproj", "{07B8C9B7-8289-46CB-9875-048A57758EEE}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{6D308E4A-CEC7-4536-9B87-81CD337A87AD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentMapping", "Samples\FluentMapper\FluentMapping.csproj", "{FEEA5AFE-459A-4D13-81D0-252E1A2E6F4E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentMapping", "Samples\FluentMapper\FluentMapping.csproj", "{FEEA5AFE-459A-4D13-81D0-252E1A2E6F4E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentMapping.Tests", "Samples\FluentMapper.Tests\FluentMapping.Tests.csproj", "{8A7BB3EA-3E6A-4D04-A801-D5CD1620DA0D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentMapping.Tests", "Samples\FluentMapper.Tests\FluentMapping.Tests.csproj", "{8A7BB3EA-3E6A-4D04-A801-D5CD1620DA0D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spectacle", "Samples\Spectacle\Spectacle.csproj", "{EFA9335C-411B-4597-B0B6-5438D1AE04C3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectacle", "Samples\Spectacle\Spectacle.csproj", "{EFA9335C-411B-4597-B0B6-5438D1AE04C3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObjectPrintingTests", "ObjectPrintingTests\ObjectPrintingTests.csproj", "{D4C55796-285B-43A6-B0C8-CCAFD9311A7E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -35,6 +37,10 @@ Global {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Debug|Any CPU.Build.0 = Debug|Any CPU {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Release|Any CPU.ActiveCfg = Release|Any CPU {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Release|Any CPU.Build.0 = Release|Any CPU + {D4C55796-285B-43A6-B0C8-CCAFD9311A7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D4C55796-285B-43A6-B0C8-CCAFD9311A7E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4C55796-285B-43A6-B0C8-CCAFD9311A7E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D4C55796-285B-43A6-B0C8-CCAFD9311A7E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -44,4 +50,7 @@ Global {8A7BB3EA-3E6A-4D04-A801-D5CD1620DA0D} = {6D308E4A-CEC7-4536-9B87-81CD337A87AD} {EFA9335C-411B-4597-B0B6-5438D1AE04C3} = {6D308E4A-CEC7-4536-9B87-81CD337A87AD} EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A5E3DAB2-51C4-45F2-8C29-BD9CE58F6479} + EndGlobalSection EndGlobal