diff --git a/ObjectPrinting/Extensions/EnumerableExtensions.cs b/ObjectPrinting/Extensions/EnumerableExtensions.cs new file mode 100644 index 00000000..0c6bf03e --- /dev/null +++ b/ObjectPrinting/Extensions/EnumerableExtensions.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Text; + +namespace ObjectPrinting.Extensions; + +public static class EnumerableExtensions +{ + public static string ObjectPrint( + this IEnumerable enumerable, + Serializer serializer, + ImmutableList previous) + { + + previous = previous.Add(enumerable); + + return enumerable.ObjectPrintingSerialize( + "[", "]", + obj => $"{serializer.PrintToString(obj, previous)},", + previous.Count); + } + + public static string ObjectPrintingSerialize( + this IEnumerable enumerable, + string startSymbol, + string endSymbol, + Func elementSerializer, + int indentationLevel) + { + var sb = new StringBuilder(); + sb.AppendLine(startSymbol); + + var indentation = new string('\t', indentationLevel); + + foreach (var obj in enumerable) + { + sb.Append(indentation); + sb.AppendLine(elementSerializer(obj)); + } + + sb.Append(new string('\t', indentationLevel - 1)); + sb.Append(endSymbol); + + return sb.ToString(); + } +} \ No newline at end of file diff --git a/ObjectPrinting/Extensions/IDictionaryExtensions.cs b/ObjectPrinting/Extensions/IDictionaryExtensions.cs new file mode 100644 index 00000000..dc074a60 --- /dev/null +++ b/ObjectPrinting/Extensions/IDictionaryExtensions.cs @@ -0,0 +1,29 @@ +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace ObjectPrinting.Extensions; + +public static class DictionaryExtensions +{ + public static string ObjectPrint( + this IDictionary dictionary, + Serializer serializer, + ImmutableList previous) + { + previous = previous.Add(dictionary); + + return dictionary + .CastToDictionaryEntries() + .ObjectPrintingSerialize( + "{", "}", + pair => $"{pair.Key}: {serializer.PrintToString(pair.Value, previous)};", + previous.Count); + } + + private static IEnumerable CastToDictionaryEntries(this IDictionary dictionary) + { + foreach (DictionaryEntry entry in dictionary) + yield return entry; + } +} \ No newline at end of file diff --git a/ObjectPrinting/Extensions/ObjectExtensions.cs b/ObjectPrinting/Extensions/ObjectExtensions.cs new file mode 100644 index 00000000..dd89946b --- /dev/null +++ b/ObjectPrinting/Extensions/ObjectExtensions.cs @@ -0,0 +1,14 @@ +namespace ObjectPrinting.Extensions; + +public static class ObjectExtensions +{ + public static string PrintToString(this T obj, PrintingConfig printingConfig) + { + return new Serializer(printingConfig).PrintToString(obj); + } + + public static string PrintToString(this T obj) + { + return new Serializer(ObjectPrinter.For()).PrintToString(obj); + } +} \ No newline at end of file diff --git a/ObjectPrinting/Interfaces/IPropertySpecifier.cs b/ObjectPrinting/Interfaces/IPropertySpecifier.cs new file mode 100644 index 00000000..93c4636e --- /dev/null +++ b/ObjectPrinting/Interfaces/IPropertySpecifier.cs @@ -0,0 +1,9 @@ +using System; + +namespace ObjectPrinting.Interfaces +{ + public interface IPropertySpecifier + { + public PrintingConfig With(Func serializer); + } +} \ No newline at end of file diff --git a/ObjectPrinting/Interfaces/ISerializer.cs b/ObjectPrinting/Interfaces/ISerializer.cs new file mode 100644 index 00000000..072607bf --- /dev/null +++ b/ObjectPrinting/Interfaces/ISerializer.cs @@ -0,0 +1,9 @@ +using System.Collections.Immutable; + +namespace ObjectPrinting.Interfaces; + +public interface ISerializer +{ + public string PrintToString(object obj, ImmutableList previous); + public string PrintToString(T obj); +} \ No newline at end of file diff --git a/ObjectPrinting/Interfaces/ISerializerSetter.cs b/ObjectPrinting/Interfaces/ISerializerSetter.cs new file mode 100644 index 00000000..2a8fa214 --- /dev/null +++ b/ObjectPrinting/Interfaces/ISerializerSetter.cs @@ -0,0 +1,10 @@ +using System; +using System.Reflection; + +namespace ObjectPrinting.Interfaces +{ + public interface ISerializerSetter + { + public void SetSerializer(Func serializer, PropertyInfo propertyInfo); + } +} \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinting.csproj b/ObjectPrinting/ObjectPrinting.csproj index 1c5eaf1c..b871c4c9 100644 --- a/ObjectPrinting/ObjectPrinting.csproj +++ b/ObjectPrinting/ObjectPrinting.csproj @@ -1,14 +1,9 @@  - 8 + latest netcoreapp3.1 false - - - - - diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index a9e08211..ec61e90d 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,41 +1,108 @@ using System; -using System.Linq; -using System.Text; +using System.Collections.Generic; +using System.Globalization; +using System.Linq.Expressions; +using System.Reflection; +using ObjectPrinting.Interfaces; -namespace ObjectPrinting +namespace ObjectPrinting; + +public class PrintingConfig : ISerializerSetter { - public class PrintingConfig - { - public string PrintToString(TOwner obj) - { - return PrintToString(obj, 0); - } - - 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(); - } + private readonly Dictionary> typeSerializers = new(); + private readonly Dictionary> propertySerializers = new(); + + private readonly HashSet excludedProps = new(); + private readonly HashSet excludedTypes = new(); + + public PrintingConfig Exclude() + { + excludedTypes.Add(typeof(T)); + return this; + } + + public PrintingConfig Exclude(Expression> expression) + { + excludedProps.Add(GetPropertyInfoFromExpression(expression)); + return this; + } + + public IPropertySpecifier Serialize() + { + return new PropertySpecifier(this); + } + + public IPropertySpecifier Serialize(Expression> expression) + { + return new PropertySpecifier(this, GetPropertyInfoFromExpression(expression)); + } + + public PrintingConfig SetCulture(CultureInfo cultureInfo) + where T: IFormattable + { + return Serialize() + .With(obj => string.Format(cultureInfo, "{0}", obj)); + } + + public PrintingConfig SetCulture(Expression> expression, CultureInfo cultureInfo) + where T: IFormattable + { + return Serialize(expression) + .With(obj => string.Format(cultureInfo, "{0}", obj)); + } + + public PrintingConfig SliceStrings(int maxLength) + { + if (maxLength <= 0) + throw new ArgumentException("Parameter maxLength must be positive"); + + return Serialize() + .With(s => maxLength >= s.Length ? s : s[..maxLength]); + } + + void ISerializerSetter.SetSerializer(Func serializer, PropertyInfo propertyInfo) + { + if (propertyInfo is not null) + propertySerializers[propertyInfo] = obj => serializer((T) obj); + else + typeSerializers[typeof(T)] = obj => serializer((T) obj); + } + + internal bool TrySerializeValueType(Type type, object obj, out string serialized) + { + serialized = ""; + + if (obj is not string && !type.IsValueType) + return false; + + serialized = typeSerializers.TryGetValue(type, out var serializer) + ? serializer(obj) + : obj.ToString(); + + return true; + } + + internal bool IsExcluded(PropertyInfo propertyInfo) + { + return excludedProps.Contains(propertyInfo) || excludedTypes.Contains(propertyInfo.PropertyType); + } + + internal bool TrySerializeProperty(object obj, PropertyInfo propertyInfo, out string serialized) + { + serialized = ""; + + if (!propertySerializers.TryGetValue(propertyInfo, out var serializer)) + return false; + + serialized = serializer(propertyInfo.GetValue(obj)); + return true; + } + + private static PropertyInfo GetPropertyInfoFromExpression(Expression> expression) + { + if (expression.Body is not MemberExpression memberExpression) + throw new ArgumentException($"Expression {expression} is not MemberExpression"); + + return memberExpression.Member as PropertyInfo; } } \ No newline at end of file diff --git a/ObjectPrinting/PropertySpecifier.cs b/ObjectPrinting/PropertySpecifier.cs new file mode 100644 index 00000000..143359c1 --- /dev/null +++ b/ObjectPrinting/PropertySpecifier.cs @@ -0,0 +1,30 @@ +using System; +using System.Reflection; +using ObjectPrinting.Interfaces; + +namespace ObjectPrinting +{ + public class PropertySpecifier : IPropertySpecifier + { + private PrintingConfig PrintingConfig { get; } + private readonly PropertyInfo propertyInfo; + + public PropertySpecifier(PrintingConfig printingConfig, PropertyInfo propertyInfo) + { + PrintingConfig = printingConfig; + this.propertyInfo = propertyInfo; + } + + public PropertySpecifier(PrintingConfig printingConfig) + { + PrintingConfig = printingConfig; + } + + public PrintingConfig With(Func serializer) + { + ((ISerializerSetter)PrintingConfig).SetSerializer(serializer, propertyInfo); + + return PrintingConfig; + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/Serializer.cs b/ObjectPrinting/Serializer.cs new file mode 100644 index 00000000..d35ab55b --- /dev/null +++ b/ObjectPrinting/Serializer.cs @@ -0,0 +1,74 @@ +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection; +using System.Text; +using ObjectPrinting.Extensions; +using ObjectPrinting.Interfaces; + +namespace ObjectPrinting; + +public class Serializer : ISerializer +{ + private readonly PrintingConfig printingConfig; + + public Serializer(PrintingConfig printingConfig) + { + this.printingConfig = printingConfig; + } + + public string PrintToString(T obj) + { + return PrintToString(obj, new List().ToImmutableList()); + } + + public string PrintToString(object obj, ImmutableList previous) + { + if (obj == null) + return "null"; + + var type = obj.GetType(); + + if (printingConfig.TrySerializeValueType(type, obj, out var serializedValue)) + return serializedValue; + + if (IsCyclic(obj, previous)) + return "cyclic link"; + + if (obj is IDictionary dictionary) + return dictionary.ObjectPrint(this, previous); + + if (obj is IEnumerable enumerable) + return enumerable.Cast().ObjectPrint(this, previous); + + var indentation = new string('\t', previous.Count + 1); + previous = previous.Add(obj); + var sb = new StringBuilder(); + sb.AppendLine(type.Name + " ("); + + foreach (var property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public)) + { + if (printingConfig.IsExcluded(property)) + continue; + + sb.Append($"{indentation}{property.Name}: "); + + if (printingConfig.TrySerializeProperty(obj, property, out var serialized)) + sb.Append(serialized); + else + sb.Append(PrintToString(property.GetValue(obj), previous)); + + sb.AppendLine(";"); + } + + sb.Append(new string('\t', previous.Count - 1) + ")"); + + return sb.ToString(); + } + + private static bool IsCyclic(object current, IEnumerable previous) + { + return previous.Any(e => ReferenceEquals(e, current)); + } +} \ No newline at end of file diff --git a/ObjectPrinting/Solved/ObjectExtensions.cs b/ObjectPrinting/Solved/ObjectExtensions.cs deleted file mode 100644 index b0c94553..00000000 --- a/ObjectPrinting/Solved/ObjectExtensions.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ObjectPrinting.Solved -{ - public static class ObjectExtensions - { - public static string PrintToString(this T obj) - { - return ObjectPrinter.For().PrintToString(obj); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/ObjectPrinter.cs b/ObjectPrinting/Solved/ObjectPrinter.cs deleted file mode 100644 index 540ee769..00000000 --- a/ObjectPrinting/Solved/ObjectPrinter.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ObjectPrinting.Solved -{ - public class ObjectPrinter - { - public static PrintingConfig For() - { - return new PrintingConfig(); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/PrintingConfig.cs b/ObjectPrinting/Solved/PrintingConfig.cs deleted file mode 100644 index 0ec5aeb2..00000000 --- a/ObjectPrinting/Solved/PrintingConfig.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Linq; -using System.Linq.Expressions; -using System.Text; - -namespace ObjectPrinting.Solved -{ - public class PrintingConfig - { - public PropertyPrintingConfig Printing() - { - return new PropertyPrintingConfig(this); - } - - public PropertyPrintingConfig Printing(Expression> memberSelector) - { - return new PropertyPrintingConfig(this); - } - - public PrintingConfig Excluding(Expression> memberSelector) - { - return this; - } - - internal PrintingConfig Excluding() - { - return this; - } - - public string PrintToString(TOwner obj) - { - return PrintToString(obj, 0); - } - - 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(); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/PropertyPrintingConfig.cs b/ObjectPrinting/Solved/PropertyPrintingConfig.cs deleted file mode 100644 index a509697d..00000000 --- a/ObjectPrinting/Solved/PropertyPrintingConfig.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Globalization; - -namespace ObjectPrinting.Solved -{ - public class PropertyPrintingConfig : IPropertyPrintingConfig - { - private readonly PrintingConfig printingConfig; - - public PropertyPrintingConfig(PrintingConfig printingConfig) - { - this.printingConfig = printingConfig; - } - - public PrintingConfig Using(Func print) - { - return printingConfig; - } - - public PrintingConfig Using(CultureInfo culture) - { - return printingConfig; - } - - PrintingConfig IPropertyPrintingConfig.ParentConfig => printingConfig; - } - - public interface IPropertyPrintingConfig - { - PrintingConfig ParentConfig { get; } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs b/ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs deleted file mode 100644 index dd392239..00000000 --- a/ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace ObjectPrinting.Solved -{ - public static class PropertyPrintingConfigExtensions - { - public static string PrintToString(this T obj, Func, PrintingConfig> config) - { - return config(ObjectPrinter.For()).PrintToString(obj); - } - - public static PrintingConfig TrimmedToLength(this PropertyPrintingConfig propConfig, int maxLen) - { - return ((IPropertyPrintingConfig)propConfig).ParentConfig; - } - - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Solved/Tests/ObjectPrinterAcceptanceTests.cs deleted file mode 100644 index ac52d5ee..00000000 --- a/ObjectPrinting/Solved/Tests/ObjectPrinterAcceptanceTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Globalization; -using NUnit.Framework; - -namespace ObjectPrinting.Solved.Tests -{ - [TestFixture] - public class ObjectPrinterAcceptanceTests - { - [Test] - public void Demo() - { - var person = new Person { Name = "Alex", Age = 19 }; - - var printer = ObjectPrinter.For() - //1. Исключить из сериализации свойства определенного типа - .Excluding() - //2. Указать альтернативный способ сериализации для определенного типа - .Printing().Using(i => i.ToString("X")) - //3. Для числовых типов указать культуру - .Printing().Using(CultureInfo.InvariantCulture) - //4. Настроить сериализацию конкретного свойства - //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - .Printing(p => p.Name).TrimmedToLength(10) - //6. Исключить из сериализации конкретного свойства - .Excluding(p => p.Age); - - string s1 = printer.PrintToString(person); - - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - string s2 = person.PrintToString(); - - //8. ...с конфигурированием - string s3 = person.PrintToString(s => s.Excluding(p => p.Age)); - Console.WriteLine(s1); - Console.WriteLine(s2); - Console.WriteLine(s3); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/Tests/Person.cs b/ObjectPrinting/Solved/Tests/Person.cs deleted file mode 100644 index 858ebbf8..00000000 --- a/ObjectPrinting/Solved/Tests/Person.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace ObjectPrinting.Solved.Tests -{ - public class Person - { - public Guid Id { get; set; } - public string Name { get; set; } - public double Height { get; set; } - public int Age { get; set; } - } -} \ 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/ObjectPrinting/Tests/Person.cs b/ObjectPrinting/Tests/Person.cs deleted file mode 100644 index f9555955..00000000 --- a/ObjectPrinting/Tests/Person.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace ObjectPrinting.Tests -{ - public class Person - { - public Guid Id { get; set; } - public string Name { get; set; } - public double Height { get; set; } - public int Age { get; set; } - } -} \ No newline at end of file diff --git a/ObjectPrintingTests/ObjectPrintingTests.cs b/ObjectPrintingTests/ObjectPrintingTests.cs new file mode 100644 index 00000000..3826b023 --- /dev/null +++ b/ObjectPrintingTests/ObjectPrintingTests.cs @@ -0,0 +1,235 @@ +using System.Drawing; +using System.Globalization; +using ObjectPrinting.Extensions; + +namespace ObjectPrintingTests; + +public class ObjectPrintingTests +{ + private Person person; + private Room room; + + [SetUp] + public void SetUp() + { + person = new Person + { + Name = "Frank", + Surname = "Sinatra", + Age = 19000000, + Id = new Guid(), + Height = 180.5, + Money = 300, + }; + + room = new Room + { + Width = 32.5, + Length = 48.6, + Height = 2.2, + }; + } + + [Test] + public void Exclude_ExcludesGivenTypes() + { + var config = ObjectPrinter.For() + .Exclude() + .Exclude() + .Exclude(); + + person.PrintToString(config) + .Should() + .Be("Person (\n\tHeight: 180,5;\n\tAge: 19000000;\n)"); + } + + [Test] + public void Exclude_ExcludesGivenProperties() + { + var config = ObjectPrinter.For() + .Exclude(p => p.Id) + .Exclude(p => p.Name) + .Exclude(p => p.Height); + + person.PrintToString(config) + .Should() + .Be("Person (\n\tSurname: Sinatra;\n\tAge: 19000000;\n\tMoney: 300;\n)"); + } + + [Test] + public void Select_SerializesSpecifiedTypes() + { + var config = ObjectPrinter.For() + .Serialize().With(num => Math.Ceiling(num).ToString()); + + room.PrintToString(config) + .Should() + .Be("Room (\n\tWidth: 33;\n\tLength: 49;\n\tHeight: 3;\n)"); + } + + [Test] + public void Select_SerializesSpecifiedProperties() + { + var config = ObjectPrinter.For() + .Serialize(r => r.Height).With(num => (num / 2).ToString()); + + room.PrintToString(config) + .Should() + .Be("Room (\n\tWidth: 32,5;\n\tLength: 48,6;\n\tHeight: 1,1;\n)"); + } + + [Test] + public void SetCulture_WorksWithGivenType() + { + var cultureObject = new CultureObject + { + DateTimeProp = new DateTime(2020, 01, 01), + DoubleProp = 22.22 + }; + + var config = ObjectPrinter.For() + .SetCulture(CultureInfo.InvariantCulture); + + cultureObject.PrintToString(config) + .Should() + .Be("CultureObject (\n\tDateTimeProp: 01/01/2020 00:00:00;\n\tDoubleProp: 22,22;\n)"); + } + + [Test] + public void SetCulture_WorksWithGivenProperty() + { + var cultureObject = new CultureObject + { + DateTimeProp = new DateTime(2020, 01, 01), + DoubleProp = 22.22 + }; + + var config = ObjectPrinter.For() + .SetCulture(c => c.DoubleProp, CultureInfo.InvariantCulture); + + cultureObject.PrintToString(config) + .Should() + .Be("CultureObject (\n\tDateTimeProp: 01.01.2020 00:00:00;\n\tDoubleProp: 22.22;\n)"); + } + + [Test] + public void SliceStrings_Works() + { + var fullName = new FullName + { + Name = "SmallName", + Surname = "VeryBigSurNameWithALotOfWater", + }; + + var config = ObjectPrinter.For() + .SliceStrings(10); + + fullName.PrintToString(config) + .Should() + .Be("FullName (\n\tName: SmallName;\n\tSurname: VeryBigSur;\n)"); + } + + [Test] + public void PrintToString_WorksWithIEnumerable() + { + var arr = new[] { 1, 2 }; + + arr.PrintToString() + .Should() + .Be("[\n\t1,\n\t2,\n]"); + } + + [Test] + public void PrintToString_WorksWithNestedIEnumerable() + { + var arr = new[] { new []{1, 2}, new []{3, 4} }; + + arr.PrintToString() + .Should() + .Be("[\n\t[\n\t\t1,\n\t\t2,\n\t],\n\t[\n\t\t3,\n\t\t4,\n\t],\n]"); + } + + [Test] + public void PrintToString_WorksWithIDictionary() + { + var dict = new Dictionary + { + [1] = 1, + [2] = 4, + [3] = 9 + }; + + dict.PrintToString() + .Should() + .Be("{\n\t1: 1;\n\t2: 4;\n\t3: 9;\n}"); + } + + [Test] + public void PrintToString_WorksWithNestedIDictionary() + { + var dict = new Dictionary> + { + [1] = new () + { + [2] = 3, + [4] = 5, + }, + [6] = new () + { + [7] = 8, + [9] = 10, + }, + }; + + dict.PrintToString() + .Should() + .Be("{\n\t1: {\n\t\t2: 3;\n\t\t4: 5;\n\t};\n\t6: {\n\t\t7: 8;\n\t\t9: 10;\n\t};\n}"); + } + + [Test] + public void PropertyGettingMethod_ThrowsArgumentExceptionOnWrongExpression() + { + new Action(() => ObjectPrinter.For() + .Exclude(p => 4)) + .Should() + .ThrowExactly() + .Where(e => e.Message.Contains("MemberExpression")); + + new Action(() => ObjectPrinter.For() + .Serialize(p => 4)) + .Should() + .ThrowExactly() + .Where(e => e.Message.Contains("MemberExpression")); + } + + [Test] + public void PrintToString_HandlesCyclicLinks() + { + var obj1 = new CyclicObject {SomeProp = 30}; + + var obj2 = new CyclicObject {SomeProp = 50}; + + obj1.Another = obj2; + obj2.Another = obj1; + + obj1.PrintToString() + .Should() + .Be("CyclicObject (\n\tSomeProp: 30;\n\tAnother: CyclicObject (\n\t\tSomeProp: 50;\n\t\tAnother: cyclic link;\n\t);\n)"); + } + + [Test] + public void PrintToString_WorksWithStrings() + { + "serialize me".PrintToString() + .Should() + .Be("serialize me"); + } + + [Test] + public void PrintToString_WorksWithValueTypes() + { + new Point(1, 1).PrintToString() + .Should() + .Be("{X=1,Y=1}"); + } +} \ No newline at end of file diff --git a/ObjectPrintingTests/ObjectPrintingTests.csproj b/ObjectPrintingTests/ObjectPrintingTests.csproj new file mode 100644 index 00000000..6ab58ff5 --- /dev/null +++ b/ObjectPrintingTests/ObjectPrintingTests.csproj @@ -0,0 +1,25 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + diff --git a/ObjectPrintingTests/Objects/CultureObject.cs b/ObjectPrintingTests/Objects/CultureObject.cs new file mode 100644 index 00000000..dd876099 --- /dev/null +++ b/ObjectPrintingTests/Objects/CultureObject.cs @@ -0,0 +1,7 @@ +namespace ObjectPrintingTests.Objects; + +public class CultureObject +{ + public DateTime DateTimeProp { get; set; } + public double DoubleProp { get; set; } +} \ No newline at end of file diff --git a/ObjectPrintingTests/Objects/CyclicObject.cs b/ObjectPrintingTests/Objects/CyclicObject.cs new file mode 100644 index 00000000..a20f4549 --- /dev/null +++ b/ObjectPrintingTests/Objects/CyclicObject.cs @@ -0,0 +1,7 @@ +namespace ObjectPrintingTests.Objects; + +public class CyclicObject +{ + public int SomeProp { get; set; } + public CyclicObject Another { get; set; } +} \ No newline at end of file diff --git a/ObjectPrintingTests/Objects/FullName.cs b/ObjectPrintingTests/Objects/FullName.cs new file mode 100644 index 00000000..af7a006c --- /dev/null +++ b/ObjectPrintingTests/Objects/FullName.cs @@ -0,0 +1,7 @@ +namespace ObjectPrintingTests.Objects; + +public class FullName +{ + public string Name { get; set; } + public string Surname { get; set; } +} \ No newline at end of file diff --git a/ObjectPrintingTests/Objects/Person.cs b/ObjectPrintingTests/Objects/Person.cs new file mode 100644 index 00000000..32b76f6f --- /dev/null +++ b/ObjectPrintingTests/Objects/Person.cs @@ -0,0 +1,11 @@ +namespace ObjectPrintingTests.Objects; + +public class Person +{ + public Guid Id { get; set; } + public string Name { get; set; } + public string Surname { get; set; } + public double Height { get; set; } + public int Age { get; set; } + public long Money { get; set; } +} \ No newline at end of file diff --git a/ObjectPrintingTests/Objects/Room.cs b/ObjectPrintingTests/Objects/Room.cs new file mode 100644 index 00000000..2c94a3af --- /dev/null +++ b/ObjectPrintingTests/Objects/Room.cs @@ -0,0 +1,8 @@ +namespace ObjectPrintingTests.Objects; + +public class Room +{ + public double Width { get; set; } + public double Length { get; set; } + public double Height { get; set; } +} \ No newline at end of file diff --git a/ObjectPrintingTests/Usings.cs b/ObjectPrintingTests/Usings.cs new file mode 100644 index 00000000..a83421a3 --- /dev/null +++ b/ObjectPrintingTests/Usings.cs @@ -0,0 +1,4 @@ +global using NUnit.Framework; +global using FluentAssertions; +global using ObjectPrinting; +global using ObjectPrintingTests.Objects; diff --git a/Test/Program.cs b/Test/Program.cs new file mode 100644 index 00000000..6787bbdb --- /dev/null +++ b/Test/Program.cs @@ -0,0 +1,10 @@ +// See https://aka.ms/new-console-template for more information + +Func a = (A item) => item.id; + + + +class A +{ + public int id; +} \ No newline at end of file diff --git a/Test/Test.csproj b/Test/Test.csproj new file mode 100644 index 00000000..2b14c817 --- /dev/null +++ b/Test/Test.csproj @@ -0,0 +1,10 @@ + + + + Exe + net7.0 + enable + enable + + + diff --git a/fluent-api.sln b/fluent-api.sln index 69c8db9e..d753c5a7 100644 --- a/fluent-api.sln +++ b/fluent-api.sln @@ -13,6 +13,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentMapping.Tests", "Samp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spectacle", "Samples\Spectacle\Spectacle.csproj", "{EFA9335C-411B-4597-B0B6-5438D1AE04C3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObjectPrintingTests", "ObjectPrintingTests\ObjectPrintingTests.csproj", "{871B8341-3408-40B7-80CA-B5EBC1C507AF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ObjectPrinting", "ObjectPrinting", "{770E7FC3-D26C-459C-A84C-1F31A8F5717A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -35,6 +39,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 + {871B8341-3408-40B7-80CA-B5EBC1C507AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {871B8341-3408-40B7-80CA-B5EBC1C507AF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {871B8341-3408-40B7-80CA-B5EBC1C507AF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {871B8341-3408-40B7-80CA-B5EBC1C507AF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -43,5 +51,7 @@ Global {FEEA5AFE-459A-4D13-81D0-252E1A2E6F4E} = {6D308E4A-CEC7-4536-9B87-81CD337A87AD} {8A7BB3EA-3E6A-4D04-A801-D5CD1620DA0D} = {6D308E4A-CEC7-4536-9B87-81CD337A87AD} {EFA9335C-411B-4597-B0B6-5438D1AE04C3} = {6D308E4A-CEC7-4536-9B87-81CD337A87AD} + {07B8C9B7-8289-46CB-9875-048A57758EEE} = {770E7FC3-D26C-459C-A84C-1F31A8F5717A} + {871B8341-3408-40B7-80CA-B5EBC1C507AF} = {770E7FC3-D26C-459C-A84C-1F31A8F5717A} EndGlobalSection EndGlobal