diff --git a/ObjectPrinting/IPrintingConfig.cs b/ObjectPrinting/IPrintingConfig.cs new file mode 100644 index 000000000..c061ca227 --- /dev/null +++ b/ObjectPrinting/IPrintingConfig.cs @@ -0,0 +1,17 @@ +using System; +using System.Linq.Expressions; + +namespace ObjectPrinting; + +public interface IPrintingConfig +{ + IPrintingConfig Excluding(); + + IPrintingConfig Excluding(Expression> memberSelector); + + IPropertyPrintingConfig Printing(); + + IPropertyPrintingConfig Printing(Expression> memberSelector); + + string PrintToString(TOwner obj); +} \ No newline at end of file diff --git a/ObjectPrinting/IPropertyPrintingConfig.cs b/ObjectPrinting/IPropertyPrintingConfig.cs new file mode 100644 index 000000000..4e21a473e --- /dev/null +++ b/ObjectPrinting/IPropertyPrintingConfig.cs @@ -0,0 +1,13 @@ +using System; +using System.Globalization; + +namespace ObjectPrinting; + +public interface IPropertyPrintingConfig : IPrintingConfig +{ + IPropertyPrintingConfig UsingSerializer(Func serializer); + + IPropertyPrintingConfig UsingCulture(CultureInfo culture); + + IPropertyPrintingConfig TrimmedToLength(int maxLength); +} \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinter.cs b/ObjectPrinting/ObjectPrinter.cs index 3c7867c32..f1eb49258 100644 --- a/ObjectPrinting/ObjectPrinter.cs +++ b/ObjectPrinting/ObjectPrinter.cs @@ -1,10 +1,9 @@ -namespace ObjectPrinting +namespace ObjectPrinting; + +public static class ObjectPrinter { - public class ObjectPrinter + public static IPrintingConfig For() { - public static PrintingConfig For() - { - return new PrintingConfig(); - } + return new PrintingConfig(); } } \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinting.csproj b/ObjectPrinting/ObjectPrinting.csproj index c5db392ff..ea98111e3 100644 --- a/ObjectPrinting/ObjectPrinting.csproj +++ b/ObjectPrinting/ObjectPrinting.csproj @@ -5,6 +5,7 @@ + diff --git a/ObjectPrinting/ObjectPrintingExtensions.cs b/ObjectPrinting/ObjectPrintingExtensions.cs new file mode 100644 index 000000000..19cabac7a --- /dev/null +++ b/ObjectPrinting/ObjectPrintingExtensions.cs @@ -0,0 +1,16 @@ +using System; + +namespace ObjectPrinting; + +public static class ObjectPrintingExtensions +{ + public static string PrintToString(this TOwner obj) + { + return ObjectPrinter.For().PrintToString(obj); + } + + public static string PrintToString(this TOwner obj, Func, IPrintingConfig> config) + { + return config(ObjectPrinter.For()).PrintToString(obj); + } +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index a9e082117..c9ee0a272 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,41 +1,345 @@ -using System; -using System.Linq; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq.Expressions; +using System.Reflection; using System.Text; -namespace ObjectPrinting +namespace ObjectPrinting; + +public class PrintingConfig : IPrintingConfig { - public class PrintingConfig + private readonly HashSet _excludedTypes = []; + private readonly HashSet _excludedMembers = []; + private readonly Dictionary> _typeSerializers = []; + private readonly Dictionary> _memberSerializers = []; + private readonly Dictionary _typeCultures = []; + private readonly Dictionary _memberCultures = []; + private readonly Dictionary _memberStringTrims = []; + private readonly Dictionary _typeStringTrims = []; + + private readonly object _sync = new(); + + internal void SetTypeSerializer(Type type, Func serializer) + { + ArgumentNullException.ThrowIfNull(type); + ArgumentNullException.ThrowIfNull(serializer); + + var normalizedType = NormalizeType(type); + + lock (_sync) + { + _typeSerializers[normalizedType] = serializer; + } + } + + internal void SetMemberSerializer(MemberInfo member, Func serializer) + { + ArgumentNullException.ThrowIfNull(member); + ArgumentNullException.ThrowIfNull(serializer); + + lock (_sync) + { + _memberSerializers[member] = serializer; + } + } + + internal void SetTypeCulture(Type type, CultureInfo culture) + { + ArgumentNullException.ThrowIfNull(type); + ArgumentNullException.ThrowIfNull(culture); + + var normalizedType = NormalizeType(type); + + lock (_sync) + { + _typeCultures[normalizedType] = culture; + } + } + + internal void SetMemberCulture(MemberInfo member, CultureInfo culture) + { + ArgumentNullException.ThrowIfNull(member); + ArgumentNullException.ThrowIfNull(culture); + + lock (_sync) + { + _memberCultures[member] = culture; + } + } + + internal void SetMemberStringTrim(MemberInfo member, int maxLength) + { + ArgumentNullException.ThrowIfNull(member); + if (maxLength < 0) + throw new ArgumentOutOfRangeException(nameof(maxLength), "Параметр maxLength должен быть неотрицательным."); + + lock (_sync) + { + _memberStringTrims[member] = maxLength; + } + } + + internal void SetTypeStringTrim(Type type, int maxLength) + { + ArgumentNullException.ThrowIfNull(type); + if (maxLength < 0) + throw new ArgumentOutOfRangeException(nameof(maxLength), "Параметр maxLength должен быть неотрицательным."); + + var normalizedType = NormalizeType(type); + + lock (_sync) + { + _typeStringTrims[normalizedType] = maxLength; + } + } + + public IPrintingConfig Excluding() + { + var normalizedType = NormalizeType(typeof(TPropType)); + + lock (_sync) + { + _excludedTypes.Add(normalizedType); + return this; + } + } + + public IPrintingConfig Excluding(Expression> memberSelector) + { + ArgumentNullException.ThrowIfNull(memberSelector); + + if (memberSelector.Body is not MemberExpression expression) + throw new ArgumentException("Выражение должно указывать на поле или свойство.", nameof(memberSelector)); + + lock (_sync) + { + _excludedMembers.Add(expression.Member); + return this; + } + } + + public IPropertyPrintingConfig Printing() + { + var normalizedType = NormalizeType(typeof(TProp)); + return new PropertyPrintingConfig(this, normalizedType, null!); + } + + public IPropertyPrintingConfig Printing(Expression> memberSelector) + { + ArgumentNullException.ThrowIfNull(memberSelector); + + if (memberSelector.Body is not MemberExpression expression) + throw new ArgumentException("Выражение должно указывать на поле или свойство.", nameof(memberSelector)); + + var normalizedType = NormalizeType(typeof(TProp)); + return new PropertyPrintingConfig(this, normalizedType, expression.Member); + } + + public string PrintToString(TOwner obj) { - public string PrintToString(TOwner obj) + lock (_sync) { - return PrintToString(obj, 0); + var visited = new HashSet(new ReferenceEqualityComparer()); + var sb = new StringBuilder(); + + AppendTo(sb, obj!, 0, visited, null!, typeof(TOwner)); + + return sb.ToString(); } + } - private string PrintToString(object obj, int nestingLevel) + private void AppendTo(StringBuilder sb, object obj, int nestingLevel, HashSet visited, MemberInfo member, Type expectedType) + { + if (obj == null) { - //TODO apply configurations - if (obj == null) - return "null" + Environment.NewLine; + sb.Append("null ("); + sb.Append(expectedType.Name); + sb.Append(')'); + sb.Append(Environment.NewLine); + return; + } - var finalTypes = new[] + var type = NormalizeType(obj.GetType()); + + if (member != null && _memberSerializers.TryGetValue(member, out var memberSerializer)) + { + sb.Append(memberSerializer(obj)); + sb.Append(Environment.NewLine); + return; + } + + if (_typeSerializers.TryGetValue(type, out var typeSerializer)) + { + sb.Append(typeSerializer(obj)); + sb.Append(Environment.NewLine); + return; + } + + if (IsFinalType(type)) + { + sb.Append(FormatFinalValue(obj, type, member)); + sb.Append(Environment.NewLine); + return; + } + + if (!visited.Add(obj)) + { + sb.Append("cyclic reference"); + sb.Append(Environment.NewLine); + return; + } + + try + { + sb.Append(obj.GetType().Name); + sb.Append(Environment.NewLine); + + var indent = new string('\t', nestingLevel + 1); + + foreach (var memberInfo in GetSerializableMembers(obj.GetType())) { - typeof(int), typeof(double), typeof(float), typeof(string), - typeof(DateTime), typeof(TimeSpan) + if (ShouldSkipMember(memberInfo)) + continue; + + var value = GetMemberValue(obj, memberInfo); + var memberType = GetMemberType(memberInfo); + + sb.Append(indent); + sb.Append(memberInfo.Name); + sb.Append(" = "); + + AppendTo(sb, value!, nestingLevel + 1, visited, memberInfo, memberType); + } + } + finally + { + visited.Remove(obj); + } + } + + private static Type GetMemberType(MemberInfo member) + { + return member switch + { + FieldInfo f => f.FieldType, + PropertyInfo p => p.PropertyType, + _ => typeof(object) + }; + } + + private static IEnumerable GetSerializableMembers(Type type) + { + const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public; + + foreach (var member in type.GetFields(flags)) + yield return member; + + foreach (var member in type.GetProperties(flags)) + { + var getter = member.GetMethod; + if (getter is null || !getter.IsPublic) + continue; + + yield return member; + } + } + + private static object? GetMemberValue(object obj, MemberInfo member) + { + try + { + return member switch + { + FieldInfo field => field.GetValue(obj), + PropertyInfo property => property.GetValue(obj), + _ => null }; - if (finalTypes.Contains(obj.GetType())) - return obj + Environment.NewLine; + } + catch (TargetInvocationException ex) when (ex.InnerException != null) + { + return $""; + } + catch (Exception ex) + { + return $""; + } + } - 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()) + private static bool IsFinalType(Type type) + { + type = NormalizeType(type); + + return type.IsPrimitive + || type.IsEnum + || type == typeof(string) + || type == typeof(decimal) + || type == typeof(DateTime) + || type == typeof(TimeSpan) + || type == typeof(Guid); + } + + private string FormatFinalValue(object obj, Type type, MemberInfo? member) + { + type = NormalizeType(type); + + string result; + + CultureInfo? culture = null; + if (member != null && _memberCultures.TryGetValue(member, out var memberCulture)) + culture = memberCulture; + else if (_typeCultures.TryGetValue(type, out var typeCulture)) + culture = typeCulture; + + if (obj is IFormattable formattable && culture != null) + result = formattable.ToString(null, culture) ?? string.Empty; + else + result = obj.ToString() ?? string.Empty; + + if (type == typeof(string)) + { + var needsTrim = false; + var maxLength = 0; + + if (member != null && _memberStringTrims.TryGetValue(member, out var memberMax)) { - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); + needsTrim = result.Length > memberMax; + maxLength = memberMax; } - return sb.ToString(); + else if (_typeStringTrims.TryGetValue(type, out var typeMax)) + { + needsTrim = result.Length > typeMax; + maxLength = typeMax; + } + + if (needsTrim) + result = result[..maxLength]; } + + return result; + } + + private bool ShouldSkipMember(MemberInfo member) + { + if (_excludedMembers.Contains(member)) + return true; + + var type = member switch + { + PropertyInfo p => p.PropertyType, + FieldInfo f => f.FieldType, + _ => null + }; + + return type != null && _excludedTypes.Contains(NormalizeType(type)); + } + + private static Type NormalizeType(Type type) + { + var underlying = Nullable.GetUnderlyingType(type); + if (underlying != null) + return underlying; + + return type; } -} \ No newline at end of file +} diff --git a/ObjectPrinting/PropertyPrintingConfig.cs b/ObjectPrinting/PropertyPrintingConfig.cs new file mode 100644 index 000000000..8adf26354 --- /dev/null +++ b/ObjectPrinting/PropertyPrintingConfig.cs @@ -0,0 +1,77 @@ +using System; +using System.Globalization; +using System.Linq.Expressions; +using System.Reflection; + +namespace ObjectPrinting; + +public class PropertyPrintingConfig(PrintingConfig parent, Type targetType, MemberInfo memberInfo) : IPropertyPrintingConfig +{ + private readonly PrintingConfig _parent = parent; + private readonly Type _targetType = targetType; + private readonly MemberInfo? _memberInfo = memberInfo; + + public IPropertyPrintingConfig UsingSerializer(Func serializer) + { + ArgumentNullException.ThrowIfNull(serializer); + + string boxed(object o) => serializer((TProp)o); + + if (_memberInfo != null) + _parent.SetMemberSerializer(_memberInfo, boxed); + else + _parent.SetTypeSerializer(_targetType, boxed); + + return this; + } + + public IPropertyPrintingConfig UsingCulture(CultureInfo culture) + { + ArgumentNullException.ThrowIfNull(culture); + + if (_memberInfo != null) + _parent.SetMemberCulture(_memberInfo, culture); + else + _parent.SetTypeCulture(_targetType, culture); + + return this; + } + + public IPropertyPrintingConfig TrimmedToLength(int maxLength) + { + if (maxLength < 0) + throw new ArgumentOutOfRangeException(nameof(maxLength), "Параметр maxLength должен быть неотрицательным."); + + if (_memberInfo != null) + _parent.SetMemberStringTrim(_memberInfo, maxLength); + else + _parent.SetTypeStringTrim(_targetType, maxLength); + + return this; + } + + public IPrintingConfig Excluding() + { + return _parent.Excluding(); + } + + public IPrintingConfig Excluding(Expression> memberSelector) + { + return _parent.Excluding(memberSelector); + } + + public IPropertyPrintingConfig Printing() + { + return _parent.Printing(); + } + + public IPropertyPrintingConfig Printing(Expression> memberSelector) + { + return _parent.Printing(memberSelector); + } + + public string PrintToString(TOwner obj) + { + return _parent.PrintToString(obj); + } +} diff --git a/ObjectPrinting/ReferenceEqualityComparer.cs b/ObjectPrinting/ReferenceEqualityComparer.cs new file mode 100644 index 000000000..3e461004a --- /dev/null +++ b/ObjectPrinting/ReferenceEqualityComparer.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace ObjectPrinting; + +public class ReferenceEqualityComparer : IEqualityComparer +{ + public new bool Equals(object? x, object? y) + { + return ReferenceEquals(x, y); + } + + public int GetHashCode(object obj) + { + return obj.GetHashCode(); + } +} + diff --git a/ObjectPrinting/Solved/ObjectExtensions.cs b/ObjectPrinting/Solved/ObjectExtensions.cs deleted file mode 100644 index b0c94553c..000000000 --- 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 540ee769c..000000000 --- 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 0ec5aeb2b..000000000 --- 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 a509697d1..000000000 --- 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 dd3922394..000000000 --- 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 ac52d5ee5..000000000 --- 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 858ebbf8d..000000000 --- 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 index 4c8b2445c..6b81a9c95 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,27 +1,124 @@ -using NUnit.Framework; +using FluentAssertions; +using NUnit.Framework; +using System; +using static System.Globalization.CultureInfo; -namespace ObjectPrinting.Tests +namespace ObjectPrinting.Tests; + +[TestFixture] +public class ObjectPrinterAcceptanceTests { - [TestFixture] - public class ObjectPrinterAcceptanceTests + private const int TrimmedLength = 2; + + private Person _person = null!; + private IPrintingConfig _printer = null!; + + [SetUp] + public void SetUp() { - [Test] - public void Demo() + _person = new Person { - 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. ...с конфигурированием - } + Id = Guid.NewGuid(), + Name = "Mikasa", + Age = 19, + Height = 1.76, + Gender = "Female" + }; + + _printer = ObjectPrinter.For(); + } + + [Test] + public void PrintToStringExtension_DefaultConfiguration_DefaultSerialization() + { + var result = _person.PrintToString(); + + result.Should().Contain("Mikasa"); + result.Should().Contain("19"); + result.Should().Contain("1,76"); + } + + [Test] + public void PrintToStringExtension_WithCustomConfiguration_ApplyCustomConfiguration() + { + var result = _person.PrintToString(cfg => cfg.Printing(p => p.Name).TrimmedToLength(TrimmedLength).Printing(p => p.Height).UsingCulture(InvariantCulture)); + + result.Should().Contain("Mi"); + result.Should().Contain("1.76"); + } + + [Test] + public void PrintToString_ExcludingType_SkipAllMembersOfThisType() + { + _printer = _printer.Excluding(); + + var result = _printer.PrintToString(_person); + + result.Should().NotContain(nameof(_person.Name)); + result.Should().NotContain(nameof(_person.Gender)); + } + + [Test] + public void PrintToString_UsingCustomSerializerForType_ApplySerializerToAllMembersOfType() + { + _printer = _printer.Printing().UsingSerializer(i => $"Целое число: {i}"); + + var result = _printer.PrintToString(_person); + + result.Should().Contain("Целое число: 19"); + } + + [Test] + public void PrintToString_SetCultureForNumericType_ApplyCultureToSerialization() + { + _printer = _printer.Printing().UsingCulture(InvariantCulture); + + var result = _printer.PrintToString(_person); + + result.Should().Contain("1.76"); + } + + [Test] + public void PrintToString_CustomSerializerForProperty_ChangeOnlyThisProperty() + { + _printer = _printer.Printing(p => p.Age).UsingSerializer(a => $"Возраст человека: {a}"); + + var result = _printer.PrintToString(_person); + + result.Should().Contain("Возраст человека: 19"); + } + + [Test] + public void PrintToString_TrimProperty_TrimPropertyCorrectly() + { + _printer = _printer.Printing(p => p.Name).TrimmedToLength(TrimmedLength); + + var result = _printer.PrintToString(_person); + + result.Should().Contain("Mi"); + } + + [Test] + public void PrintToString_ExcludeProperty_SkipThisProperty() + { + _printer = _printer.Excluding(p => p.Height); + + var result = _printer.PrintToString(_person); + + result.Should().NotContain("Height"); + } + + [Test] + public void PrintToString_CyclicReference_ShouldNotCallStackOverflow() + { + var parent = new Person { Name = "Grisha" }; + var child = new Person { Name = "Eren" }; + + parent.Child = child; + child.Child = parent; + + var result = _printer.PrintToString(parent); + + result.Should().Contain("cyclic reference"); } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrinting/Tests/Person.cs index f95559554..055e2e3fc 100644 --- a/ObjectPrinting/Tests/Person.cs +++ b/ObjectPrinting/Tests/Person.cs @@ -1,12 +1,18 @@ using System; -namespace ObjectPrinting.Tests +namespace ObjectPrinting.Tests; + +public class Person { - public class Person - { - public Guid Id { get; set; } - public string Name { get; set; } - public double Height { get; set; } - public int Age { get; set; } - } + public Guid Id { get; set; } + + public string? Name { get; set; } + + public double Height { get; set; } + + public int Age { get; set; } + + public Person? Child { get; set; } + + public string? Gender; } \ No newline at end of file