diff --git a/ObjectPrinting/Configs/Extensions/PropertyPrintingConfigExtensions.cs b/ObjectPrinting/Configs/Extensions/PropertyPrintingConfigExtensions.cs new file mode 100644 index 000000000..0ece21feb --- /dev/null +++ b/ObjectPrinting/Configs/Extensions/PropertyPrintingConfigExtensions.cs @@ -0,0 +1,22 @@ +using System; +using ObjectPrinting.Configs.Interfaces; + +namespace ObjectPrinting.Configs.Extensions; + +public static class PropertyPrintingConfigExtensions +{ + public static PrintingConfig Trim( + this PropertyPrintingConfig config, + int maxLen) + { + ArgumentOutOfRangeException.ThrowIfNegative(maxLen); + + var parent = ((IChildPrintingConfig)config).ParentConfig; + + var path = config.PropertyPath; + + parent.Settings.StringTrimLengths[path] = maxLen; + + return parent; + } +} \ No newline at end of file diff --git a/ObjectPrinting/Configs/Extensions/TypePrintingConfigExtensions.cs b/ObjectPrinting/Configs/Extensions/TypePrintingConfigExtensions.cs new file mode 100644 index 000000000..831d8dc44 --- /dev/null +++ b/ObjectPrinting/Configs/Extensions/TypePrintingConfigExtensions.cs @@ -0,0 +1,34 @@ +using System; +using System.Numerics; +using ObjectPrinting.Configs.Interfaces; + +namespace ObjectPrinting.Configs.Extensions; + +public static class TypePrintingConfigExtensions +{ + public static PrintingConfig Trim( + this TypePrintingConfig config, + int maxLen) + { + ArgumentOutOfRangeException.ThrowIfNegative(maxLen); + + var parent = ((IChildPrintingConfig)config).ParentConfig; + + parent.Settings.GlobalStringTrimLength = maxLen; + + return parent; + } + + public static PrintingConfig Use( + this TypePrintingConfig config, + IFormatProvider provider) where TPropType : IFormattable + { + ArgumentNullException.ThrowIfNull(provider); + + var parent = ((IChildPrintingConfig)config).ParentConfig; + + parent.Settings.TypeCultures[typeof(TPropType)] = provider; + + return parent; + } +} \ No newline at end of file diff --git a/ObjectPrinting/Configs/Interfaces/IChildPrintingConfig.cs b/ObjectPrinting/Configs/Interfaces/IChildPrintingConfig.cs new file mode 100644 index 000000000..44e7be6d1 --- /dev/null +++ b/ObjectPrinting/Configs/Interfaces/IChildPrintingConfig.cs @@ -0,0 +1,6 @@ +namespace ObjectPrinting.Configs.Interfaces; + +public interface IChildPrintingConfig +{ + PrintingConfig ParentConfig { get; } +} \ No newline at end of file diff --git a/ObjectPrinting/Configs/PrintingConfig.cs b/ObjectPrinting/Configs/PrintingConfig.cs new file mode 100644 index 000000000..f2c5451fc --- /dev/null +++ b/ObjectPrinting/Configs/PrintingConfig.cs @@ -0,0 +1,26 @@ +using System; +using System.Linq.Expressions; +using ObjectPrinting.PrintingHandlers; + +namespace ObjectPrinting.Configs; + +public class PrintingConfig(PrintingSettings settings) +{ + public readonly PrintingSettings Settings = settings; + + public TypePrintingConfig For() + { + return new TypePrintingConfig(this); + } + + public PropertyPrintingConfig For(Expression> selector) + { + return new PropertyPrintingConfig(this, selector); + } + + public string PrintToString(TOwner obj) + { + var engine = new PrintingEngine(Settings); + return engine.Print(obj); + } +} \ No newline at end of file diff --git a/ObjectPrinting/Configs/PropertyPrintingConfig.cs b/ObjectPrinting/Configs/PropertyPrintingConfig.cs new file mode 100644 index 000000000..520784449 --- /dev/null +++ b/ObjectPrinting/Configs/PropertyPrintingConfig.cs @@ -0,0 +1,50 @@ +using System; +using System.Linq.Expressions; +using ObjectPrinting.Configs.Interfaces; + +namespace ObjectPrinting.Configs; + +public class PropertyPrintingConfig : IChildPrintingConfig +{ + private readonly PrintingConfig printingConfig; + public readonly string PropertyPath; + + PrintingConfig IChildPrintingConfig.ParentConfig => printingConfig; + + internal PropertyPrintingConfig(PrintingConfig parentConfig, Expression> selector) + { + printingConfig = parentConfig ?? throw new ArgumentNullException(nameof(parentConfig)); + PropertyPath = BuildMemberPath(selector); + } + + public PrintingConfig Use(Func serializer) + { + ArgumentNullException.ThrowIfNull(serializer); + + printingConfig.Settings.PropertySerializers[PropertyPath] = obj => serializer((TPropType)obj!); + return printingConfig; + } + + public PrintingConfig Exclude() + { + printingConfig.Settings.ExcludedProperties.Add(PropertyPath); + return printingConfig; + } + + private static string BuildMemberPath(Expression> selector) + { + ArgumentNullException.ThrowIfNull(selector); + + var expression = selector.Body; + var parts = new System.Collections.Generic.List(); + while (expression is MemberExpression m) + { + parts.Add(m.Member.Name); + expression = m.Expression; + } + + parts.Reverse(); + var ownerName = typeof(TOwner).Name; + return ownerName + "." + string.Join('.', parts); + } +} \ No newline at end of file diff --git a/ObjectPrinting/Configs/TypePrintingConfig.cs b/ObjectPrinting/Configs/TypePrintingConfig.cs new file mode 100644 index 000000000..4ee9fc53c --- /dev/null +++ b/ObjectPrinting/Configs/TypePrintingConfig.cs @@ -0,0 +1,29 @@ +using System; +using ObjectPrinting.Configs.Interfaces; + +namespace ObjectPrinting.Configs; + +public class TypePrintingConfig : IChildPrintingConfig +{ + private readonly PrintingConfig printingConfig; + + PrintingConfig IChildPrintingConfig.ParentConfig => printingConfig; + + internal TypePrintingConfig(PrintingConfig parentConfig) + { + printingConfig = parentConfig ?? throw new ArgumentNullException(nameof(parentConfig)); + } + + public PrintingConfig Use(Func serializer) + { + ArgumentNullException.ThrowIfNull(serializer); + printingConfig.Settings.TypeSerializers[typeof(TPropType)] = obj => serializer((TPropType)obj!); + return printingConfig; + } + + public PrintingConfig Exclude() + { + printingConfig.Settings.ExcludedTypes.Add(typeof(TPropType)); + return printingConfig; + } +} \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinter.cs b/ObjectPrinting/ObjectPrinter.cs index 3c7867c32..27792ba7a 100644 --- a/ObjectPrinting/ObjectPrinter.cs +++ b/ObjectPrinting/ObjectPrinter.cs @@ -1,10 +1,11 @@ -namespace ObjectPrinting +using ObjectPrinting.Configs; + +namespace ObjectPrinting; + +public class ObjectPrinter { - public class ObjectPrinter + public static PrintingConfig InClass() { - public static PrintingConfig For() - { - return new PrintingConfig(); - } + return new PrintingConfig(new PrintingSettings()); } } \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinterExtensions.cs b/ObjectPrinting/ObjectPrinterExtensions.cs new file mode 100644 index 000000000..6f3287e9e --- /dev/null +++ b/ObjectPrinting/ObjectPrinterExtensions.cs @@ -0,0 +1,19 @@ +using System; +using ObjectPrinting.Configs; + +namespace ObjectPrinting; + +public static class ObjectPrinterExtensions +{ + public static string PrintToString(this T obj) + { + return ObjectPrinter.InClass().PrintToString(obj); + } + + public static string PrintToString(this T obj, Func, PrintingConfig> config) + { + ArgumentNullException.ThrowIfNull(config); + var printer = config(ObjectPrinter.InClass()); + return printer.PrintToString(obj); + } +} \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinting.csproj b/ObjectPrinting/ObjectPrinting.csproj index c5db392ff..f8c85bb5c 100644 --- a/ObjectPrinting/ObjectPrinting.csproj +++ b/ObjectPrinting/ObjectPrinting.csproj @@ -5,8 +5,6 @@ - - - + diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs deleted file mode 100644 index a9e082117..000000000 --- a/ObjectPrinting/PrintingConfig.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Linq; -using System.Text; - -namespace ObjectPrinting -{ - 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(); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/PrintingHandlers/ApplyingSettings/ApplierResult.cs b/ObjectPrinting/PrintingHandlers/ApplyingSettings/ApplierResult.cs new file mode 100644 index 000000000..2cc2a04ce --- /dev/null +++ b/ObjectPrinting/PrintingHandlers/ApplyingSettings/ApplierResult.cs @@ -0,0 +1,27 @@ +namespace ObjectPrinting.PrintingHandlers.ApplyingSettings; + +public readonly struct ApplierResult +{ + public bool Applied { get; } + public bool Exclude { get; } + public string? Serialized { get; } + public object? NewValue { get; } + + private ApplierResult(bool applied, bool exclude, string? serialized, object? newValue) + { + Applied = applied; + Exclude = exclude; + Serialized = serialized; + NewValue = newValue; + } + + public static ApplierResult NotApplied => new(false, false, null, null); + + public static ApplierResult Excluded() => new(true, true, null, null); + + public static ApplierResult SerializedValue(string value) => + new(true, false, value, null); + + public static ApplierResult Modified(object? newValue) => + new(true, false, null, newValue); +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingHandlers/ApplyingSettings/Appliers/CultureSettingsApplier.cs b/ObjectPrinting/PrintingHandlers/ApplyingSettings/Appliers/CultureSettingsApplier.cs new file mode 100644 index 000000000..d5ee6f3a5 --- /dev/null +++ b/ObjectPrinting/PrintingHandlers/ApplyingSettings/Appliers/CultureSettingsApplier.cs @@ -0,0 +1,17 @@ +using System; +using ObjectPrinting.PrintingHandlers.ApplyingSettings.Interfaces; + +namespace ObjectPrinting.PrintingHandlers.ApplyingSettings.Appliers; + +internal class CultureSettingsApplier : ISettingsApplier +{ + public ApplierResult Apply(ValueContext context) + { + if (context.Type == null || + !context.Settings.TypeCultures.TryGetValue(context.Type, out var culture)) + return ApplierResult.NotApplied; + + var str = ((IFormattable)context.Value).ToString(null, culture); + return ApplierResult.SerializedValue(str); + } +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingHandlers/ApplyingSettings/Appliers/ExcludeSettingsApplier.cs b/ObjectPrinting/PrintingHandlers/ApplyingSettings/Appliers/ExcludeSettingsApplier.cs new file mode 100644 index 000000000..15ff23ccf --- /dev/null +++ b/ObjectPrinting/PrintingHandlers/ApplyingSettings/Appliers/ExcludeSettingsApplier.cs @@ -0,0 +1,15 @@ +using ObjectPrinting.PrintingHandlers.ApplyingSettings.Interfaces; + +namespace ObjectPrinting.PrintingHandlers.ApplyingSettings.Appliers; + +internal class ExcludeSettingsApplier : ISettingsApplier +{ + public ApplierResult Apply(ValueContext context) + { + if (context.Settings.ExcludedProperties.Contains(context.Path) || + (context.Type != null && context.Settings.ExcludedTypes.Contains(context.Type))) + return ApplierResult.Excluded(); + + return ApplierResult.NotApplied; + } +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingHandlers/ApplyingSettings/Appliers/SerializerSettingsApplier.cs b/ObjectPrinting/PrintingHandlers/ApplyingSettings/Appliers/SerializerSettingsApplier.cs new file mode 100644 index 000000000..c6eb95dd8 --- /dev/null +++ b/ObjectPrinting/PrintingHandlers/ApplyingSettings/Appliers/SerializerSettingsApplier.cs @@ -0,0 +1,18 @@ +using ObjectPrinting.PrintingHandlers.ApplyingSettings.Interfaces; + +namespace ObjectPrinting.PrintingHandlers.ApplyingSettings.Appliers; + +internal class SerializerSettingsApplier : ISettingsApplier +{ + public ApplierResult Apply(ValueContext context) + { + if (context.Settings.PropertySerializers.TryGetValue(context.Path, out var propertySerializer)) + return ApplierResult.SerializedValue(propertySerializer(context.Value)); + + if (context.Type != null && + context.Settings.TypeSerializers.TryGetValue(context.Type, out var typeSerializer)) + return ApplierResult.SerializedValue(typeSerializer(context.Value)); + + return ApplierResult.NotApplied; + } +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingHandlers/ApplyingSettings/Appliers/TrimStringSettingsApplier.cs b/ObjectPrinting/PrintingHandlers/ApplyingSettings/Appliers/TrimStringSettingsApplier.cs new file mode 100644 index 000000000..9ae5091d9 --- /dev/null +++ b/ObjectPrinting/PrintingHandlers/ApplyingSettings/Appliers/TrimStringSettingsApplier.cs @@ -0,0 +1,25 @@ +using System; +using ObjectPrinting.PrintingHandlers.ApplyingSettings.Interfaces; + +namespace ObjectPrinting.PrintingHandlers.ApplyingSettings.Appliers; + +internal class TrimStringSettingsApplier : ISettingsApplier +{ + public ApplierResult Apply(ValueContext context) + { + if (context.Type != typeof(string) || context.Value is not string s) + return ApplierResult.NotApplied; + + var settings = context.Settings; + + if (settings.StringTrimLengths.TryGetValue(context.Path, out var propertyMax)) + { + return ApplierResult.Modified(s.Length <= propertyMax ? s : s.AsSpan(0, propertyMax).ToString()); + } + + if (settings.GlobalStringTrimLength <= 0) return ApplierResult.NotApplied; + + var max = settings.GlobalStringTrimLength; + return ApplierResult.Modified(s.Length <= max ? s : s.AsSpan(0, max).ToString()); + } +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingHandlers/ApplyingSettings/AppliersRunner.cs b/ObjectPrinting/PrintingHandlers/ApplyingSettings/AppliersRunner.cs new file mode 100644 index 000000000..8c28c6b32 --- /dev/null +++ b/ObjectPrinting/PrintingHandlers/ApplyingSettings/AppliersRunner.cs @@ -0,0 +1,43 @@ +using ObjectPrinting.PrintingHandlers.ApplyingSettings.Appliers; +using ObjectPrinting.PrintingHandlers.ApplyingSettings.Interfaces; + +namespace ObjectPrinting.PrintingHandlers.ApplyingSettings; + +internal class ApplierRunner +{ + private readonly ISettingsApplier[] appliers = + [ + new ExcludeSettingsApplier(), + new TrimStringSettingsApplier(), + new SerializerSettingsApplier(), + new CultureSettingsApplier() + ]; + + public ApplierResult Run(ValueContext context) + { + var currentValue = context.Value; + var anyApplied = false; + + foreach (var applier in appliers) + { + context.Value = currentValue; + + var res = applier.Apply(context); + if (!res.Applied) + continue; + + anyApplied = true; + + if (res.Exclude) + return ApplierResult.Excluded(); + + if (res.Serialized != null) + return ApplierResult.SerializedValue(res.Serialized); + + if (res.NewValue != null) + currentValue = res.NewValue; + } + + return !anyApplied ? ApplierResult.NotApplied : ApplierResult.Modified(currentValue); + } +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingHandlers/ApplyingSettings/Interfaces/ISettingsApplier.cs b/ObjectPrinting/PrintingHandlers/ApplyingSettings/Interfaces/ISettingsApplier.cs new file mode 100644 index 000000000..ce8e9ecb8 --- /dev/null +++ b/ObjectPrinting/PrintingHandlers/ApplyingSettings/Interfaces/ISettingsApplier.cs @@ -0,0 +1,6 @@ +namespace ObjectPrinting.PrintingHandlers.ApplyingSettings.Interfaces; + +internal interface ISettingsApplier +{ + ApplierResult Apply(ValueContext ctx); +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingHandlers/HandlingStrategies/EnumerableStrategy.cs b/ObjectPrinting/PrintingHandlers/HandlingStrategies/EnumerableStrategy.cs new file mode 100644 index 000000000..fe71b21f1 --- /dev/null +++ b/ObjectPrinting/PrintingHandlers/HandlingStrategies/EnumerableStrategy.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections; +using System.Text; +using ObjectPrinting.PrintingHandlers.HandlingStrategies.Interfaces; + +namespace ObjectPrinting.PrintingHandlers.HandlingStrategies +{ + internal class EnumerableStrategy : IStrategy + { + public bool CanHandle(ValueContext context) + { + return context.Value switch + { + null or string => false, + _ => context.Value is IEnumerable + }; + } + + public string Print(ValueContext context, Func recurse) + { + var sequence = (IEnumerable)context.Value!; + var type = context.Type ?? sequence.GetType(); + var sb = new StringBuilder(); + sb.AppendLine($"{type.Name} ["); + + var i = 0; + foreach (var item in sequence) + { + var childContext = new ValueContext + { + Value = item, + Type = item?.GetType(), + Path = $"{context.Path}[{i}]", + Indent = context.Indent + 1, + Settings = context.Settings, + Visited = context.Visited + }; + + var printed = recurse(childContext); + var prefix = new string('\t', context.Indent + 1); + + sb.Append(prefix).Append($"[{i}] = "); + + sb.AppendLine(printed == "" ? "null" : printed); + + i++; + } + + sb.Append(new string('\t', context.Indent)).Append("]"); + + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingHandlers/HandlingStrategies/Helpers/TypeHelpers.cs b/ObjectPrinting/PrintingHandlers/HandlingStrategies/Helpers/TypeHelpers.cs new file mode 100644 index 000000000..ab47ee696 --- /dev/null +++ b/ObjectPrinting/PrintingHandlers/HandlingStrategies/Helpers/TypeHelpers.cs @@ -0,0 +1,12 @@ +using System; + +namespace ObjectPrinting.PrintingHandlers.HandlingStrategies.Helpers; + +internal static class TypeHelpers +{ + public static bool IsTypePrimitive(this Type? type) + { + if (type == null) return false; + return type.IsPrimitive || type == typeof(decimal) || type == typeof(Guid); + } +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingHandlers/HandlingStrategies/Interfaces/IStrategy.cs b/ObjectPrinting/PrintingHandlers/HandlingStrategies/Interfaces/IStrategy.cs new file mode 100644 index 000000000..028ebf92c --- /dev/null +++ b/ObjectPrinting/PrintingHandlers/HandlingStrategies/Interfaces/IStrategy.cs @@ -0,0 +1,10 @@ +using System; + +namespace ObjectPrinting.PrintingHandlers.HandlingStrategies.Interfaces +{ + internal interface IStrategy + { + bool CanHandle(ValueContext context); + string Print(ValueContext context, Func recurse); + } +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingHandlers/HandlingStrategies/NullStrategy.cs b/ObjectPrinting/PrintingHandlers/HandlingStrategies/NullStrategy.cs new file mode 100644 index 000000000..3a8106228 --- /dev/null +++ b/ObjectPrinting/PrintingHandlers/HandlingStrategies/NullStrategy.cs @@ -0,0 +1,11 @@ +using System; +using ObjectPrinting.PrintingHandlers.HandlingStrategies.Interfaces; + +namespace ObjectPrinting.PrintingHandlers.HandlingStrategies; + +internal class NullStrategy : IStrategy +{ + public bool CanHandle(ValueContext context) => context.Value == null; + + public string Print(ValueContext context, Func recurse) => "null"; +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingHandlers/HandlingStrategies/ObjectStrategy.cs b/ObjectPrinting/PrintingHandlers/HandlingStrategies/ObjectStrategy.cs new file mode 100644 index 000000000..1e53a12e0 --- /dev/null +++ b/ObjectPrinting/PrintingHandlers/HandlingStrategies/ObjectStrategy.cs @@ -0,0 +1,71 @@ +using System; +using System.Reflection; +using System.Text; +using ObjectPrinting.PrintingHandlers.HandlingStrategies.Helpers; +using ObjectPrinting.PrintingHandlers.HandlingStrategies.Interfaces; + +namespace ObjectPrinting.PrintingHandlers.HandlingStrategies +{ + internal class ObjectStrategy : IStrategy + { + public bool CanHandle(ValueContext context) + { + if (context.Value is null or string) return false; + + var type = context.Type; + return !type.IsTypePrimitive(); + } + + public string Print(ValueContext context, Func recurse) + { + var obj = context.Value!; + var type = context.Type ?? obj.GetType(); + + if (!type.IsValueType) + { + if (!context.Visited.Add(obj)) + return "[Cyclic Reference]"; + } + + var sb = new StringBuilder(); + sb.AppendLine(type.Name); + + var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + foreach (var p in properties) + { + var childPath = $"{type.Name}.{p.Name}"; + var value = p.GetValue(obj); + + var childContext = new ValueContext + { + Value = value, + Type = value?.GetType(), + Path = childPath, + Indent = context.Indent, + Settings = context.Settings, + Visited = context.Visited + }; + + var printed = recurse(childContext); + if (printed == "") continue; + + var prefix = new string('\t', context.Indent + 1); + if (printed.Contains(Environment.NewLine)) + { + sb.Append(prefix).Append(p.Name).Append(" = ").AppendLine(); + var childIndent = new string('\t', context.Indent + 2); + var lines = printed.Split([Environment.NewLine], StringSplitOptions.None); + + foreach (var line in lines) + sb.Append(childIndent).AppendLine(line); + } + else + { + sb.Append(prefix).Append(p.Name).Append(" = ").AppendLine(printed); + } + } + + return sb.ToString().TrimEnd('\r', '\n'); + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingHandlers/HandlingStrategies/PrimitiveStrategy.cs b/ObjectPrinting/PrintingHandlers/HandlingStrategies/PrimitiveStrategy.cs new file mode 100644 index 000000000..6b47b01eb --- /dev/null +++ b/ObjectPrinting/PrintingHandlers/HandlingStrategies/PrimitiveStrategy.cs @@ -0,0 +1,19 @@ +using System; +using ObjectPrinting.PrintingHandlers.HandlingStrategies.Helpers; +using ObjectPrinting.PrintingHandlers.HandlingStrategies.Interfaces; + +namespace ObjectPrinting.PrintingHandlers.HandlingStrategies; + +internal class PrimitiveStrategy : IStrategy +{ + public bool CanHandle(ValueContext context) + { + var type = context.Type; + return type.IsTypePrimitive(); + } + + public string Print(ValueContext context, Func recurse) + { + return context.Value?.ToString() ?? "null"; + } +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingHandlers/HandlingStrategies/StrategySelector.cs b/ObjectPrinting/PrintingHandlers/HandlingStrategies/StrategySelector.cs new file mode 100644 index 000000000..b69cc2ee1 --- /dev/null +++ b/ObjectPrinting/PrintingHandlers/HandlingStrategies/StrategySelector.cs @@ -0,0 +1,21 @@ +using System.Linq; +using ObjectPrinting.PrintingHandlers.HandlingStrategies.Interfaces; + +namespace ObjectPrinting.PrintingHandlers.HandlingStrategies; + +internal class StrategySelector +{ + private readonly IStrategy[] strategies = + [ + new NullStrategy(), + new PrimitiveStrategy(), + new StringStrategy(), + new EnumerableStrategy(), + new ObjectStrategy() + ]; + + public IStrategy Select(ValueContext context) + { + return strategies.First(s => s.CanHandle(context)); + } +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingHandlers/HandlingStrategies/StringStrategy.cs b/ObjectPrinting/PrintingHandlers/HandlingStrategies/StringStrategy.cs new file mode 100644 index 000000000..a041678a6 --- /dev/null +++ b/ObjectPrinting/PrintingHandlers/HandlingStrategies/StringStrategy.cs @@ -0,0 +1,14 @@ +using System; +using ObjectPrinting.PrintingHandlers.HandlingStrategies.Interfaces; + +namespace ObjectPrinting.PrintingHandlers.HandlingStrategies; + +internal class StringStrategy : IStrategy +{ + public bool CanHandle(ValueContext context) => context.Value is string; + + public string Print(ValueContext context, Func recurse) + { + return (string?)context.Value ?? "null"; + } +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingHandlers/PrintingEngine.cs b/ObjectPrinting/PrintingHandlers/PrintingEngine.cs new file mode 100644 index 000000000..30271a8f2 --- /dev/null +++ b/ObjectPrinting/PrintingHandlers/PrintingEngine.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using ObjectPrinting.PrintingHandlers.ApplyingSettings; +using ObjectPrinting.PrintingHandlers.HandlingStrategies; + +namespace ObjectPrinting.PrintingHandlers +{ + internal class PrintingEngine(PrintingSettings settings) + { + private readonly PrintingSettings settings = settings ?? throw new ArgumentNullException(nameof(settings)); + private readonly ApplierRunner applierRunner = new(); + private readonly StrategySelector strategySelector = new(); + + public string Print(object? root) + { + var visited = new HashSet(ReferenceEqualityComparer.Instance); + var context = new ValueContext + { + Value = root, + Type = root?.GetType(), + Path = root?.GetType()?.Name ?? "null", + Indent = 0, + Settings = settings, + Visited = visited + }; + + return PrintInternal(context).TrimEnd('\r', '\n'); + } + + private string PrintInternal(ValueContext context) + { + var result = applierRunner.Run(context); + + if (result.Applied) + { + if (result.Exclude) + return ""; + + if (result.Serialized != null) + return result.Serialized; + + if (result.NewValue != null) + { + context.Value = result.NewValue; + context.Type = result.NewValue?.GetType(); + } + } + + var strategy = strategySelector.Select(context); + return strategy.Print(context, PrintInternal); + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingHandlers/ValueContext.cs b/ObjectPrinting/PrintingHandlers/ValueContext.cs new file mode 100644 index 000000000..3205e9967 --- /dev/null +++ b/ObjectPrinting/PrintingHandlers/ValueContext.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace ObjectPrinting.PrintingHandlers +{ + internal sealed class ValueContext + { + public object? Value { get; set; } + public Type? Type { get; set; } + public string Path { get; init; } = ""; + public int Indent { get; init; } + public PrintingSettings Settings { get; init; } = null!; + public HashSet Visited { get; init; } = null!; + } +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingSettings.cs b/ObjectPrinting/PrintingSettings.cs new file mode 100644 index 000000000..39102a74f --- /dev/null +++ b/ObjectPrinting/PrintingSettings.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace ObjectPrinting; + +public class PrintingSettings +{ + public HashSet ExcludedTypes { get; } = []; + public HashSet ExcludedProperties { get; } = []; + public Dictionary> TypeSerializers { get; } = new(); + public Dictionary> PropertySerializers { get; } = new(); + public Dictionary TypeCultures { get; } = new(); + public Dictionary StringTrimLengths { get; } = new(); + public int GlobalStringTrimLength { get; set; } = 0; +} \ No newline at end of file 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 deleted file mode 100644 index 4c8b2445c..000000000 --- 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 f95559554..000000000 --- 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/ExpectedTestsResults/ObjectPrinter_ShouldApplyCulture_WhenCultureIsSpecifiedForType_Test.txt b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldApplyCulture_WhenCultureIsSpecifiedForType_Test.txt new file mode 100644 index 000000000..d9bc8c004 --- /dev/null +++ b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldApplyCulture_WhenCultureIsSpecifiedForType_Test.txt @@ -0,0 +1,7 @@ +Person + Id = 00000000-0000-0000-0000-000000000000 + Name = Alex + Height = 1234,56 + Age = 10 + Friend = null + Scores = null \ No newline at end of file diff --git a/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldExcludeProperty_WhenPropertyIsExcluded_Test.txt b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldExcludeProperty_WhenPropertyIsExcluded_Test.txt new file mode 100644 index 000000000..bf419c2b0 --- /dev/null +++ b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldExcludeProperty_WhenPropertyIsExcluded_Test.txt @@ -0,0 +1,6 @@ +Person + Id = 00000000-0000-0000-0000-000000000000 + Name = Alex + Height = 181 + Friend = null + Scores = null \ No newline at end of file diff --git a/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldExcludeType_WhenTypeIsExcluded_Test.txt b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldExcludeType_WhenTypeIsExcluded_Test.txt new file mode 100644 index 000000000..8c35b0a2c --- /dev/null +++ b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldExcludeType_WhenTypeIsExcluded_Test.txt @@ -0,0 +1,6 @@ +Person + Name = Alex + Height = 181 + Age = 30 + Friend = null + Scores = null \ No newline at end of file diff --git a/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldHandleCyclicReferences_WhenCycleDetected_Test.txt b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldHandleCyclicReferences_WhenCycleDetected_Test.txt new file mode 100644 index 000000000..9ef955cc2 --- /dev/null +++ b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldHandleCyclicReferences_WhenCycleDetected_Test.txt @@ -0,0 +1,14 @@ +Person + Id = 00000000-0000-0000-0000-000000000000 + Name = A + Height = 0 + Age = 0 + Friend = + Person + Id = 00000000-0000-0000-0000-000000000000 + Name = B + Height = 0 + Age = 0 + Friend = [Cyclic Reference] + Scores = null + Scores = null \ No newline at end of file diff --git a/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldIncreaseIndentation_WhenObjectIsNested_Test.txt b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldIncreaseIndentation_WhenObjectIsNested_Test.txt new file mode 100644 index 000000000..597268bd9 --- /dev/null +++ b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldIncreaseIndentation_WhenObjectIsNested_Test.txt @@ -0,0 +1,14 @@ +Person + Id = 00000000-0000-0000-0000-000000000000 + Name = Alex + Height = 180 + Age = 10 + Friend = + Person + Id = 00000000-0000-0000-0000-000000000000 + Name = John + Height = 150 + Age = 20 + Friend = null + Scores = null + Scores = null \ No newline at end of file diff --git a/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldPrintDictionary_WhenDictionaryContainsPrimitives_Test.txt b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldPrintDictionary_WhenDictionaryContainsPrimitives_Test.txt new file mode 100644 index 000000000..2eae8158b --- /dev/null +++ b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldPrintDictionary_WhenDictionaryContainsPrimitives_Test.txt @@ -0,0 +1,8 @@ +Dictionary`2 [ + [0] = KeyValuePair`2 + Key = one + Value = 1 + [1] = KeyValuePair`2 + Key = two + Value = 2 +] \ No newline at end of file diff --git a/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldPrintEmptyObject_WhenAllPropertiesDefault_Test.txt b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldPrintEmptyObject_WhenAllPropertiesDefault_Test.txt new file mode 100644 index 000000000..55590cb3c --- /dev/null +++ b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldPrintEmptyObject_WhenAllPropertiesDefault_Test.txt @@ -0,0 +1,7 @@ +Person + Id = 00000000-0000-0000-0000-000000000000 + Name = null + Height = 0 + Age = 0 + Friend = null + Scores = null \ No newline at end of file diff --git a/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldPrintList_WhenListContainsPrimitives_Test.txt b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldPrintList_WhenListContainsPrimitives_Test.txt new file mode 100644 index 000000000..4c928a850 --- /dev/null +++ b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldPrintList_WhenListContainsPrimitives_Test.txt @@ -0,0 +1,12 @@ +Person + Id = 00000000-0000-0000-0000-000000000000 + Name = Test + Height = 100 + Age = 10 + Friend = null + Scores = + List`1 [ + [0] = 1 + [1] = 2 + [2] = 3 + ] \ No newline at end of file diff --git a/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldPrintNestedCollections_WhenCollectionsAreNested_Test.txt b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldPrintNestedCollections_WhenCollectionsAreNested_Test.txt new file mode 100644 index 000000000..99469d219 --- /dev/null +++ b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldPrintNestedCollections_WhenCollectionsAreNested_Test.txt @@ -0,0 +1,10 @@ +List`1 [ + [0] = List`1 [ + [0] = 1 + [1] = 2 + ] + [1] = List`1 [ + [0] = 3 + [1] = 4 + ] +] \ No newline at end of file diff --git a/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldPrintNestedObject_WhenObjectContainsInnerObject_Test.txt b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldPrintNestedObject_WhenObjectContainsInnerObject_Test.txt new file mode 100644 index 000000000..aeb38bc70 --- /dev/null +++ b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldPrintNestedObject_WhenObjectContainsInnerObject_Test.txt @@ -0,0 +1,14 @@ +Person + Id = 00000000-0000-0000-0000-000000000000 + Name = Alex + Height = 180 + Age = 30 + Friend = + Person + Id = 00000000-0000-0000-0000-000000000000 + Name = John + Height = 150 + Age = 20 + Friend = null + Scores = null + Scores = null \ No newline at end of file diff --git a/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldPrintSimpleObject_WhenPropertiesArePrimitive_Test.txt b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldPrintSimpleObject_WhenPropertiesArePrimitive_Test.txt new file mode 100644 index 000000000..a4be0ef3d --- /dev/null +++ b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldPrintSimpleObject_WhenPropertiesArePrimitive_Test.txt @@ -0,0 +1,7 @@ +Person + Id = 00000000-0000-0000-0000-000000000000 + Name = Alex + Height = 180,5 + Age = 25 + Friend = null + Scores = null \ No newline at end of file diff --git a/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldTrimStrings_WhenTrimIsApplied_Test.txt b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldTrimStrings_WhenTrimIsApplied_Test.txt new file mode 100644 index 000000000..5115a2933 --- /dev/null +++ b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldTrimStrings_WhenTrimIsApplied_Test.txt @@ -0,0 +1,7 @@ +Person + Id = 00000000-0000-0000-0000-000000000000 + Name = Alex + Height = 100 + Age = 5 + Friend = null + Scores = null \ No newline at end of file diff --git a/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldUseCustomPropertySerializer_WhenSerializerForPropertyProvided_Test.txt b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldUseCustomPropertySerializer_WhenSerializerForPropertyProvided_Test.txt new file mode 100644 index 000000000..03fd1d838 --- /dev/null +++ b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldUseCustomPropertySerializer_WhenSerializerForPropertyProvided_Test.txt @@ -0,0 +1,7 @@ +Person + Id = 00000000-0000-0000-0000-000000000000 + Name = NAME(Alex) + Height = 170 + Age = 18 + Friend = null + Scores = null \ No newline at end of file diff --git a/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldUseCustomTypeSerializer_WhenSerializerForTypeProvided_Test.txt b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldUseCustomTypeSerializer_WhenSerializerForTypeProvided_Test.txt new file mode 100644 index 000000000..527765ede --- /dev/null +++ b/ObjectPrintingTests/ExpectedTestsResults/ObjectPrinter_ShouldUseCustomTypeSerializer_WhenSerializerForTypeProvided_Test.txt @@ -0,0 +1,7 @@ +Person + Id = 00000000-0000-0000-0000-000000000000 + Name = Alex + Height = 170 + Age = Int:18 + Friend = null + Scores = null \ No newline at end of file diff --git a/ObjectPrintingTests/Models/Person.cs b/ObjectPrintingTests/Models/Person.cs new file mode 100644 index 000000000..ca8824ef2 --- /dev/null +++ b/ObjectPrintingTests/Models/Person.cs @@ -0,0 +1,11 @@ +namespace ObjectPrintingTests.Models; + +public class Person +{ + public Guid Id { get; set; } + public string Name { get; set; } + public double Height { get; set; } + public int Age { get; set; } + public Person Friend { get; set; } + public List Scores { get; set; } +} \ No newline at end of file diff --git a/ObjectPrintingTests/ObjectPrinterAcceptanceTests.cs b/ObjectPrintingTests/ObjectPrinterAcceptanceTests.cs new file mode 100644 index 000000000..4aa134e1f --- /dev/null +++ b/ObjectPrintingTests/ObjectPrinterAcceptanceTests.cs @@ -0,0 +1,48 @@ +using System.Globalization; +using ObjectPrinting; +using ObjectPrinting.Configs.Extensions; +using ObjectPrintingTests.Models; + +namespace ObjectPrintingTests; + +[TestFixture] +public class ObjectPrinterAcceptanceTests +{ + [Test] + public void Demo() + { + var person = new Person { Name = "Alex", Age = 19, Height = 178.2, Id = Guid.NewGuid() }; + + var printer = ObjectPrinter + .InClass() + + //1. Исключить из сериализации свойства определенного типа + .For().Exclude() + + //2. Указать альтернативный способ сериализации для определенного типа + .For().Use(d => $"{d:F1} cm") + + //3. Для числовых типов указать культуру + .For().Use(CultureInfo.InvariantCulture) + + //4. Настроить сериализацию конкретного свойства + .For(p => p.Name).Use(name => $"Current name is {name}") + + //5. Настроить обрезание строковых свойств + .For().Trim(4) + .For(p => p.Name).Trim(3) + + //6. Исключить конкретное свойство + .For(p => p.Age).Exclude(); + + var s1 = printer.PrintToString(person); + + //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию + var s2 = person.PrintToString(); + //8. ...с конфигурированием + var s3 = person.PrintToString(config => config + .For().Exclude() + .For(p => p.Name).Trim(2) + ); + } +} \ No newline at end of file diff --git a/ObjectPrintingTests/ObjectPrinterTests.cs b/ObjectPrintingTests/ObjectPrinterTests.cs new file mode 100644 index 000000000..c34c0bcd8 --- /dev/null +++ b/ObjectPrintingTests/ObjectPrinterTests.cs @@ -0,0 +1,358 @@ +using System.Globalization; +using FluentAssertions; +using ObjectPrinting; +using ObjectPrinting.Configs; +using ObjectPrinting.Configs.Extensions; +using ObjectPrintingTests.Models; + +namespace ObjectPrintingTests; + +public class ObjectPrinterTests +{ + private static string LoadExpected() => + File.ReadAllText( + $"{TestContext.CurrentContext.TestDirectory}/../../../ExpectedTestsResults/{TestContext.CurrentContext.Test.MethodName}.txt"); + + [Test] + public void ObjectPrinter_ShouldPrintNull_WhenObjectIsNull_Test() + { + var printer = ObjectPrinter.InClass(); + var result = printer.PrintToString(null); + result.Should().Be("null"); + } + + [Test] + public void ObjectPrinter_ShouldPrintFullProperty_WhenSerializerReturnsNull_Test() + { + var person = new Person { Name = "Alex" }; + + var printer = ObjectPrinter.InClass() + .For(p => p.Name).Use(_ => null); + + var result = printer.PrintToString(person); + + result.Should().Contain("Name = Alex"); + } + + [Test] + public void ObjectPrinter_ShouldPrintSimpleObject_WhenPropertiesArePrimitive_Test() + { + var person = new Person + { + Id = Guid.Empty, + Name = "Alex", + Height = 180.5, + Age = 25, + Friend = null, + Scores = null + }; + + var printer = ObjectPrinter.InClass(); + var result = printer.PrintToString(person); + + var expected = LoadExpected(); + result.Should().Be(expected); + } + + [Test] + public void ObjectPrinter_ShouldExcludeType_WhenTypeIsExcluded_Test() + { + var person = new Person + { + Id = Guid.Empty, + Name = "Alex", + Height = 181, + Age = 30, + Friend = null, + Scores = null + }; + + var printer = ObjectPrinter.InClass().For().Exclude(); + var result = printer.PrintToString(person); + + var expected = LoadExpected(); + result.Should().Be(expected); + } + + [Test] + public void ObjectPrinter_ShouldExcludeProperty_WhenPropertyIsExcluded_Test() + { + var person = new Person + { + Id = Guid.Empty, + Name = "Alex", + Height = 181, + Age = 30, + Friend = null, + Scores = null + }; + + var printer = ObjectPrinter.InClass().For(p => p.Age).Exclude(); + var result = printer.PrintToString(person); + + var expected = LoadExpected(); + result.Should().Be(expected); + } + + [Test] + public void ObjectPrinter_ShouldUseCustomTypeSerializer_WhenSerializerForTypeProvided_Test() + { + var person = new Person + { + Id = Guid.Empty, + Name = "Alex", + Height = 170, + Age = 18, + Friend = null, + Scores = null + }; + + var printer = ObjectPrinter.InClass() + .For().Use(i => $"Int:{i}"); + + var result = printer.PrintToString(person); + var expected = LoadExpected(); + + result.Should().Be(expected); + } + + [Test] + public void ObjectPrinter_ShouldUseCustomPropertySerializer_WhenSerializerForPropertyProvided_Test() + { + var person = new Person + { + Id = Guid.Empty, + Name = "Alex", + Height = 170, + Age = 18, + Friend = null, + Scores = null + }; + + var printer = ObjectPrinter.InClass() + .For(p => p.Name).Use(v => $"NAME({v})"); + + var result = printer.PrintToString(person); + var expected = LoadExpected(); + + result.Should().Be(expected); + } + + [Test] + public void ObjectPrinter_ShouldApplyCulture_WhenCultureIsSpecifiedForType_Test() + { + var person = new Person + { + Id = Guid.Empty, + Name = "Alex", + Height = 1234.56, + Age = 10, + Friend = null, + Scores = null + }; + + var printer = ObjectPrinter.InClass() + .For().Use(new CultureInfo("de-DE")); + + var result = printer.PrintToString(person); + var expected = LoadExpected(); + + result.Should().Be(expected); + } + + [Test] + public void ObjectPrinter_ShouldTrimStrings_WhenTrimIsApplied_Test() + { + var person = new Person + { + Id = Guid.Empty, + Name = "Alexander", + Height = 100, + Age = 5, + Friend = null, + Scores = null + }; + + var printer = ObjectPrinter.InClass() + .For(p => p.Name).Trim(4); + + var result = printer.PrintToString(person); + var expected = LoadExpected(); + + result.Should().Be(expected); + } + + [Test] + public void ObjectPrinter_ShouldPrintList_WhenListContainsPrimitives_Test() + { + var person = new Person + { + Id = Guid.Empty, + Name = "Test", + Height = 100, + Age = 10, + Friend = null, + Scores = new List { 1, 2, 3 } + }; + + var printer = ObjectPrinter.InClass(); + var result = printer.PrintToString(person); + var expected = LoadExpected(); + + result.Should().Be(expected); + } + + [Test] + public void ObjectPrinter_ShouldPrintNestedObject_WhenObjectContainsInnerObject_Test() + { + var friend = new Person + { + Id = Guid.Empty, + Name = "John", + Height = 150, + Age = 20 + }; + + var person = new Person + { + Id = Guid.Empty, + Name = "Alex", + Height = 180, + Age = 30, + Friend = friend + }; + + var printer = ObjectPrinter.InClass(); + var result = printer.PrintToString(person); + + var expected = LoadExpected(); + result.Should().Be(expected); + } + + [Test] + public void ObjectPrinter_ShouldHandleCyclicReferences_WhenCycleDetected_Test() + { + var a = new Person { Name = "A" }; + var b = new Person { Name = "B" }; + a.Friend = b; + b.Friend = a; + + var printer = ObjectPrinter.InClass(); + var result = printer.PrintToString(a); + + var expected = LoadExpected(); + result.Should().Be(expected); + } + + [Test] + public void ObjectPrinter_ShouldIncreaseIndentation_WhenObjectIsNested_Test() + { + var person = new Person + { + Id = Guid.Empty, + Name = "Alex", + Height = 180, + Age = 10, + Friend = new Person + { + Id = Guid.Empty, + Name = "John", + Height = 150, + Age = 20 + } + }; + + var printer = ObjectPrinter.InClass(); + var result = printer.PrintToString(person); + + var expected = LoadExpected(); + result.Should().Be(expected); + } + + [Test] + public void ObjectPrinter_ShouldPrintEmptyObject_WhenAllPropertiesDefault_Test() + { + var person = new Person(); + var printer = ObjectPrinter.InClass(); + + var result = printer.PrintToString(person); + var expected = LoadExpected(); + + result.Should().Be(expected); + } + + [Test] + public void ObjectPrinter_ShouldPrintDictionary_WhenDictionaryContainsPrimitives_Test() + { + var dict = new Dictionary + { + ["one"] = 1, + ["two"] = 2 + }; + + var printer = ObjectPrinter.InClass>(); + var result = printer.PrintToString(dict); + + var expected = LoadExpected(); + result.Should().Be(expected); + } + + [Test] + public void ObjectPrinter_ShouldPrintNestedCollections_WhenCollectionsAreNested_Test() + { + var data = new List> + { + new() { 1, 2 }, + new() { 3, 4 } + }; + + var printer = ObjectPrinter.InClass>>(); + var result = printer.PrintToString(data); + + var expected = LoadExpected(); + result.Should().Be(expected); + } + + [Test] + public void ObjectPrinter_ShouldFormatDateTime_WithCustomCulture_Test() + { + var date = new DateTime(2025, 5, 1, 13, 45, 0); + + var printer = ObjectPrinter.InClass() + .For().Use(new CultureInfo("de-DE")); + + var result = printer.PrintToString(date); + + result.Should().Be("01.05.2025 13:45:00"); + } + + [Test] + public void ObjectPrinter_ShouldApplyTrimBeforeSerializer_Test() + { + var person = new Person { Name = "Alexander" }; + + var printer = ObjectPrinter + .InClass() + .For(p => p.Name).Trim(4) + .For(p => p.Name).Use(s => $"<{s}>"); + + + var result = printer.PrintToString(person); + + result.Should().Contain(""); + } + + [Test] + public void ObjectPrinter_ShouldApplyCultureThenTypeSerializer_Test() + { + var person = new Person { Height = 1234.567 }; + + var printer = ObjectPrinter.InClass() + .For().Use(new CultureInfo("fr-FR")) + .For().Use(d => $"<{d}>"); + + var result = printer.PrintToString(person); + + result.Should().Contain("<1234,567>"); + } +} \ No newline at end of file diff --git a/ObjectPrintingTests/ObjectPrintingTests.csproj b/ObjectPrintingTests/ObjectPrintingTests.csproj new file mode 100644 index 000000000..94438164d --- /dev/null +++ b/ObjectPrintingTests/ObjectPrintingTests.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + diff --git a/fluent-api.sln b/fluent-api.sln index 69c8db9ed..32e4f0fd7 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", "{D7C7A9DE-63A2-493A-BFA4-903A407FD2D4}" +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 + {D7C7A9DE-63A2-493A-BFA4-903A407FD2D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7C7A9DE-63A2-493A-BFA4-903A407FD2D4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7C7A9DE-63A2-493A-BFA4-903A407FD2D4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7C7A9DE-63A2-493A-BFA4-903A407FD2D4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE