diff --git a/ObjectPrinting/Extensions/InnerPrintingConfigExtensions.cs b/ObjectPrinting/Extensions/InnerPrintingConfigExtensions.cs new file mode 100644 index 00000000..8ae9266d --- /dev/null +++ b/ObjectPrinting/Extensions/InnerPrintingConfigExtensions.cs @@ -0,0 +1,24 @@ +using System; +using System.Globalization; +using ObjectPrinting.InnerPrintingConfigs; + +namespace ObjectPrinting.Extensions +{ + public static class InnerPrintingConfigExtensions + { + public static PrintingConfig Using(this IInnerPrintingConfig config, CultureInfo culture) + { + return config.Using(x => x.ToString(culture));; + } + + public static PrintingConfig Using(this IInnerPrintingConfig config, CultureInfo culture) + { + return config.Using(x => x.ToString(culture));; + } + + public static PrintingConfig Using(this IInnerPrintingConfig config, CultureInfo culture) + { + return config.Using(x => x.ToString(culture));; + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/Extensions/ObjectExtensions.cs b/ObjectPrinting/Extensions/ObjectExtensions.cs new file mode 100644 index 00000000..8dd12359 --- /dev/null +++ b/ObjectPrinting/Extensions/ObjectExtensions.cs @@ -0,0 +1,17 @@ +using System; + +namespace ObjectPrinting.Extensions +{ + public static class ObjectExtensions + { + public static string PrintToString(this T obj) + { + return ObjectPrinter.For().PrintToString(obj); + } + + public static string PrintToString(this T obj, Func, PrintingConfig> config) + { + return config(ObjectPrinter.For()).PrintToString(obj); + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/Extensions/StringExtensions.cs b/ObjectPrinting/Extensions/StringExtensions.cs new file mode 100644 index 00000000..a78e5316 --- /dev/null +++ b/ObjectPrinting/Extensions/StringExtensions.cs @@ -0,0 +1,12 @@ +namespace ObjectPrinting.Extensions +{ + public static class StringExtensions + { + internal static string Truncate(this string text, int maxLength, string truncationSuffix = "") + { + return text.Length > maxLength + ? text[..maxLength] + truncationSuffix + : text; + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/Extensions/TypeExtensions.cs b/ObjectPrinting/Extensions/TypeExtensions.cs new file mode 100644 index 00000000..c5114867 --- /dev/null +++ b/ObjectPrinting/Extensions/TypeExtensions.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using ObjectPrinting.PropertyOrField; + +namespace ObjectPrinting.Extensions +{ + public static class TypeExtensions + { + internal static IEnumerable GetFieldsAndProperties(this Type type, BindingFlags bindingAttr) + { + return type.GetFields(bindingAttr) + .Select(x => new PropertyOrField.PropertyOrField(x)) + .Concat(type.GetProperties(bindingAttr).Select(x => new PropertyOrField.PropertyOrField(x))); + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/InnerPrintingConfigs/IInnerPrintingConfig.cs b/ObjectPrinting/InnerPrintingConfigs/IInnerPrintingConfig.cs new file mode 100644 index 00000000..9067897d --- /dev/null +++ b/ObjectPrinting/InnerPrintingConfigs/IInnerPrintingConfig.cs @@ -0,0 +1,10 @@ +using System; + +namespace ObjectPrinting.InnerPrintingConfigs +{ + public interface IInnerPrintingConfig + { + PrintingConfig Using(Func print); + PrintingConfig TrimmedToLength(int maxLen); + } +} \ No newline at end of file diff --git a/ObjectPrinting/InnerPrintingConfigs/MemberPrintingConfig.cs b/ObjectPrinting/InnerPrintingConfigs/MemberPrintingConfig.cs new file mode 100644 index 00000000..3fb24e4a --- /dev/null +++ b/ObjectPrinting/InnerPrintingConfigs/MemberPrintingConfig.cs @@ -0,0 +1,32 @@ +using System; +using System.Reflection; +using ObjectPrinting.Extensions; + +namespace ObjectPrinting.InnerPrintingConfigs +{ + public class MemberPrintingConfig : PrintingConfig, IInnerPrintingConfig + { + private readonly MemberInfo memberInfo; + + internal MemberPrintingConfig(PrintingConfig parent, MemberInfo memberInfo) : base(parent) + { + this.memberInfo = memberInfo; + } + + public PrintingConfig Using(Func print) + { + MemberSerializers[memberInfo] = obj => print((TMemberType)obj); + return this; + } + + public PrintingConfig TrimmedToLength(int maxLen) + { + var isSerialized = MemberSerializers.TryGetValue(memberInfo, out var prevSerializer); + MemberSerializers[memberInfo] = isSerialized + ? obj => prevSerializer(obj).Truncate(maxLen) + : obj => obj.ToString().Truncate(maxLen); + + return this; + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/InnerPrintingConfigs/TypePrintingConfig.cs b/ObjectPrinting/InnerPrintingConfigs/TypePrintingConfig.cs new file mode 100644 index 00000000..c9577c7f --- /dev/null +++ b/ObjectPrinting/InnerPrintingConfigs/TypePrintingConfig.cs @@ -0,0 +1,26 @@ +using System; +using ObjectPrinting.Extensions; + +namespace ObjectPrinting.InnerPrintingConfigs +{ + public class TypePrintingConfig : PrintingConfig, IInnerPrintingConfig + { + internal TypePrintingConfig(PrintingConfig parent) : base(parent) + { } + + public PrintingConfig Using(Func print) + { + TypeSerializers[typeof(TType)] = obj => print((TType)obj); + return this; + } + + public PrintingConfig TrimmedToLength(int maxLen) + { + var isSerialized = TypeSerializers.TryGetValue(typeof(TType), out var prevSerializer); + TypeSerializers[typeof(TType)] = isSerialized + ? obj => prevSerializer(obj).Truncate(maxLen) + : obj => obj.ToString().Truncate(maxLen); + return this; + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinter.cs b/ObjectPrinting/ObjectPrinter.cs index 3c7867c3..55c7832b 100644 --- a/ObjectPrinting/ObjectPrinter.cs +++ b/ObjectPrinting/ObjectPrinter.cs @@ -1,10 +1,10 @@ namespace ObjectPrinting { - public class ObjectPrinter + public static class ObjectPrinter { - public static PrintingConfig For() + public static PrintingConfig For(int maxNestingLevel = 10) { - return new PrintingConfig(); + return new PrintingConfig(maxNestingLevel); } } } \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinting.csproj b/ObjectPrinting/ObjectPrinting.csproj index 1c5eaf1c..92214dc3 100644 --- a/ObjectPrinting/ObjectPrinting.csproj +++ b/ObjectPrinting/ObjectPrinting.csproj @@ -1,14 +1,9 @@  - 8 - netcoreapp3.1 + 9 + net6.0 false - - - - - diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index a9e08211..531f4eac 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,11 +1,80 @@ -using System; +using System; +using System.Collections; +using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; +using System.Reflection; using System.Text; +using ObjectPrinting.Extensions; +using ObjectPrinting.InnerPrintingConfigs; +using ObjectPrinting.PropertyOrField; namespace ObjectPrinting { public class PrintingConfig { + private readonly Type[] finalTypes = + { + typeof(int), typeof(double), typeof(float), typeof(string), + typeof(DateTime), typeof(TimeSpan), typeof(Guid) + }; + private readonly HashSet excludedTypes = new(); + private readonly HashSet excludedMembers = new(); + protected readonly Dictionary> TypeSerializers = new(); + protected readonly Dictionary> MemberSerializers = new(); + private int MaxNestingLevel { get; } + + public PrintingConfig(int maxNestingLevel = 10) + { + MaxNestingLevel = maxNestingLevel; + } + + protected PrintingConfig(PrintingConfig parent) + { + excludedTypes = new HashSet(parent.excludedTypes); + excludedMembers = new HashSet(parent.excludedMembers); + TypeSerializers = new Dictionary>(parent.TypeSerializers); + MemberSerializers = new Dictionary>(parent.MemberSerializers); + MaxNestingLevel = parent.MaxNestingLevel; + } + + public IInnerPrintingConfig Printing() + { + var config = new PrintingConfig(this); + return new TypePrintingConfig(config); + } + + public IInnerPrintingConfig Printing(Expression> memberSelector) + { + if (memberSelector.Body is not MemberExpression memberExpression) + throw new ArgumentException("memberSelector should me MemberExpression"); + var memberInfo = memberExpression.Member; + if (memberInfo.DeclaringType != typeof(TOwner)) + throw new ArgumentException($"You should access {typeof(TOwner)} property or field"); + + var config = new PrintingConfig(this); + return new MemberPrintingConfig(config, memberInfo); + } + + public PrintingConfig Excluding(Expression> memberSelector) + { + if (memberSelector.Body is not MemberExpression memberExpression) + throw new ArgumentException("memberSelector should me MemberExpression"); + var memberInfo = memberExpression.Member; + if (memberInfo.DeclaringType != typeof(TOwner)) + throw new ArgumentException($"You should access {typeof(TOwner)} property or field"); + var config = new PrintingConfig(this); + config.excludedMembers.Add(memberInfo); + return config; + } + + public PrintingConfig Excluding() + { + var config = new PrintingConfig(this); + config.excludedTypes.Add(typeof(TType)); + return config; + } + public string PrintToString(TOwner obj) { return PrintToString(obj, 0); @@ -13,29 +82,75 @@ public string PrintToString(TOwner obj) 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())) + var type = obj.GetType(); + + if (TypeSerializers.TryGetValue(type, out var serializer)) + return serializer(obj) + Environment.NewLine; + if (finalTypes.Contains(type) || nestingLevel == MaxNestingLevel) return obj + Environment.NewLine; + if (obj is ICollection collection) + return Environment.NewLine + SerializeCollection(collection, nestingLevel); + + return SerializeByFieldsAndProperties(obj, nestingLevel); + } - var identation = new string('\t', nestingLevel + 1); - var sb = new StringBuilder(); + private string SerializeByFieldsAndProperties(object obj, int nestingLevel) + { 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)); - } + var identation = new string('\t', nestingLevel + 1); + var sb = new StringBuilder(type.Name + Environment.NewLine); + + foreach (var member in GetNonExcludedPropertyOrFields(type)) + sb.Append(identation + member.Name + " = " + GetSerializedMemberOrDefault(obj, member, nestingLevel + 1)); + return sb.ToString(); } + + private string GetSerializedMemberOrDefault(object obj, IPropertyOrField member, int nestingLevel) + { + var isMemberSerialized = MemberSerializers.ContainsKey(member.UnderlyingMember); + var memberValue = member.GetValue(obj); + + return isMemberSerialized + ? MemberSerializers[member.UnderlyingMember](memberValue) + Environment.NewLine + : PrintToString(memberValue, nestingLevel); + } + + private IEnumerable GetNonExcludedPropertyOrFields(Type type) + { + return type + .GetFieldsAndProperties(BindingFlags.Instance | BindingFlags.Public) + .Where(x => !excludedMembers.Contains(x.UnderlyingMember) + && !excludedTypes.Contains(x.DataType)); + } + + private string SerializeCollection(ICollection collection, int nestingLevel) + { + var serialization = new StringBuilder(); + var index = 0; + + if (collection is IDictionary dictionary) + foreach (var key in dictionary.Keys) + serialization.Append(PrintCollectionItem(key, dictionary[key], nestingLevel)); + else foreach (var item in collection) + serialization.Append(PrintCollectionItem(index++, item , nestingLevel)); + + return serialization.ToString(); + } + + private string PrintCollectionItem(object key, object value, int nestingLevel) + { + var identation = new string('\t', nestingLevel); + var isFinalOrCollection = finalTypes.Contains(value.GetType()) || value is ICollection; + + var serialization = new StringBuilder(); + serialization.Append($"{identation}[{PrintToString(key, nestingLevel).TrimEnd()}]:{(isFinalOrCollection ? "" : Environment.NewLine)}"); + serialization.Append($"{(isFinalOrCollection ? " " : identation + '\t')}{PrintToString(value, nestingLevel + 1)}"); + + return serialization.ToString(); + } } } \ No newline at end of file diff --git a/ObjectPrinting/PropertyOrField/IPropertyOrField.cs b/ObjectPrinting/PropertyOrField/IPropertyOrField.cs new file mode 100644 index 00000000..837d9a0a --- /dev/null +++ b/ObjectPrinting/PropertyOrField/IPropertyOrField.cs @@ -0,0 +1,13 @@ +using System; +using System.Reflection; + +namespace ObjectPrinting.PropertyOrField +{ + internal interface IPropertyOrField + { + string Name { get; } + Type DataType { get; } + MemberInfo UnderlyingMember { get; } + Func GetValue { get; } + } +} \ No newline at end of file diff --git a/ObjectPrinting/PropertyOrField/PropertyOrField.cs b/ObjectPrinting/PropertyOrField/PropertyOrField.cs new file mode 100644 index 00000000..f21b0e9d --- /dev/null +++ b/ObjectPrinting/PropertyOrField/PropertyOrField.cs @@ -0,0 +1,29 @@ +using System; +using System.Reflection; + +namespace ObjectPrinting.PropertyOrField +{ + internal class PropertyOrField : IPropertyOrField + { + public string Name { get; } + public Type DataType { get; } + public MemberInfo UnderlyingMember { get; } + public Func GetValue { get; } + + public PropertyOrField(PropertyInfo propertyInfo) + { + Name = propertyInfo.Name; + DataType = propertyInfo.PropertyType; + UnderlyingMember = propertyInfo; + GetValue = propertyInfo.GetValue; + } + + public PropertyOrField(FieldInfo fieldInfo) + { + Name = fieldInfo.Name; + DataType = fieldInfo.FieldType; + UnderlyingMember = fieldInfo; + GetValue = fieldInfo.GetValue; + } + } +} \ 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/ObjectPrintingTests/ObjectPrinterAcceptanceTests.cs b/ObjectPrintingTests/ObjectPrinterAcceptanceTests.cs new file mode 100644 index 00000000..562a8fc0 --- /dev/null +++ b/ObjectPrintingTests/ObjectPrinterAcceptanceTests.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using NUnit.Framework; +using ObjectPrinting.Extensions; + +namespace ObjectPrinting.Tests +{ + [TestFixture] + public class ObjectPrinterAcceptanceTests + { + [Test] + public void Demo() + { + var person = new Person {Name = "Alex", Height = 1.11, Age = 19, Parent = new Person {Name = "Andrew", Age = 90}}; + var persons = new List {person, person}; + + var printer = ObjectPrinter.For() + .Excluding() + .Printing(x => x.Age).Using(x => x > 18 ? "Старый" : "Молодой") + .Printing().Using(i => i.ToString("X")) + .Printing().Using(CultureInfo.InvariantCulture) + .Printing(p => p.Name).TrimmedToLength(2); + + var listPrinter = ObjectPrinter.For>() + .Excluding() + .Printing().Using(CultureInfo.GetCultureInfo("ru")); + + Console.WriteLine(printer.PrintToString(person)); + Console.WriteLine(person.PrintToString()); + Console.WriteLine(person.PrintToString(s => s.Excluding(p => p.Age))); + Console.WriteLine(listPrinter.PrintToString(persons)); + } + } +} \ No newline at end of file diff --git a/ObjectPrintingTests/ObjectPrintingTests.csproj b/ObjectPrintingTests/ObjectPrintingTests.csproj new file mode 100644 index 00000000..e740ccf3 --- /dev/null +++ b/ObjectPrintingTests/ObjectPrintingTests.csproj @@ -0,0 +1,25 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrintingTests/Person.cs similarity index 71% rename from ObjectPrinting/Tests/Person.cs rename to ObjectPrintingTests/Person.cs index f9555955..17b3b478 100644 --- a/ObjectPrinting/Tests/Person.cs +++ b/ObjectPrintingTests/Person.cs @@ -6,7 +6,10 @@ 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 Person Parent { get; set; } } } \ No newline at end of file diff --git a/ObjectPrintingTests/PrintingConfigTests.cs b/ObjectPrintingTests/PrintingConfigTests.cs new file mode 100644 index 00000000..8671e62d --- /dev/null +++ b/ObjectPrintingTests/PrintingConfigTests.cs @@ -0,0 +1,238 @@ +using System; +using System.Globalization; +using NUnit.Framework; +using ObjectPrinting.Extensions; +using FluentAssertions; + +namespace ObjectPrinting.Tests +{ + public class PrintingConfigTests + { + private PrintingConfig sut; + private Person simplePerson; + private Person personWithNesting; + private Person personWithCyclicReference; + private List persons; + private Dictionary dictPersons; + + [SetUp] + public void SetUp() + { + sut = new PrintingConfig(); + simplePerson = new Person {Name = "Alex", Surname = "Obama", Age = 19}; + personWithNesting = new Person {Name = "Oleg", Surname = "Ponso", Age = 5, Parent = simplePerson}; + personWithCyclicReference = new Person {Name = "IAmCyclic", Surname = "ThatsHorrible", Age = 19}; + personWithCyclicReference.Parent = personWithCyclicReference; + persons = new List {simplePerson, personWithNesting}; + dictPersons = new Dictionary + { + [simplePerson.Name] = simplePerson, + [personWithNesting.Name] = personWithNesting + }; + } + + [Test] + public void Printing_ThrowsException_OnProvidingNotMemberExpression() + { + var a = () => sut.Printing(x => x); + + a.Should().Throw(); + } + + [Test] + public void Printing_ThrowsException_OnAccessingSomethingNotDeclaredInOwnedClass() + { + var differentObj = (1, 2); + var a = () => sut.Printing(x => differentObj.Item1); + + a.Should().Throw(); + } + + [Test] + public void Printing_NotThrowsStackOverflowException_OnPrintingObjectWithCyclicReference() + { + var a = () => sut.PrintToString(personWithCyclicReference); + + a.Should().NotThrow(); + } + + [Test] + public void Printing_ReturnsStringWithAllFieldAndProperties_OnDefault() + { + sut.PrintToString(simplePerson) + .Should() + .ContainAll(nameof(Person.Age), nameof(Person.Id), nameof(Person.Height), nameof(Person.Name), + nameof(Person.Surname), nameof(Person.Parent)) + .And.ContainAll( + simplePerson.Name, + simplePerson.Height.ToString(CultureInfo.InvariantCulture), + simplePerson.Age.ToString(), + simplePerson.Id.ToString(), + "null", + simplePerson.Surname); + } + + [Test] + public void PrintToString_WorksCorrect_OnCollections() + { + persons.PrintToString() + .Should() + .ContainAll(simplePerson.Name, personWithNesting.Name, personWithNesting.Parent.Name) + .And.ContainAll("[0]:", "[1]:"); + + dictPersons.PrintToString() + .Should() + .ContainAll(simplePerson.Name, personWithNesting.Name, personWithNesting.Parent.Name) + .And.ContainAll($"[{simplePerson.Name}]:", $"[{personWithNesting.Name}]:"); + } + + [Test] + public void Excluding_DoNotAffectsPreviousConfig_OnExcludeType() + { + var first = sut.Excluding(); + var second = first.Excluding(); + first.PrintToString(simplePerson).Should().NotBeSameAs(second.PrintToString(simplePerson)); + } + + [Test] + public void Excluding_DoNotAffectsPreviousConfig_OnExcludeMember() + { + var first = sut.Excluding(x => x.Age); + var second = first.Excluding(x => x.Id); + first.PrintToString(simplePerson).Should().NotBeSameAs(second.PrintToString(simplePerson)); + } + + [Test] + public void Using_DoNotAffectsPreviousConfig_OnProvidingTypeSerializer() + { + var first = sut.Printing().Using(x => "X"); + var second = first.Printing().Using(x => "Y"); + first.PrintToString(simplePerson).Should().NotBeSameAs(second.PrintToString(simplePerson)); + } + + [Test] + public void Using_DoNotAffectsPreviousConfig_OnProvidingMemberSerializer() + { + var first = sut.Printing(x => x.Id).Using(x => "X"); + var second = first.Printing(x => x.Id).Using(x => "Y"); + first.PrintToString(simplePerson).Should().NotBeSameAs(second.PrintToString(simplePerson)); + } + + [Test] + public void Excluding_ExcludeTypeMembersFromString_OnExcludeType() + { + sut.Excluding().PrintToString(simplePerson).Should().NotContainAny("Name"); + sut.Excluding().PrintToString(simplePerson).Should().NotContainAny("Id"); + sut.Excluding().PrintToString(simplePerson).Should().NotContainAny("Age"); + } + + [Test] + public void Excluding_ExcludeTypeMembersFromNestingString_OnExcludeType() + { + sut.Excluding().PrintToString(personWithNesting).Should().NotContainAny("Name"); + sut.Excluding().PrintToString(personWithNesting).Should().NotContainAny("Id"); + sut.Excluding().PrintToString(personWithNesting).Should().NotContainAny("Age"); + } + + [Test] + public void Excluding_ExcludesMembersFromString_OnExcludeMember() + { + sut.Excluding(x => x.Age).PrintToString(simplePerson).Should().NotContainAny("Age"); + sut.Excluding(x => x.Id).PrintToString(simplePerson).Should().NotContainAny("Id"); + sut.Excluding(x => x.Name).PrintToString(simplePerson).Should().NotContainAny("Name"); + } + + [Test] + public void Excluding_ExcludesMembersFromNestingString_OnExcludeMember() + { + sut.Excluding(x => x.Age).PrintToString(personWithNesting).Should().NotContainAny("Age"); + sut.Excluding(x => x.Id).PrintToString(personWithNesting).Should().NotContainAny("Id"); + sut.Excluding(x => x.Name).PrintToString(personWithNesting).Should().NotContainAny("Name"); + } + + [Test] + public void Using_AppliedCultureToMembers_OnProvidingCultureToMember() + { + var ruCulture = CultureInfo.GetCultureInfo("ru"); + + sut.Printing(x => x.Height) + .Using(CultureInfo.InvariantCulture) + .PrintToString(simplePerson) + .Should() + .Contain(simplePerson.Height.ToString(CultureInfo.InvariantCulture)); + + sut.Printing(x => x.Height) + .Using(ruCulture) + .PrintToString(simplePerson) + .Should() + .Contain(simplePerson.Height.ToString(ruCulture)); + } + + [Test] + public void Using_AppliedCultureToTypes_OnProvidingCultureToType() + { + var ruCulture = CultureInfo.GetCultureInfo("ru"); + + sut.Printing() + .Using(CultureInfo.InvariantCulture) + .PrintToString(simplePerson) + .Should() + .Contain(simplePerson.Height.ToString(CultureInfo.InvariantCulture)); + + sut.Printing() + .Using(ruCulture) + .PrintToString(simplePerson) + .Should() + .Contain(simplePerson.Height.ToString(ruCulture)); + } + + [Test] + public void Using_AppliesCustomTypeSerialize_OnProvidingTypeSerializer() + { + sut.Printing() + .Using(x => x > 18 ? "Старый" : "Молодой") + .PrintToString(simplePerson) + .Should() + .ContainAny("Старый") + .And + .NotContainAny("Молодой"); + } + + [Test] + public void Using_AppliesCustomMemberSerialize_OnProvidingMemberSerializer() + { + sut.Printing(x => x.Age) + .Using(x => x > 18 ? "Старый" : "Молодой") + .PrintToString(simplePerson) + .Should() + .ContainAny("Старый") + .And + .NotContainAny("Молодой"); + } + + [Test] + public void TrimmedToLength_TrimsStrings_OfProvidedType() + { + sut.Printing() + .TrimmedToLength(2) + .PrintToString(simplePerson) + .Should() + .Contain("Al") + .And.Contain("Ob") + .And.NotContain("Alex") + .And.NotContain("Obama"); + } + + [Test] + public void TrimmedToLength_TrimsStrings_OfProvidedMember() + { + sut.Printing(x => x.Name) + .TrimmedToLength(2) + .PrintToString(simplePerson) + .Should() + .Contain("Al") + .And.NotContain("Alex") + .And.Contain("Obama"); + } + } +} \ No newline at end of file diff --git a/ObjectPrintingTests/Usings.cs b/ObjectPrintingTests/Usings.cs new file mode 100644 index 00000000..e0315ac9 --- /dev/null +++ b/ObjectPrintingTests/Usings.cs @@ -0,0 +1,2 @@ +global using NUnit.Framework; +global using FluentAssertions; diff --git a/fluent-api.sln b/fluent-api.sln index 69c8db9e..a1c4597e 100644 --- a/fluent-api.sln +++ b/fluent-api.sln @@ -13,6 +13,8 @@ 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", "{AA875A42-9533-4A18-81D5-12F5C70B568B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -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 + {AA875A42-9533-4A18-81D5-12F5C70B568B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA875A42-9533-4A18-81D5-12F5C70B568B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA875A42-9533-4A18-81D5-12F5C70B568B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA875A42-9533-4A18-81D5-12F5C70B568B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE